| 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..d53cde14d1f37b632c2f442342e2f52a04f664f6
|
| --- /dev/null
|
| +++ b/ui/views/test/widget_event_generator_mac.mm
|
| @@ -0,0 +1,343 @@
|
| +// 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 ConvertRootPointToTarget(NSWindow* target,
|
| + const gfx::Point& point_in_root) {
|
| + // 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_root.x(),
|
| + NSHeight(content_rect) - point_in_root.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_LEFT_MOUSE_BUTTON)
|
| + return left;
|
| + if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
|
| + return right;
|
| + return other;
|
| +}
|
| +
|
| +// 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_root,
|
| + 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 = ConvertRootPointToTarget(window, point_in_root);
|
| + 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)
|
| + : EventGeneratorBase(gfx::Point()),
|
| + impl_(new Impl),
|
| + ns_window_(ns_window) {
|
| + DCHECK(!g_active_generator);
|
| + g_active_generator = this;
|
| +}
|
| +
|
| +WidgetEventGeneratorMac::~WidgetEventGeneratorMac() {
|
| + DCHECK_EQ(this, g_active_generator);
|
| + g_active_generator = NULL;
|
| +}
|
| +
|
| +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();
|
| +}
|
| +
|
| +gfx::Point WidgetEventGeneratorMac::GetLocationInCurrentRoot() const {
|
| + // WidgetEventGeneratorMac currently assumes a single window positioned at the
|
| + // screen origin to avoid flakiness when AppKit repositions the window (e.g.
|
| + // depending where the Dock is positioned on screen).
|
| + // TODO(tapted): Support multiple windows if tests need them.
|
| + return current_location();
|
| +}
|
| +
|
| +void WidgetEventGeneratorMac::DoMoveMouseTo(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());
|
| + }
|
| + set_current_location(point_in_screen);
|
| +}
|
| +
|
| +void WidgetEventGeneratorMac::DispatchMouseEvent(
|
| + ui::EventType type,
|
| + const gfx::Point& location_in_root,
|
| + int flags,
|
| + int changed_button_flags) {
|
| + DispatchMouseEventInWindow(ns_window_,
|
| + type,
|
| + location_in_root,
|
| + changed_button_flags);
|
| +}
|
| +
|
| +} // namespace views
|
| +} // namespace test
|
|
|