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 #include "ui/events/event_processor.h" |
| 10 #include "ui/events/event_target_iterator.h" |
| 11 #import "ui/events/test/cocoa_test_event_utils.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 namespace { |
| 45 |
| 46 NSPoint ConvertRootPointToTarget(NSWindow* target, |
| 47 const gfx::Point& point_in_root) { |
| 48 // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can |
| 49 // reposition the window on screen and make things flaky. Initially, just |
| 50 // assume that the contentRect of |target| is at the top-left corner of the |
| 51 // screen. |
| 52 NSRect content_rect = [target contentRectForFrameRect:[target frame]]; |
| 53 return NSMakePoint(point_in_root.x(), |
| 54 NSHeight(content_rect) - point_in_root.y()); |
| 55 } |
| 56 |
| 57 // Inverse of ui::EventFlagsFromModifiers(). |
| 58 NSUInteger EventFlagsToModifiers(int flags) { |
| 59 NSUInteger modifiers = 0; |
| 60 modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0; |
| 61 modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0; |
| 62 modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0; |
| 63 modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0; |
| 64 modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0; |
| 65 // ui::EF_*_MOUSE_BUTTON not handled here. |
| 66 // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped. |
| 67 return modifiers; |
| 68 } |
| 69 |
| 70 // Picks the corresponding mouse event type for the buttons set in |flags|. |
| 71 NSEventType PickMouseEventType(int flags, |
| 72 NSEventType left, |
| 73 NSEventType right, |
| 74 NSEventType other) { |
| 75 if (flags & ui::EF_LEFT_MOUSE_BUTTON) |
| 76 return left; |
| 77 if (flags & ui::EF_RIGHT_MOUSE_BUTTON) |
| 78 return right; |
| 79 return other; |
| 80 } |
| 81 |
| 82 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set |
| 83 // using the inverse of ui::EventFlagsFromNSEventWithModifiers(). |
| 84 NSEventType EventTypeToNative(ui::EventType ui_event_type, |
| 85 int flags, |
| 86 NSUInteger* modifiers) { |
| 87 if (modifiers) |
| 88 *modifiers = EventFlagsToModifiers(flags); |
| 89 switch (ui_event_type) { |
| 90 case ui::ET_UNKNOWN: |
| 91 return 0; |
| 92 case ui::ET_KEY_PRESSED: |
| 93 return NSKeyDown; |
| 94 case ui::ET_KEY_RELEASED: |
| 95 return NSKeyUp; |
| 96 case ui::ET_MOUSE_PRESSED: |
| 97 return PickMouseEventType(flags, |
| 98 NSLeftMouseDown, |
| 99 NSRightMouseDown, |
| 100 NSOtherMouseDown); |
| 101 case ui::ET_MOUSE_RELEASED: |
| 102 return PickMouseEventType(flags, |
| 103 NSLeftMouseUp, |
| 104 NSRightMouseUp, |
| 105 NSOtherMouseUp); |
| 106 case ui::ET_MOUSE_DRAGGED: |
| 107 return PickMouseEventType(flags, |
| 108 NSLeftMouseDragged, |
| 109 NSRightMouseDragged, |
| 110 NSOtherMouseDragged); |
| 111 case ui::ET_MOUSE_MOVED: |
| 112 return NSMouseMoved; |
| 113 case ui::ET_MOUSEWHEEL: |
| 114 return NSScrollWheel; |
| 115 case ui::ET_MOUSE_ENTERED: |
| 116 return NSMouseEntered; |
| 117 case ui::ET_MOUSE_EXITED: |
| 118 return NSMouseExited; |
| 119 case ui::ET_SCROLL_FLING_START: |
| 120 return NSEventTypeSwipe; |
| 121 default: |
| 122 NOTREACHED(); |
| 123 return 0; |
| 124 } |
| 125 } |
| 126 |
| 127 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:]. |
| 128 // sendEvent is a black box which (among other things) will try to peek at the |
| 129 // event queue and can block indefinitely. |
| 130 void EmulateSendEvent(NSWindow* window, NSEvent* event) { |
| 131 NSResponder* responder = [window firstResponder]; |
| 132 switch ([event type]) { |
| 133 case NSKeyDown: |
| 134 [responder keyDown:event]; |
| 135 return; |
| 136 case NSKeyUp: |
| 137 [responder keyUp:event]; |
| 138 return; |
| 139 } |
| 140 |
| 141 // For mouse events, NSWindow will use -[NSView hitTest:] for the initial |
| 142 // mouseDown, and then keep track of the NSView returned. The toolkit-views |
| 143 // RootView does this too. So, for tests, assume tracking will be done there, |
| 144 // and the NSWindow's contentView is wrapping a views::internal::RootView. |
| 145 responder = [window contentView]; |
| 146 switch ([event type]) { |
| 147 case NSLeftMouseDown: |
| 148 [responder mouseDown:event]; |
| 149 break; |
| 150 case NSRightMouseDown: |
| 151 [responder rightMouseDown:event]; |
| 152 break; |
| 153 case NSOtherMouseDown: |
| 154 [responder otherMouseDown:event]; |
| 155 break; |
| 156 case NSLeftMouseUp: |
| 157 [responder mouseUp:event]; |
| 158 break; |
| 159 case NSRightMouseUp: |
| 160 [responder rightMouseUp:event]; |
| 161 break; |
| 162 case NSOtherMouseUp: |
| 163 [responder otherMouseUp:event]; |
| 164 break; |
| 165 case NSLeftMouseDragged: |
| 166 [responder mouseDragged:event]; |
| 167 break; |
| 168 case NSRightMouseDragged: |
| 169 [responder rightMouseDragged:event]; |
| 170 break; |
| 171 case NSOtherMouseDragged: |
| 172 [responder otherMouseDragged:event]; |
| 173 break; |
| 174 case NSMouseMoved: |
| 175 // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that |
| 176 // NSTrackingAreas have been appropriately installed on |responder|. |
| 177 [responder mouseMoved:event]; |
| 178 break; |
| 179 case NSScrollWheel: |
| 180 [responder scrollWheel:event]; |
| 181 break; |
| 182 case NSMouseEntered: |
| 183 case NSMouseExited: |
| 184 // With the assumptions in NSMouseMoved, it doesn't make sense for the |
| 185 // generator to handle entered/exited separately. It's the responsibility |
| 186 // of views::internal::RootView to convert the moved events into entered |
| 187 // and exited events for the individual views. |
| 188 NOTREACHED(); |
| 189 break; |
| 190 case NSEventTypeSwipe: |
| 191 // NSEventTypeSwipe events can't be generated using public interfaces on |
| 192 // NSEvent, so this will need to be handled at a higher level. |
| 193 NOTREACHED(); |
| 194 break; |
| 195 default: |
| 196 NOTREACHED(); |
| 197 } |
| 198 } |
| 199 |
| 200 void DispatchMouseEventInWindow(NSWindow* window, |
| 201 ui::EventType event_type, |
| 202 const gfx::Point& point_in_root, |
| 203 int flags) { |
| 204 NSUInteger click_count = 0; |
| 205 if (event_type == ui::ET_MOUSE_PRESSED || |
| 206 event_type == ui::ET_MOUSE_RELEASED) { |
| 207 if (flags & ui::EF_IS_TRIPLE_CLICK) |
| 208 click_count = 3; |
| 209 else if (flags & ui::EF_IS_DOUBLE_CLICK) |
| 210 click_count = 2; |
| 211 else |
| 212 click_count = 1; |
| 213 } |
| 214 NSPoint point = ConvertRootPointToTarget(window, point_in_root); |
| 215 NSUInteger modifiers = 0; |
| 216 NSEventType type = EventTypeToNative(event_type, flags, &modifiers); |
| 217 NSEvent* event = [NSEvent mouseEventWithType:type |
| 218 location:point |
| 219 modifierFlags:modifiers |
| 220 timestamp:0 |
| 221 windowNumber:[window windowNumber] |
| 222 context:nil |
| 223 eventNumber:0 |
| 224 clickCount:click_count |
| 225 pressure:1.0]; |
| 226 |
| 227 // Typically events go through NSApplication. For tests, dispatch the event |
| 228 // directly to make things more predicatble. |
| 229 EmulateSendEvent(window, event); |
| 230 } |
| 231 |
| 232 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything |
| 233 // defined inline is just a stub. Interesting overrides are defined below the |
| 234 // class. |
| 235 class EventGeneratorDelegateMac : public ui::EventTarget, |
| 236 public ui::EventSource, |
| 237 public ui::EventProcessor, |
| 238 public ui::EventTargeter, |
| 239 public ui::test::EventGeneratorDelegate { |
| 240 public: |
| 241 EventGeneratorDelegateMac(NSWindow* window); |
| 242 virtual ~EventGeneratorDelegateMac() {} |
| 243 |
| 244 // Overridden from ui::EventTarget: |
| 245 virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE { return true; } |
| 246 virtual ui::EventTarget* GetParentTarget() OVERRIDE { return NULL; } |
| 247 virtual scoped_ptr<ui::EventTargetIterator> GetChildIterator() const OVERRIDE; |
| 248 virtual ui::EventTargeter* GetEventTargeter() OVERRIDE { |
| 249 return this; |
| 250 } |
| 251 |
| 252 // Overridden from ui::EventHandler (via ui::EventTarget): |
| 253 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; |
| 254 |
| 255 // Overridden from ui::EventSource: |
| 256 virtual ui::EventProcessor* GetEventProcessor() OVERRIDE { return this; } |
| 257 |
| 258 // Overridden from ui::EventProcessor: |
| 259 virtual ui::EventTarget* GetRootTarget() OVERRIDE { return this; } |
| 260 |
| 261 // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor): |
| 262 virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE { |
| 263 return true; |
| 264 } |
| 265 |
| 266 // Overridden from ui::test::EventGeneratorDelegate: |
| 267 virtual ui::EventTarget* GetTargetAt(const gfx::Point& location) OVERRIDE { |
| 268 return this; |
| 269 } |
| 270 virtual ui::EventSource* GetEventSource(ui::EventTarget* target) OVERRIDE { |
| 271 return this; |
| 272 } |
| 273 virtual gfx::Point CenterOfTarget( |
| 274 const ui::EventTarget* target) const OVERRIDE { |
| 275 return gfx::Point(); |
| 276 } |
| 277 virtual void ConvertPointFromTarget(const ui::EventTarget* target, |
| 278 gfx::Point* point) const OVERRIDE {} |
| 279 virtual void ConvertPointToTarget(const ui::EventTarget* target, |
| 280 gfx::Point* point) const OVERRIDE {} |
| 281 virtual void ConvertPointFromHost(const ui::EventTarget* hosted_target, |
| 282 gfx::Point* point) const OVERRIDE {} |
| 283 |
| 284 private: |
| 285 NSWindow* window_; |
| 286 ScopedClassSwizzler swizzle_pressed_; |
| 287 |
| 288 DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac); |
| 289 }; |
| 290 |
| 291 EventGeneratorDelegateMac::EventGeneratorDelegateMac(NSWindow* window) |
| 292 : window_(window), |
| 293 swizzle_pressed_([NSEvent class], |
| 294 [NSEventDonor class], |
| 295 @selector(pressedMouseButtons)) { |
| 296 } |
| 297 |
| 298 scoped_ptr<ui::EventTargetIterator> |
| 299 EventGeneratorDelegateMac::GetChildIterator() const { |
| 300 // Return NULL to dispatch all events to the result of GetRootTarget(). |
| 301 return scoped_ptr<ui::EventTargetIterator>(); |
| 302 } |
| 303 |
| 304 void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) { |
| 305 DispatchMouseEventInWindow(window_, |
| 306 event->type(), |
| 307 event->location(), |
| 308 event->changed_button_flags()); |
| 309 } |
| 310 |
| 311 } // namespace |
| 312 |
| 313 WidgetEventGenerator::WidgetEventGenerator(Widget* widget) |
| 314 : WidgetEventGeneratorMac(widget->GetNativeWindow()) { |
| 315 } |
| 316 |
| 317 WidgetEventGenerator::WidgetEventGenerator(gfx::NativeView context) |
| 318 : WidgetEventGeneratorMac([context window]) { |
| 319 } |
| 320 |
| 321 WidgetEventGenerator::WidgetEventGenerator( |
| 322 gfx::NativeView context, |
| 323 Widget* window_for_initial_location) |
| 324 : WidgetEventGeneratorMac([context window]) { |
| 325 set_current_location( |
| 326 window_for_initial_location->GetRestoredBounds().CenterPoint()); |
| 327 } |
| 328 |
| 329 WidgetEventGenerator::~WidgetEventGenerator() { |
| 330 } |
| 331 |
| 332 WidgetEventGeneratorMac::WidgetEventGeneratorMac(gfx::NativeWindow ns_window) |
| 333 : EventGenerator(new EventGeneratorDelegateMac(ns_window), gfx::Point()) { |
| 334 DCHECK(!g_active_generator); |
| 335 g_active_generator = this; |
| 336 } |
| 337 |
| 338 WidgetEventGeneratorMac::~WidgetEventGeneratorMac() { |
| 339 DCHECK_EQ(this, g_active_generator); |
| 340 g_active_generator = NULL; |
| 341 } |
| 342 |
| 343 } // namespace views |
| 344 } // namespace test |
OLD | NEW |