Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(467)

Side by Side Diff: ui/views/test/widget_event_generator_mac.mm

Issue 322893005: MacViews: Add WidgetEventGenerator to abstract platform-specific event generation for tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase on crrev/334573008 Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698