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