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 |