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

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: self review + related fix for r283070 Created 6 years, 5 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 #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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698