OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "chrome/browser/ui/cocoa/draggable_button.h" | 5 #import "chrome/browser/ui/cocoa/draggable_button.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #import "base/memory/scoped_nsobject.h" | |
9 | |
10 namespace { | |
11 | |
12 // Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>. | |
13 // TODO(viettrungluu): Do we want common, standard code for drag hysteresis? | |
14 const CGFloat kWebDragStartHysteresisX = 5.0; | |
15 const CGFloat kWebDragStartHysteresisY = 5.0; | |
16 const CGFloat kDragExpirationTimeout = 0.45; | |
17 | |
18 } | |
19 | 8 |
20 @implementation DraggableButton | 9 @implementation DraggableButton |
21 | 10 |
22 @synthesize draggable = draggable_; | |
23 @synthesize actsOnMouseDown = actsOnMouseDown_; | |
24 @synthesize durationMouseWasDown = durationMouseWasDown_; | |
25 @synthesize actionHasFired = actionHasFired_; | |
26 @synthesize whenMouseDown = whenMouseDown_; | |
27 | |
28 | |
29 - (id)initWithFrame:(NSRect)frame { | 11 - (id)initWithFrame:(NSRect)frame { |
30 if ((self = [super initWithFrame:frame])) { | 12 if ((self = [super initWithFrame:frame])) { |
31 draggable_ = YES; | 13 draggableButtonImpl_.reset( |
32 actsOnMouseDown_ = NO; | 14 [[DraggableButtonImpl alloc] initWithButton:self]); |
33 actionHasFired_ = NO; | |
34 } | 15 } |
35 return self; | 16 return self; |
36 } | 17 } |
37 | 18 |
38 - (id)initWithCoder:(NSCoder*)coder { | 19 - (id)initWithCoder:(NSCoder*)coder { |
39 if ((self = [super initWithCoder:coder])) { | 20 if ((self = [super initWithCoder:coder])) { |
40 draggable_ = YES; | 21 draggableButtonImpl_.reset( |
41 actsOnMouseDown_ = NO; | 22 [[DraggableButtonImpl alloc] initWithButton:self]); |
42 actionHasFired_ = NO; | |
43 } | 23 } |
44 return self; | 24 return self; |
45 } | 25 } |
46 | 26 |
47 - (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta | 27 - (DraggableButtonImpl*)draggableButton { |
48 yDelta:(float)yDelta | 28 return draggableButtonImpl_.get(); |
49 xHysteresis:(float)xHysteresis | |
50 yHysteresis:(float)yHysteresis { | |
51 return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); | |
52 } | |
53 | |
54 - (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta | |
55 yDelta:(float)yDelta | |
56 xHysteresis:(float)xHysteresis | |
57 yHysteresis:(float)yHysteresis { | |
58 return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis); | |
59 } | |
60 | |
61 | |
62 // Determine whether a mouse down should turn into a drag; started as copy of | |
63 // NSTableView code. | |
64 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | |
65 withExpiration:(NSDate*)expiration | |
66 xHysteresis:(float)xHysteresis | |
67 yHysteresis:(float)yHysteresis { | |
68 if ([mouseDownEvent type] != NSLeftMouseDown) { | |
69 return NO; | |
70 } | |
71 | |
72 NSEvent* nextEvent = nil; | |
73 NSEvent* firstEvent = nil; | |
74 NSEvent* dragEvent = nil; | |
75 NSEvent* mouseUp = nil; | |
76 BOOL dragIt = NO; | |
77 | |
78 while ((nextEvent = [[self window] | |
79 nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask) | |
80 untilDate:expiration | |
81 inMode:NSEventTrackingRunLoopMode | |
82 dequeue:YES]) != nil) { | |
83 if (firstEvent == nil) { | |
84 firstEvent = nextEvent; | |
85 } | |
86 if ([nextEvent type] == NSLeftMouseDragged) { | |
87 float deltax = [nextEvent locationInWindow].x - | |
88 [mouseDownEvent locationInWindow].x; | |
89 float deltay = [nextEvent locationInWindow].y - | |
90 [mouseDownEvent locationInWindow].y; | |
91 dragEvent = nextEvent; | |
92 if ([self deltaIndicatesConclusionReachedWithXDelta:deltax | |
93 yDelta:deltay | |
94 xHysteresis:xHysteresis | |
95 yHysteresis:yHysteresis]) { | |
96 dragIt = [self deltaIndicatesDragStartWithXDelta:deltax | |
97 yDelta:deltay | |
98 xHysteresis:xHysteresis | |
99 yHysteresis:yHysteresis]; | |
100 break; | |
101 } | |
102 } else if ([nextEvent type] == NSLeftMouseUp) { | |
103 mouseUp = nextEvent; | |
104 break; | |
105 } | |
106 } | |
107 | |
108 // Since we've been dequeuing the events (If we don't, we'll never see | |
109 // the mouse up...), we need to push some of the events back on. | |
110 // It makes sense to put the first and last drag events and the mouse | |
111 // up if there was one. | |
112 if (mouseUp != nil) { | |
113 [NSApp postEvent:mouseUp atStart:YES]; | |
114 } | |
115 if (dragEvent != nil) { | |
116 [NSApp postEvent:dragEvent atStart:YES]; | |
117 } | |
118 if (firstEvent != mouseUp && firstEvent != dragEvent) { | |
119 [NSApp postEvent:firstEvent atStart:YES]; | |
120 } | |
121 | |
122 return dragIt; | |
123 } | |
124 | |
125 - (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent | |
126 withExpiration:(NSDate*)expiration { | |
127 return [self dragShouldBeginFromMouseDown:mouseDownEvent | |
128 withExpiration:expiration | |
129 xHysteresis:kWebDragStartHysteresisX | |
130 yHysteresis:kWebDragStartHysteresisY]; | |
131 } | 29 } |
132 | 30 |
133 - (void)mouseUp:(NSEvent*)theEvent { | 31 - (void)mouseUp:(NSEvent*)theEvent { |
134 durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; | 32 if ([draggableButtonImpl_ mouseUp:theEvent] == kDraggableButtonMixinCallSuper) |
| 33 [super mouseUp:theEvent]; |
| 34 } |
135 | 35 |
136 if (actionHasFired_) | 36 - (void)mouseDown:(NSEvent*)theEvent { |
137 return; | 37 if ([draggableButtonImpl_ mouseDown:theEvent] == |
138 | 38 kDraggableButtonMixinCallSuper) { |
139 if (!draggable_) { | 39 [super mouseDown:theEvent]; |
140 [super mouseUp:theEvent]; | |
141 return; | |
142 } | |
143 | |
144 // There are non-drag cases where a mouseUp: may happen | |
145 // (e.g. mouse-down, cmd-tab to another application, move mouse, | |
146 // mouse-up). So we check. | |
147 NSPoint viewLocal = [self convertPoint:[theEvent locationInWindow] | |
148 fromView:[[self window] contentView]]; | |
149 if (NSPointInRect(viewLocal, [self bounds])) { | |
150 [self performClick:self]; | |
151 } | 40 } |
152 } | 41 } |
153 | 42 |
154 - (void)secondaryMouseUpAction:(BOOL)wasInside { | |
155 // Override if you want to do any extra work on mouseUp, after a mouseDown | |
156 // action has already fired. | |
157 } | |
158 | |
159 - (void)performMouseDownAction:(NSEvent*)theEvent { | |
160 int eventMask = NSLeftMouseUpMask; | |
161 | |
162 [[self target] performSelector:[self action] withObject:self]; | |
163 actionHasFired_ = YES; | |
164 | |
165 while (1) { | |
166 theEvent = [[self window] nextEventMatchingMask:eventMask]; | |
167 if (!theEvent) | |
168 continue; | |
169 NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] | |
170 fromView:nil]; | |
171 BOOL isInside = [self mouse:mouseLoc inRect:[self bounds]]; | |
172 [self highlight:isInside]; | |
173 | |
174 switch ([theEvent type]) { | |
175 case NSLeftMouseUp: | |
176 durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_; | |
177 [self secondaryMouseUpAction:isInside]; | |
178 break; | |
179 default: | |
180 /* Ignore any other kind of event. */ | |
181 break; | |
182 } | |
183 } | |
184 | |
185 [self highlight:NO]; | |
186 } | |
187 | |
188 // Mimic "begin a click" operation visually. Do NOT follow through | |
189 // with normal button event handling. | |
190 - (void)mouseDown:(NSEvent*)theEvent { | |
191 [[NSCursor arrowCursor] set]; | |
192 | |
193 whenMouseDown_ = [theEvent timestamp]; | |
194 actionHasFired_ = NO; | |
195 | |
196 if (draggable_) { | |
197 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout]; | |
198 if ([self dragShouldBeginFromMouseDown:theEvent | |
199 withExpiration:date]) { | |
200 [self beginDrag:theEvent]; | |
201 [self endDrag]; | |
202 } else { | |
203 if (actsOnMouseDown_) { | |
204 [self performMouseDownAction:theEvent]; | |
205 } else { | |
206 [super mouseDown:theEvent]; | |
207 } | |
208 | |
209 } | |
210 } else { | |
211 if (actsOnMouseDown_) { | |
212 [self performMouseDownAction:theEvent]; | |
213 } else { | |
214 [super mouseDown:theEvent]; | |
215 } | |
216 } | |
217 } | |
218 | |
219 - (void)beginDrag:(NSEvent*)dragEvent { | 43 - (void)beginDrag:(NSEvent*)dragEvent { |
220 // Must be overridden by subclasses. | 44 // Must be overridden by subclasses. |
221 NOTREACHED(); | 45 NOTREACHED(); |
222 } | 46 } |
223 | 47 |
224 - (void)endDrag { | |
225 [self highlight:NO]; | |
226 } | |
227 | |
228 @end // @interface DraggableButton | 48 @end // @interface DraggableButton |
OLD | NEW |