Index: ui/views/test/widget_event_generator_mac.mm |
diff --git a/ui/views/test/widget_event_generator_mac.mm b/ui/views/test/widget_event_generator_mac.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..16f74e0dc033974d3732739abb8b4e6d51ab789e |
--- /dev/null |
+++ b/ui/views/test/widget_event_generator_mac.mm |
@@ -0,0 +1,366 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ui/views/test/widget_event_generator.h" |
+ |
+#import <Cocoa/Cocoa.h> |
+ |
+#import "base/test/mock_chrome_application_mac.h" |
+#import "ui/events/test/cocoa_test_event_utils.h" |
+#include "ui/gfx/vector2d_conversions.h" |
+#include "ui/views/widget/widget.h" |
+ |
+namespace { |
+ |
+// Singleton to provide state for swizzled Objective C methods. |
+views::test::WidgetEventGeneratorMac* g_active_generator = NULL; |
+ |
+} // namespace |
+ |
+@interface NSEventDonor : NSObject |
+@end |
+ |
+@implementation NSEventDonor |
+ |
+// Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the |
+// active generator. |
++ (NSUInteger)pressedMouseButtons { |
+ int flags = g_active_generator->flags(); |
+ NSUInteger bitmask = 0; |
+ if (flags & ui::EF_LEFT_MOUSE_BUTTON) |
+ bitmask |= 1; |
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON) |
+ bitmask |= 1 << 1; |
+ if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) |
+ bitmask |= 1 << 2; |
+ return bitmask; |
+} |
+ |
+@end |
+ |
+namespace views { |
+namespace test { |
+ |
+class WidgetEventGeneratorMac::Impl { |
+ public: |
+ Impl(); |
+ ~Impl() {} |
+ |
+ private: |
+ ScopedClassSwizzler swizzle_pressed_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(Impl); |
+}; |
+ |
+WidgetEventGeneratorMac::Impl::Impl() |
+ : swizzle_pressed_([NSEvent class], |
+ [NSEventDonor class], |
+ @selector(pressedMouseButtons)) { |
+} |
+ |
+namespace { |
+ |
+NSPoint ConvertScreenPointToTarget(NSWindow* target, |
+ const gfx::Point& point_in_screen) { |
+ // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can |
+ // reposition the window on screen and make things flaky. Initially, just |
+ // assume that the contentRect of |target| is at the top-left corner of the |
+ // screen. |
+ NSRect content_rect = [target contentRectForFrameRect:[target frame]]; |
+ return NSMakePoint(point_in_screen.x(), |
+ NSHeight(content_rect) - point_in_screen.y()); |
+} |
+ |
+// Inverse of ui::EventFlagsFromModifiers(). |
+NSUInteger EventFlagsToModifiers(int flags) { |
+ NSUInteger modifiers = 0; |
+ modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0; |
+ modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0; |
+ modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0; |
+ modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0; |
+ modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0; |
+ // ui::EF_*_MOUSE_BUTTON not handled here. |
+ // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped. |
+ return modifiers; |
+} |
+ |
+// Picks the corresponding mouse event type for the buttons set in |flags|. |
+NSEventType PickMouseEventType(int flags, |
+ NSEventType left, |
+ NSEventType right, |
+ NSEventType other) { |
+ if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) |
+ return other; |
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON) |
+ return right; |
+ 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.
|
+} |
+ |
+// Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set |
+// using the inverse of ui::EventFlagsFromNSEventWithModifiers(). |
+NSEventType EventTypeToNative(ui::EventType ui_event_type, |
+ int flags, |
+ NSUInteger* modifiers) { |
+ if (modifiers) |
+ *modifiers = EventFlagsToModifiers(flags); |
+ switch (ui_event_type) { |
+ case ui::ET_UNKNOWN: |
+ return 0; |
+ case ui::ET_KEY_PRESSED: |
+ return NSKeyDown; |
+ case ui::ET_KEY_RELEASED: |
+ return NSKeyUp; |
+ case ui::ET_MOUSE_PRESSED: |
+ return PickMouseEventType(flags, |
+ NSLeftMouseDown, |
+ NSRightMouseDown, |
+ NSOtherMouseDown); |
+ case ui::ET_MOUSE_RELEASED: |
+ return PickMouseEventType(flags, |
+ NSLeftMouseUp, |
+ NSRightMouseUp, |
+ NSOtherMouseUp); |
+ case ui::ET_MOUSE_DRAGGED: |
+ return PickMouseEventType(flags, |
+ NSLeftMouseDragged, |
+ NSRightMouseDragged, |
+ NSOtherMouseDragged); |
+ case ui::ET_MOUSE_MOVED: |
+ return NSMouseMoved; |
+ case ui::ET_MOUSEWHEEL: |
+ return NSScrollWheel; |
+ case ui::ET_MOUSE_ENTERED: |
+ return NSMouseEntered; |
+ case ui::ET_MOUSE_EXITED: |
+ return NSMouseExited; |
+ case ui::ET_SCROLL_FLING_START: |
+ return NSEventTypeSwipe; |
+ default: |
+ NOTREACHED(); |
+ return 0; |
+ } |
+} |
+ |
+// Emulate the dispatching that would be performed by -[NSWindow sendEvent:]. |
+// sendEvent is a black box which (among other things) will try to peek at the |
+// event queue and can block indefinitely. |
+void EmulateSendEvent(NSWindow* window, NSEvent* event) { |
+ NSResponder* responder = [window firstResponder]; |
+ switch ([event type]) { |
+ case NSKeyDown: |
+ [responder keyDown:event]; |
+ return; |
+ case NSKeyUp: |
+ [responder keyUp:event]; |
+ return; |
+ } |
+ |
+ // For mouse events, NSWindow will use -[NSView hitTest:] for the initial |
+ // mouseDown, and then keep track of the NSView returned. The toolkit-views |
+ // RootView does this too. So, for tests, assume tracking will be done there, |
+ // and the NSWindow's contentView is wrapping a views::internal::RootView. |
+ responder = [window contentView]; |
+ switch ([event type]) { |
+ case NSLeftMouseDown: |
+ [responder mouseDown:event]; |
+ break; |
+ case NSRightMouseDown: |
+ [responder rightMouseDown:event]; |
+ break; |
+ case NSOtherMouseDown: |
+ [responder otherMouseDown:event]; |
+ break; |
+ case NSLeftMouseUp: |
+ [responder mouseUp:event]; |
+ break; |
+ case NSRightMouseUp: |
+ [responder rightMouseUp:event]; |
+ break; |
+ case NSOtherMouseUp: |
+ [responder otherMouseUp:event]; |
+ break; |
+ case NSLeftMouseDragged: |
+ [responder mouseDragged:event]; |
+ break; |
+ case NSRightMouseDragged: |
+ [responder rightMouseDragged:event]; |
+ break; |
+ case NSOtherMouseDragged: |
+ [responder otherMouseDragged:event]; |
+ break; |
+ case NSMouseMoved: |
+ // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that |
+ // NSTrackingAreas have been appropriately installed on |responder|. |
+ [responder mouseMoved:event]; |
+ break; |
+ case NSScrollWheel: |
+ [responder scrollWheel:event]; |
+ break; |
+ case NSMouseEntered: |
+ case NSMouseExited: |
+ // With the assumptions in NSMouseMoved, it doesn't make sense for the |
+ // generator to handle entered/exited separately. It's the responsibility |
+ // of views::internal::RootView to convert the moved events into entered |
+ // and exited events for the individual views. |
+ NOTREACHED(); |
+ break; |
+ case NSEventTypeSwipe: |
+ // NSEventTypeSwipe events can't be generated using public interfaces on |
+ // NSEvent, so this will need to be handled at a higher level. |
+ NOTREACHED(); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+} |
+ |
+void DispatchMouseEventInWindow(NSWindow* window, |
+ ui::EventType event_type, |
+ const gfx::Point& point_in_screen, |
+ int flags) { |
+ NSUInteger click_count = 0; |
+ if (event_type == ui::ET_MOUSE_PRESSED || |
+ event_type == ui::ET_MOUSE_RELEASED) { |
+ if (flags & ui::EF_IS_TRIPLE_CLICK) |
+ click_count = 3; |
+ else if (flags & ui::EF_IS_DOUBLE_CLICK) |
+ click_count = 2; |
+ else |
+ click_count = 1; |
+ } |
+ NSPoint point = ConvertScreenPointToTarget(window, point_in_screen); |
+ NSUInteger modifiers = 0; |
+ NSEventType type = EventTypeToNative(event_type, flags, &modifiers); |
+ NSEvent* event = [NSEvent mouseEventWithType:type |
+ location:point |
+ modifierFlags:modifiers |
+ timestamp:0 |
+ windowNumber:[window windowNumber] |
+ context:nil |
+ eventNumber:0 |
+ clickCount:click_count |
+ pressure:1.0]; |
+ |
+ // Typically events go through NSApplication. For tests, dispatch the event |
+ // directly to make things more predicatble. |
+ EmulateSendEvent(window, event); |
+} |
+ |
+} // namespace |
+ |
+WidgetEventGenerator::WidgetEventGenerator(Widget* widget) |
+ : WidgetEventGeneratorMac(widget->GetNativeWindow()) { |
+} |
+ |
+WidgetEventGenerator::WidgetEventGenerator(gfx::NativeView context) |
+ : WidgetEventGeneratorMac([context window]) { |
+} |
+ |
+WidgetEventGenerator::WidgetEventGenerator( |
+ gfx::NativeView context, |
+ Widget* window_for_initial_location) |
+ : WidgetEventGeneratorMac([context window]) { |
+ set_current_location( |
+ window_for_initial_location->GetRestoredBounds().CenterPoint()); |
+} |
+ |
+WidgetEventGenerator::~WidgetEventGenerator() { |
+} |
+ |
+WidgetEventGeneratorMac::WidgetEventGeneratorMac(gfx::NativeWindow ns_window) |
+ : impl_(new Impl), ns_window_(ns_window), flags_(0) { |
+ DCHECK(!g_active_generator); |
+ g_active_generator = this; |
+} |
+ |
+WidgetEventGeneratorMac::~WidgetEventGeneratorMac() { |
+ DCHECK_EQ(this, g_active_generator); |
+ g_active_generator = NULL; |
+} |
+ |
+void WidgetEventGeneratorMac::PressLeftButton() { |
+ PressButton(ui::EF_LEFT_MOUSE_BUTTON); |
+} |
+ |
+void WidgetEventGeneratorMac::ReleaseLeftButton() { |
+ ReleaseButton(ui::EF_LEFT_MOUSE_BUTTON); |
+} |
+ |
+void WidgetEventGeneratorMac::ClickLeftButton() { |
+ PressLeftButton(); |
+ ReleaseLeftButton(); |
+} |
+ |
+void WidgetEventGeneratorMac::PressRightButton() { |
+ PressButton(ui::EF_RIGHT_MOUSE_BUTTON); |
+} |
+ |
+void WidgetEventGeneratorMac::ReleaseRightButton() { |
+ ReleaseButton(ui::EF_RIGHT_MOUSE_BUTTON); |
+} |
+ |
+void WidgetEventGeneratorMac::GestureTapAt(const gfx::Point& point) { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void WidgetEventGeneratorMac::GestureScrollSequence( |
+ const gfx::Point& start, |
+ const gfx::Point& end, |
+ const base::TimeDelta& duration, |
+ int steps) { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void WidgetEventGeneratorMac::GestureMultiFingerScroll( |
+ int count, |
+ const gfx::Point start[], |
+ int event_separation_time_ms, |
+ int steps, |
+ int move_x, |
+ int move_y) { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void WidgetEventGeneratorMac::MoveMouseTo(const gfx::Point& point_in_screen, |
+ int count) { |
+ DCHECK_GT(count, 0); |
+ const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ? |
+ ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED; |
+ |
+ gfx::Vector2dF diff(point_in_screen - current_location_); |
+ for (float i = 1; i <= count; i++) { |
+ gfx::Vector2dF step(diff); |
+ step.Scale(i / count); |
+ gfx::Point move_point = current_location_ + gfx::ToRoundedVector2d(step); |
+ // TOOD(tapted): Handle moving between windows. |
+ DispatchMouseEventInWindow(ns_window_, event_type, move_point, flags_); |
+ } |
+ current_location_ = point_in_screen; |
+} |
+ |
+void WidgetEventGeneratorMac::DragMouseTo(const gfx::Point& point) { |
+ PressLeftButton(); |
+ MoveMouseTo(point); |
+ ReleaseLeftButton(); |
+} |
+ |
+void WidgetEventGeneratorMac::PressButton(int flag) { |
+ flags_ |= flag; |
+ DispatchMouseEventInWindow(ns_window_, |
+ ui::ET_MOUSE_PRESSED, |
+ current_location_, |
+ flag); |
+} |
+ |
+void WidgetEventGeneratorMac::ReleaseButton(int flag) { |
+ DCHECK(flags_ & flag); |
+ flags_ &= ~flag; |
+ DispatchMouseEventInWindow(ns_window_, |
+ ui::ET_MOUSE_RELEASED, |
+ current_location_, |
+ flag); |
+} |
+ |
+} // namespace views |
+} // namespace test |