Workaround for xkbcommon dead keys.
[chromium-blink-merge.git] / ui / views / test / event_generator_delegate_mac.mm
blobdb9b627313cdc6d0cdba47af66c9fa0dfd295ef9
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.
5 #import <Cocoa/Cocoa.h>
7 #import "base/mac/scoped_objc_class_swizzler.h"
8 #include "base/memory/singleton.h"
9 #include "ui/events/event_processor.h"
10 #include "ui/events/event_target.h"
11 #include "ui/events/event_target_iterator.h"
12 #include "ui/events/event_targeter.h"
13 #include "ui/events/test/event_generator.h"
14 #include "ui/gfx/mac/coordinate_conversion.h"
16 namespace {
18 // Singleton to provide state for swizzled Objective C methods.
19 ui::test::EventGenerator* g_active_generator = NULL;
21 }  // namespace
23 @interface NSEventDonor : NSObject
24 @end
26 @implementation NSEventDonor
28 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the
29 // active generator.
30 + (NSUInteger)pressedMouseButtons {
31   if (!g_active_generator)
32     return [NSEventDonor pressedMouseButtons];  // Call original implementation.
34   int flags = g_active_generator->flags();
35   NSUInteger bitmask = 0;
36   if (flags & ui::EF_LEFT_MOUSE_BUTTON)
37     bitmask |= 1;
38   if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
39     bitmask |= 1 << 1;
40   if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
41     bitmask |= 1 << 2;
42   return bitmask;
45 @end
47 namespace {
49 NSPoint ConvertRootPointToTarget(NSWindow* target,
50                                  const gfx::Point& point_in_root) {
51   // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can
52   // reposition the window on screen and make things flaky. Initially, just
53   // assume that the contentRect of |target| is at the top-left corner of the
54   // screen.
55   NSRect content_rect = [target contentRectForFrameRect:[target frame]];
56   return NSMakePoint(point_in_root.x(),
57                      NSHeight(content_rect) - point_in_root.y());
60 // Inverse of ui::EventFlagsFromModifiers().
61 NSUInteger EventFlagsToModifiers(int flags) {
62   NSUInteger modifiers = 0;
63   modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0;
64   modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0;
65   modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0;
66   modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0;
67   modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0;
68   // ui::EF_*_MOUSE_BUTTON not handled here.
69   // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped.
70   return modifiers;
73 // Picks the corresponding mouse event type for the buttons set in |flags|.
74 NSEventType PickMouseEventType(int flags,
75                                NSEventType left,
76                                NSEventType right,
77                                NSEventType other) {
78   if (flags & ui::EF_LEFT_MOUSE_BUTTON)
79     return left;
80   if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
81     return right;
82   return other;
85 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set
86 // using the inverse of ui::EventFlagsFromNSEventWithModifiers().
87 NSEventType EventTypeToNative(ui::EventType ui_event_type,
88                               int flags,
89                               NSUInteger* modifiers) {
90   if (modifiers)
91     *modifiers = EventFlagsToModifiers(flags);
92   switch (ui_event_type) {
93     case ui::ET_UNKNOWN:
94       return 0;
95     case ui::ET_KEY_PRESSED:
96       return NSKeyDown;
97     case ui::ET_KEY_RELEASED:
98       return NSKeyUp;
99     case ui::ET_MOUSE_PRESSED:
100       return PickMouseEventType(flags,
101                                 NSLeftMouseDown,
102                                 NSRightMouseDown,
103                                 NSOtherMouseDown);
104     case ui::ET_MOUSE_RELEASED:
105       return PickMouseEventType(flags,
106                                 NSLeftMouseUp,
107                                 NSRightMouseUp,
108                                 NSOtherMouseUp);
109     case ui::ET_MOUSE_DRAGGED:
110       return PickMouseEventType(flags,
111                                 NSLeftMouseDragged,
112                                 NSRightMouseDragged,
113                                 NSOtherMouseDragged);
114     case ui::ET_MOUSE_MOVED:
115       return NSMouseMoved;
116     case ui::ET_MOUSEWHEEL:
117       return NSScrollWheel;
118     case ui::ET_MOUSE_ENTERED:
119       return NSMouseEntered;
120     case ui::ET_MOUSE_EXITED:
121       return NSMouseExited;
122     case ui::ET_SCROLL_FLING_START:
123       return NSEventTypeSwipe;
124     default:
125       NOTREACHED();
126       return 0;
127   }
130 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:].
131 // sendEvent is a black box which (among other things) will try to peek at the
132 // event queue and can block indefinitely.
133 void EmulateSendEvent(NSWindow* window, NSEvent* event) {
134   NSResponder* responder = [window firstResponder];
135   switch ([event type]) {
136     case NSKeyDown:
137       [responder keyDown:event];
138       return;
139     case NSKeyUp:
140       [responder keyUp:event];
141       return;
142   }
144   // For mouse events, NSWindow will use -[NSView hitTest:] for the initial
145   // mouseDown, and then keep track of the NSView returned. The toolkit-views
146   // RootView does this too. So, for tests, assume tracking will be done there,
147   // and the NSWindow's contentView is wrapping a views::internal::RootView.
148   responder = [window contentView];
149   switch ([event type]) {
150     case NSLeftMouseDown:
151       [responder mouseDown:event];
152       break;
153     case NSRightMouseDown:
154       [responder rightMouseDown:event];
155       break;
156     case NSOtherMouseDown:
157       [responder otherMouseDown:event];
158       break;
159     case NSLeftMouseUp:
160       [responder mouseUp:event];
161       break;
162     case NSRightMouseUp:
163       [responder rightMouseUp:event];
164       break;
165     case NSOtherMouseUp:
166       [responder otherMouseUp:event];
167       break;
168     case NSLeftMouseDragged:
169       [responder mouseDragged:event];
170       break;
171     case NSRightMouseDragged:
172       [responder rightMouseDragged:event];
173       break;
174     case NSOtherMouseDragged:
175       [responder otherMouseDragged:event];
176       break;
177     case NSMouseMoved:
178       // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that
179       // NSTrackingAreas have been appropriately installed on |responder|.
180       [responder mouseMoved:event];
181       break;
182     case NSScrollWheel:
183       [responder scrollWheel:event];
184       break;
185     case NSMouseEntered:
186     case NSMouseExited:
187       // With the assumptions in NSMouseMoved, it doesn't make sense for the
188       // generator to handle entered/exited separately. It's the responsibility
189       // of views::internal::RootView to convert the moved events into entered
190       // and exited events for the individual views.
191       NOTREACHED();
192       break;
193     case NSEventTypeSwipe:
194       // NSEventTypeSwipe events can't be generated using public interfaces on
195       // NSEvent, so this will need to be handled at a higher level.
196       NOTREACHED();
197       break;
198     default:
199       NOTREACHED();
200   }
203 NSEvent* CreateMouseEventInWindow(NSWindow* window,
204                                   ui::EventType event_type,
205                                   const gfx::Point& point_in_root,
206                                   int flags) {
207   NSUInteger click_count = 0;
208   if (event_type == ui::ET_MOUSE_PRESSED ||
209       event_type == ui::ET_MOUSE_RELEASED) {
210     if (flags & ui::EF_IS_TRIPLE_CLICK)
211       click_count = 3;
212     else if (flags & ui::EF_IS_DOUBLE_CLICK)
213       click_count = 2;
214     else
215       click_count = 1;
216   }
217   NSPoint point = ConvertRootPointToTarget(window, point_in_root);
218   NSUInteger modifiers = 0;
219   NSEventType type = EventTypeToNative(event_type, flags, &modifiers);
220   return [NSEvent mouseEventWithType:type
221                             location:point
222                        modifierFlags:modifiers
223                            timestamp:0
224                         windowNumber:[window windowNumber]
225                              context:nil
226                          eventNumber:0
227                           clickCount:click_count
228                             pressure:1.0];
231 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything
232 // defined inline is just a stub. Interesting overrides are defined below the
233 // class.
234 class EventGeneratorDelegateMac : public ui::EventTarget,
235                                   public ui::EventSource,
236                                   public ui::EventProcessor,
237                                   public ui::EventTargeter,
238                                   public ui::test::EventGeneratorDelegate {
239  public:
240   static EventGeneratorDelegateMac* GetInstance() {
241     return Singleton<EventGeneratorDelegateMac>::get();
242   }
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   }
252   // Overridden from ui::EventHandler (via ui::EventTarget):
253   virtual void OnMouseEvent(ui::MouseEvent* event) override;
255   // Overridden from ui::EventSource:
256   virtual ui::EventProcessor* GetEventProcessor() override { return this; }
258   // Overridden from ui::EventProcessor:
259   virtual ui::EventTarget* GetRootTarget() override { return this; }
261   // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor):
262   virtual bool CanDispatchToTarget(EventTarget* target) override {
263     return true;
264   }
266   // Overridden from ui::test::EventGeneratorDelegate:
267   virtual void SetContext(ui::test::EventGenerator* owner,
268                           gfx::NativeWindow root_window,
269                           gfx::NativeWindow window) override;
270   virtual ui::EventTarget* GetTargetAt(const gfx::Point& location) override {
271     return this;
272   }
273   virtual ui::EventSource* GetEventSource(ui::EventTarget* target) override {
274     return this;
275   }
276   virtual gfx::Point CenterOfTarget(
277       const ui::EventTarget* target) const override;
278   virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const override;
280   virtual void ConvertPointFromTarget(const ui::EventTarget* target,
281                                       gfx::Point* point) const override {}
282   virtual void ConvertPointToTarget(const ui::EventTarget* target,
283                                     gfx::Point* point) const override {}
284   virtual void ConvertPointFromHost(const ui::EventTarget* hosted_target,
285                                     gfx::Point* point) const override {}
287  private:
288   friend struct DefaultSingletonTraits<EventGeneratorDelegateMac>;
290   EventGeneratorDelegateMac();
291   virtual ~EventGeneratorDelegateMac();
293   ui::test::EventGenerator* owner_;
294   NSWindow* window_;
295   scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_pressed_;
297   DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac);
300 EventGeneratorDelegateMac::EventGeneratorDelegateMac()
301     : owner_(NULL),
302       window_(NULL) {
303   DCHECK(!ui::test::EventGenerator::default_delegate);
304   ui::test::EventGenerator::default_delegate = this;
307 EventGeneratorDelegateMac::~EventGeneratorDelegateMac() {
308   DCHECK_EQ(this, ui::test::EventGenerator::default_delegate);
309   ui::test::EventGenerator::default_delegate = NULL;
312 scoped_ptr<ui::EventTargetIterator>
313 EventGeneratorDelegateMac::GetChildIterator() const {
314   // Return NULL to dispatch all events to the result of GetRootTarget().
315   return scoped_ptr<ui::EventTargetIterator>();
318 void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
319   // For mouse drag events, ensure the swizzled methods return the right flags.
320   base::AutoReset<ui::test::EventGenerator*> reset(&g_active_generator, owner_);
321   NSEvent* ns_event = CreateMouseEventInWindow(window_,
322                                                event->type(),
323                                                event->location(),
324                                                event->changed_button_flags());
325   if (owner_->targeting_application())
326     [NSApp sendEvent:ns_event];
327   else
328     EmulateSendEvent(window_, ns_event);
331 void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner,
332                                            gfx::NativeWindow root_window,
333                                            gfx::NativeWindow window) {
334   swizzle_pressed_.reset();
335   owner_ = owner;
336   window_ = window;
337   if (owner_) {
338     swizzle_pressed_.reset(new base::mac::ScopedObjCClassSwizzler(
339         [NSEvent class],
340         [NSEventDonor class],
341         @selector(pressedMouseButtons)));
342   }
345 gfx::Point EventGeneratorDelegateMac::CenterOfTarget(
346     const ui::EventTarget* target) const {
347   DCHECK_EQ(target, this);
348   return CenterOfWindow(window_);
351 gfx::Point EventGeneratorDelegateMac::CenterOfWindow(
352     gfx::NativeWindow window) const {
353   DCHECK_EQ(window, window_);
354   return gfx::ScreenRectFromNSRect([window frame]).CenterPoint();
357 }  // namespace
359 namespace views {
360 namespace test {
362 void InitializeMacEventGeneratorDelegate() {
363   EventGeneratorDelegateMac::GetInstance();
366 }  // namespace test
367 }  // namespace views