Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 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 #include "ui/views/test/widget_event_generator.h" | |
| 6 | |
| 7 #import <Cocoa/Cocoa.h> | |
| 8 | |
| 9 #import "base/test/mock_chrome_application_mac.h" | |
| 10 #import "ui/events/test/cocoa_test_event_utils.h" | |
| 11 #include "ui/gfx/vector2d_conversions.h" | |
| 12 #include "ui/views/widget/widget.h" | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // Singleton to provide state for swizzled Objective C methods. | |
| 17 views::test::WidgetEventGeneratorMac* g_active_generator = NULL; | |
| 18 | |
| 19 } // namespace | |
| 20 | |
| 21 @interface NSEventDonor : NSObject | |
| 22 @end | |
| 23 | |
| 24 @implementation NSEventDonor | |
| 25 | |
| 26 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the | |
| 27 // active generator. | |
| 28 + (NSUInteger)pressedMouseButtons { | |
| 29 int flags = g_active_generator->flags(); | |
| 30 NSUInteger bitmask = 0; | |
| 31 if (flags & ui::EF_LEFT_MOUSE_BUTTON) | |
| 32 bitmask |= 1; | |
| 33 if (flags & ui::EF_RIGHT_MOUSE_BUTTON) | |
| 34 bitmask |= 1 << 1; | |
| 35 if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) | |
| 36 bitmask |= 1 << 2; | |
| 37 return bitmask; | |
| 38 } | |
| 39 | |
| 40 @end | |
| 41 | |
| 42 namespace views { | |
| 43 namespace test { | |
| 44 | |
| 45 class WidgetEventGeneratorMac::Impl { | |
| 46 public: | |
| 47 Impl(); | |
| 48 ~Impl() {} | |
| 49 | |
| 50 private: | |
| 51 ScopedClassSwizzler swizzle_pressed_; | |
| 52 | |
| 53 DISALLOW_COPY_AND_ASSIGN(Impl); | |
| 54 }; | |
| 55 | |
| 56 WidgetEventGeneratorMac::Impl::Impl() | |
| 57 : swizzle_pressed_([NSEvent class], | |
| 58 [NSEventDonor class], | |
| 59 @selector(pressedMouseButtons)) { | |
| 60 } | |
| 61 | |
| 62 namespace { | |
| 63 | |
| 64 NSPoint ConvertScreenPointToTarget(NSWindow* target, | |
| 65 const gfx::Point& point_in_screen) { | |
| 66 // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can | |
| 67 // reposition the window on screen and make things flaky. Initially, just | |
| 68 // assume that the contentRect of |target| is at the top-left corner of the | |
| 69 // screen. | |
| 70 NSRect content_rect = [target contentRectForFrameRect:[target frame]]; | |
| 71 return NSMakePoint(point_in_screen.x(), | |
| 72 NSHeight(content_rect) - point_in_screen.y()); | |
| 73 } | |
| 74 | |
| 75 // Inverse of ui::EventFlagsFromModifiers(). | |
| 76 NSUInteger EventFlagsToModifiers(int flags) { | |
| 77 NSUInteger modifiers = 0; | |
| 78 modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0; | |
| 79 modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0; | |
| 80 modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0; | |
| 81 modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0; | |
| 82 modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0; | |
| 83 // ui::EF_*_MOUSE_BUTTON not handled here. | |
| 84 // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped. | |
| 85 return modifiers; | |
| 86 } | |
| 87 | |
| 88 // Picks the corresponding mouse event type for the buttons set in |flags|. | |
| 89 NSEventType PickMouseEventType(int flags, | |
| 90 NSEventType left, | |
| 91 NSEventType right, | |
| 92 NSEventType other) { | |
| 93 if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) | |
| 94 return other; | |
| 95 if (flags & ui::EF_RIGHT_MOUSE_BUTTON) | |
| 96 return right; | |
| 97 return left; | |
|
Robert Sesek
2014/06/18 16:43:02
I'd test for left and right and then return other.
tapted
2014/06/19 11:23:21
Done.
| |
| 98 } | |
| 99 | |
| 100 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set | |
| 101 // using the inverse of ui::EventFlagsFromNSEventWithModifiers(). | |
| 102 NSEventType EventTypeToNative(ui::EventType ui_event_type, | |
| 103 int flags, | |
| 104 NSUInteger* modifiers) { | |
| 105 if (modifiers) | |
| 106 *modifiers = EventFlagsToModifiers(flags); | |
| 107 switch (ui_event_type) { | |
| 108 case ui::ET_UNKNOWN: | |
| 109 return 0; | |
| 110 case ui::ET_KEY_PRESSED: | |
| 111 return NSKeyDown; | |
| 112 case ui::ET_KEY_RELEASED: | |
| 113 return NSKeyUp; | |
| 114 case ui::ET_MOUSE_PRESSED: | |
| 115 return PickMouseEventType(flags, | |
| 116 NSLeftMouseDown, | |
| 117 NSRightMouseDown, | |
| 118 NSOtherMouseDown); | |
| 119 case ui::ET_MOUSE_RELEASED: | |
| 120 return PickMouseEventType(flags, | |
| 121 NSLeftMouseUp, | |
| 122 NSRightMouseUp, | |
| 123 NSOtherMouseUp); | |
| 124 case ui::ET_MOUSE_DRAGGED: | |
| 125 return PickMouseEventType(flags, | |
| 126 NSLeftMouseDragged, | |
| 127 NSRightMouseDragged, | |
| 128 NSOtherMouseDragged); | |
| 129 case ui::ET_MOUSE_MOVED: | |
| 130 return NSMouseMoved; | |
| 131 case ui::ET_MOUSEWHEEL: | |
| 132 return NSScrollWheel; | |
| 133 case ui::ET_MOUSE_ENTERED: | |
| 134 return NSMouseEntered; | |
| 135 case ui::ET_MOUSE_EXITED: | |
| 136 return NSMouseExited; | |
| 137 case ui::ET_SCROLL_FLING_START: | |
| 138 return NSEventTypeSwipe; | |
| 139 default: | |
| 140 NOTREACHED(); | |
| 141 return 0; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:]. | |
| 146 // sendEvent is a black box which (among other things) will try to peek at the | |
| 147 // event queue and can block indefinitely. | |
| 148 void EmulateSendEvent(NSWindow* window, NSEvent* event) { | |
| 149 NSResponder* responder = [window firstResponder]; | |
| 150 switch ([event type]) { | |
| 151 case NSKeyDown: | |
| 152 [responder keyDown:event]; | |
| 153 return; | |
| 154 case NSKeyUp: | |
| 155 [responder keyUp:event]; | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 // For mouse events, NSWindow will use -[NSView hitTest:] for the initial | |
| 160 // mouseDown, and then keep track of the NSView returned. The toolkit-views | |
| 161 // RootView does this too. So, for tests, assume tracking will be done there, | |
| 162 // and the NSWindow's contentView is wrapping a views::internal::RootView. | |
| 163 responder = [window contentView]; | |
| 164 switch ([event type]) { | |
| 165 case NSLeftMouseDown: | |
| 166 [responder mouseDown:event]; | |
| 167 break; | |
| 168 case NSRightMouseDown: | |
| 169 [responder rightMouseDown:event]; | |
| 170 break; | |
| 171 case NSOtherMouseDown: | |
| 172 [responder otherMouseDown:event]; | |
| 173 break; | |
| 174 case NSLeftMouseUp: | |
| 175 [responder mouseUp:event]; | |
| 176 break; | |
| 177 case NSRightMouseUp: | |
| 178 [responder rightMouseUp:event]; | |
| 179 break; | |
| 180 case NSOtherMouseUp: | |
| 181 [responder otherMouseUp:event]; | |
| 182 break; | |
| 183 case NSLeftMouseDragged: | |
| 184 [responder mouseDragged:event]; | |
| 185 break; | |
| 186 case NSRightMouseDragged: | |
| 187 [responder rightMouseDragged:event]; | |
| 188 break; | |
| 189 case NSOtherMouseDragged: | |
| 190 [responder otherMouseDragged:event]; | |
| 191 break; | |
| 192 case NSMouseMoved: | |
| 193 // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that | |
| 194 // NSTrackingAreas have been appropriately installed on |responder|. | |
| 195 [responder mouseMoved:event]; | |
| 196 break; | |
| 197 case NSScrollWheel: | |
| 198 [responder scrollWheel:event]; | |
| 199 break; | |
| 200 case NSMouseEntered: | |
| 201 case NSMouseExited: | |
| 202 // With the assumptions in NSMouseMoved, it doesn't make sense for the | |
| 203 // generator to handle entered/exited separately. It's the responsibility | |
| 204 // of views::internal::RootView to convert the moved events into entered | |
| 205 // and exited events for the individual views. | |
| 206 NOTREACHED(); | |
| 207 break; | |
| 208 case NSEventTypeSwipe: | |
| 209 // NSEventTypeSwipe events can't be generated using public interfaces on | |
| 210 // NSEvent, so this will need to be handled at a higher level. | |
| 211 NOTREACHED(); | |
| 212 break; | |
| 213 default: | |
| 214 NOTREACHED(); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 void DispatchMouseEventInWindow(NSWindow* window, | |
| 219 ui::EventType event_type, | |
| 220 const gfx::Point& point_in_screen, | |
| 221 int flags) { | |
| 222 NSUInteger click_count = 0; | |
| 223 if (event_type == ui::ET_MOUSE_PRESSED || | |
| 224 event_type == ui::ET_MOUSE_RELEASED) { | |
| 225 if (flags & ui::EF_IS_TRIPLE_CLICK) | |
| 226 click_count = 3; | |
| 227 else if (flags & ui::EF_IS_DOUBLE_CLICK) | |
| 228 click_count = 2; | |
| 229 else | |
| 230 click_count = 1; | |
| 231 } | |
| 232 NSPoint point = ConvertScreenPointToTarget(window, point_in_screen); | |
| 233 NSUInteger modifiers = 0; | |
| 234 NSEventType type = EventTypeToNative(event_type, flags, &modifiers); | |
| 235 NSEvent* event = [NSEvent mouseEventWithType:type | |
| 236 location:point | |
| 237 modifierFlags:modifiers | |
| 238 timestamp:0 | |
| 239 windowNumber:[window windowNumber] | |
| 240 context:nil | |
| 241 eventNumber:0 | |
| 242 clickCount:click_count | |
| 243 pressure:1.0]; | |
| 244 | |
| 245 // Typically events go through NSApplication. For tests, dispatch the event | |
| 246 // directly to make things more predicatble. | |
| 247 EmulateSendEvent(window, event); | |
| 248 } | |
| 249 | |
| 250 } // namespace | |
| 251 | |
| 252 WidgetEventGenerator::WidgetEventGenerator(Widget* widget) | |
| 253 : WidgetEventGeneratorMac(widget->GetNativeWindow()) { | |
| 254 } | |
| 255 | |
| 256 WidgetEventGenerator::WidgetEventGenerator(gfx::NativeView context) | |
| 257 : WidgetEventGeneratorMac([context window]) { | |
| 258 } | |
| 259 | |
| 260 WidgetEventGenerator::WidgetEventGenerator( | |
| 261 gfx::NativeView context, | |
| 262 Widget* window_for_initial_location) | |
| 263 : WidgetEventGeneratorMac([context window]) { | |
| 264 set_current_location( | |
| 265 window_for_initial_location->GetRestoredBounds().CenterPoint()); | |
| 266 } | |
| 267 | |
| 268 WidgetEventGenerator::~WidgetEventGenerator() { | |
| 269 } | |
| 270 | |
| 271 WidgetEventGeneratorMac::WidgetEventGeneratorMac(gfx::NativeWindow ns_window) | |
| 272 : impl_(new Impl), ns_window_(ns_window), flags_(0) { | |
| 273 DCHECK(!g_active_generator); | |
| 274 g_active_generator = this; | |
| 275 } | |
| 276 | |
| 277 WidgetEventGeneratorMac::~WidgetEventGeneratorMac() { | |
| 278 DCHECK_EQ(this, g_active_generator); | |
| 279 g_active_generator = NULL; | |
| 280 } | |
| 281 | |
| 282 void WidgetEventGeneratorMac::PressLeftButton() { | |
| 283 PressButton(ui::EF_LEFT_MOUSE_BUTTON); | |
| 284 } | |
| 285 | |
| 286 void WidgetEventGeneratorMac::ReleaseLeftButton() { | |
| 287 ReleaseButton(ui::EF_LEFT_MOUSE_BUTTON); | |
| 288 } | |
| 289 | |
| 290 void WidgetEventGeneratorMac::ClickLeftButton() { | |
| 291 PressLeftButton(); | |
| 292 ReleaseLeftButton(); | |
| 293 } | |
| 294 | |
| 295 void WidgetEventGeneratorMac::PressRightButton() { | |
| 296 PressButton(ui::EF_RIGHT_MOUSE_BUTTON); | |
| 297 } | |
| 298 | |
| 299 void WidgetEventGeneratorMac::ReleaseRightButton() { | |
| 300 ReleaseButton(ui::EF_RIGHT_MOUSE_BUTTON); | |
| 301 } | |
| 302 | |
| 303 void WidgetEventGeneratorMac::GestureTapAt(const gfx::Point& point) { | |
| 304 NOTIMPLEMENTED(); | |
| 305 } | |
| 306 | |
| 307 void WidgetEventGeneratorMac::GestureScrollSequence( | |
| 308 const gfx::Point& start, | |
| 309 const gfx::Point& end, | |
| 310 const base::TimeDelta& duration, | |
| 311 int steps) { | |
| 312 NOTIMPLEMENTED(); | |
| 313 } | |
| 314 | |
| 315 void WidgetEventGeneratorMac::GestureMultiFingerScroll( | |
| 316 int count, | |
| 317 const gfx::Point start[], | |
| 318 int event_separation_time_ms, | |
| 319 int steps, | |
| 320 int move_x, | |
| 321 int move_y) { | |
| 322 NOTIMPLEMENTED(); | |
| 323 } | |
| 324 | |
| 325 void WidgetEventGeneratorMac::MoveMouseTo(const gfx::Point& point_in_screen, | |
| 326 int count) { | |
| 327 DCHECK_GT(count, 0); | |
| 328 const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ? | |
| 329 ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED; | |
| 330 | |
| 331 gfx::Vector2dF diff(point_in_screen - current_location_); | |
| 332 for (float i = 1; i <= count; i++) { | |
| 333 gfx::Vector2dF step(diff); | |
| 334 step.Scale(i / count); | |
| 335 gfx::Point move_point = current_location_ + gfx::ToRoundedVector2d(step); | |
| 336 // TOOD(tapted): Handle moving between windows. | |
| 337 DispatchMouseEventInWindow(ns_window_, event_type, move_point, flags_); | |
| 338 } | |
| 339 current_location_ = point_in_screen; | |
| 340 } | |
| 341 | |
| 342 void WidgetEventGeneratorMac::DragMouseTo(const gfx::Point& point) { | |
| 343 PressLeftButton(); | |
| 344 MoveMouseTo(point); | |
| 345 ReleaseLeftButton(); | |
| 346 } | |
| 347 | |
| 348 void WidgetEventGeneratorMac::PressButton(int flag) { | |
| 349 flags_ |= flag; | |
| 350 DispatchMouseEventInWindow(ns_window_, | |
| 351 ui::ET_MOUSE_PRESSED, | |
| 352 current_location_, | |
| 353 flag); | |
| 354 } | |
| 355 | |
| 356 void WidgetEventGeneratorMac::ReleaseButton(int flag) { | |
| 357 DCHECK(flags_ & flag); | |
| 358 flags_ &= ~flag; | |
| 359 DispatchMouseEventInWindow(ns_window_, | |
| 360 ui::ET_MOUSE_RELEASED, | |
| 361 current_location_, | |
| 362 flag); | |
| 363 } | |
| 364 | |
| 365 } // namespace views | |
| 366 } // namespace test | |
| OLD | NEW |