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

Unified 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: cull orthogonal stuff, add WidgetTest.MouseEventTypesViaGenerator 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 side-by-side diff with in-line comments
Download patch
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..5cfa0ecf82f9862327ea2a75e72fef3ffd5237dc
--- /dev/null
+++ b/ui/views/test/widget_event_generator_mac.mm
@@ -0,0 +1,358 @@
+// 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;
+}
+
+// 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];
+ break;
+ case NSKeyUp:
+ [responder keyUp:event];
+ break;
+ case NSLeftMouseDown:
+ [responder mouseDown:event];
Andre 2014/06/17 21:17:56 The target of mouse events should be the view unde
tapted 2014/06/18 08:28:06 Hm good point. Although hitTest is just for moused
+ 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.
+ [responder mouseMoved:event];
+ break;
+ case NSScrollWheel:
+ [responder scrollWheel:event];
+ break;
+ case NSMouseEntered:
+ [responder mouseEntered:event];
+ break;
+ case NSMouseExited:
+ [responder mouseExited:event];
+ break;
+ case NSEventTypeSwipe:
+ [responder swipeWithEvent:event];
+ 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() {
+}
+
+// static
+void WidgetEventGenerator::SimulateNativeDestroy(Widget* widget) {
+ [widget->GetNativeWindow() release];
+}
+
+WidgetEventGeneratorMac::WidgetEventGeneratorMac(NSWindow* 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

Powered by Google App Engine
This is Rietveld 408576698