diff options
author | tapted <tapted@chromium.org> | 2015-02-04 17:38:27 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-05 01:40:44 +0000 |
commit | 370605a7ffb0392e48729591dc8f6fca79cdc987 (patch) | |
tree | 0f2fe70a549a2d178b643de84dc73e7a17aa2cc0 /ui | |
parent | f4cfda6132b4a8fa36b0dbe2d91638edebf362f5 (diff) | |
download | chromium_src-370605a7ffb0392e48729591dc8f6fca79cdc987.zip chromium_src-370605a7ffb0392e48729591dc8f6fca79cdc987.tar.gz chromium_src-370605a7ffb0392e48729591dc8f6fca79cdc987.tar.bz2 |
MacViews: Intercept events for Menus (after AppKit has turned them into action messages).
Currently keystrokes while a menu is open are sent to the window hosting
the menu, since it remains `key`. Menus are a different window, so this
causes all the Menu* interactive_ui_tests to fail since they simulate
pressing ESC to dismiss the menu in the tests.
Other platforms insert an event dispatcher into the message pump for the
nested run loop that shows a menu, and redirect KeyDown events to the
MenuController.
On Mac, events need to be mapped to "Action" messages by an NSResponder
to obey platform behaviour and user customizations (e.g. a "Home"
keypress should be interpreted as beginning of document, not beginning
of line).
The approach in this CL is to check in BridgedContentView, an
NSResponder, for an active menu that should be receiving events instead.
Action messages are mapped to the KeyCode that toolkit-views expects for
that action, and the MenuController is given an opportunity to swallow
the event or have it sent on to the key window.
Gets the following interactive_ui_tests passing:
MenuControllerMnemonicTest*{NoMatch,TitleMatch,MnemonicMatch}
MenuItemViewTestRemoveWithSubmenu{0,1}*
(and all Menu interactive UI tests pass after this).
BUG=403679
Review URL: https://codereview.chromium.org/866983006
Cr-Commit-Position: refs/heads/master@{#314711}
Diffstat (limited to 'ui')
-rw-r--r-- | ui/views/cocoa/bridged_content_view.mm | 53 | ||||
-rw-r--r-- | ui/views/cocoa/native_widget_mac_nswindow.mm | 26 | ||||
-rw-r--r-- | ui/views/controls/menu/menu_controller.cc | 22 | ||||
-rw-r--r-- | ui/views/controls/menu/menu_controller.h | 7 |
4 files changed, 107 insertions, 1 deletions
diff --git a/ui/views/cocoa/bridged_content_view.mm b/ui/views/cocoa/bridged_content_view.mm index 8582fb6..b5ddb05 100644 --- a/ui/views/cocoa/bridged_content_view.mm +++ b/ui/views/cocoa/bridged_content_view.mm @@ -14,9 +14,12 @@ #include "ui/gfx/canvas_paint_mac.h" #include "ui/gfx/geometry/rect.h" #include "ui/strings/grit/ui_strings.h" +#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" +using views::MenuController; + namespace { // Convert a |point| in |source_window|'s AppKit coordinate system (origin at @@ -135,6 +138,15 @@ gfx::Point MovePointToWindow(const NSPoint& point, if (!hostedView_) return; + // If there's an active MenuController it gets preference, and it will likely + // swallow the event. + MenuController* menuController = MenuController::GetActiveInstance(); + if (menuController && menuController->owner() == hostedView_->GetWidget()) { + if (menuController->OnWillDispatchKeyEvent(0, keyCode) == + ui::POST_DISPATCH_NONE) + return; + } + // If there's an active TextInputClient, it ignores the key and processes the // logical editing action. if (commandId && textInputClient_ && @@ -176,6 +188,20 @@ gfx::Point MovePointToWindow(const NSPoint& point, hostedView_->GetWidget()->OnNativeWidgetPaint(&canvas); } +- (NSTextInputContext*)inputContext { + if (!hostedView_) + return [super inputContext]; + + // If a menu is active, and -[NSView interpretKeyEvents:] asks for the + // input context, return nil. This ensures the action message is sent to + // the view, rather than any NSTextInputClient a subview has installed. + MenuController* menuController = MenuController::GetActiveInstance(); + if (menuController && menuController->owner() == hostedView_->GetWidget()) + return nil; + + return [super inputContext]; +} + // NSResponder implementation. - (void)keyDown:(NSEvent*)theEvent { @@ -474,11 +500,36 @@ gfx::Point MovePointToWindow(const NSPoint& point, } - (void)insertText:(id)text replacementRange:(NSRange)replacementRange { - if (!textInputClient_) + if (!hostedView_) return; if ([text isKindOfClass:[NSAttributedString class]]) text = [text string]; + + MenuController* menuController = MenuController::GetActiveInstance(); + if (menuController && menuController->owner() == hostedView_->GetWidget()) { + // Handle menu mnemonics (e.g. "sav" jumps to "Save"). Handles both single- + // characters and input from IME. For IME, swallow the entire string unless + // the very first character gives ui::POST_DISPATCH_PERFORM_DEFAULT. + bool swallowedAny = false; + for (NSUInteger i = 0; i < [text length]; ++i) { + if (!menuController || + menuController->OnWillDispatchKeyEvent([text characterAtIndex:i], + ui::VKEY_UNKNOWN) == + ui::POST_DISPATCH_PERFORM_DEFAULT) { + if (swallowedAny) + return; // Swallow remainder. + break; + } + swallowedAny = true; + // Ensure the menu remains active. + menuController = MenuController::GetActiveInstance(); + } + } + + if (!textInputClient_) + return; + textInputClient_->DeleteRange(gfx::Range(replacementRange)); textInputClient_->InsertText(base::SysNSStringToUTF16(text)); } diff --git a/ui/views/cocoa/native_widget_mac_nswindow.mm b/ui/views/cocoa/native_widget_mac_nswindow.mm index e8c3c8b..00b2e1a 100644 --- a/ui/views/cocoa/native_widget_mac_nswindow.mm +++ b/ui/views/cocoa/native_widget_mac_nswindow.mm @@ -6,11 +6,13 @@ #include "base/mac/foundation_util.h" #import "ui/views/cocoa/views_nswindow_delegate.h" +#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/widget/native_widget_mac.h" @interface NativeWidgetMacNSWindow () - (ViewsNSWindowDelegate*)viewsNSWindowDelegate; - (views::Widget*)viewsWidget; +- (BOOL)hasViewsMenuActive; @end @implementation NativeWidgetMacNSWindow @@ -23,6 +25,12 @@ return [[self viewsNSWindowDelegate] nativeWidgetMac]->GetWidget(); } +- (BOOL)hasViewsMenuActive { + views::MenuController* menuController = + views::MenuController::GetActiveInstance(); + return menuController && menuController->owner() == [self viewsWidget]; +} + // Ignore [super canBecome{Key,Main}Window]. The default is NO for windows with // NSBorderlessWindowMask, which is not the desired behavior. // Note these can be called via -[NSWindow close] while the widget is being torn @@ -35,6 +43,24 @@ return [self delegate] && [self viewsWidget]->CanActivate(); } +// Override sendEvent to allow key events to be forwarded to a toolkit-views +// menu while it is active, and while still allowing any native subview to +// retain firstResponder status. +- (void)sendEvent:(NSEvent*)event { + NSEventType type = [event type]; + if ((type != NSKeyDown && type != NSKeyUp) || ![self hasViewsMenuActive]) { + [super sendEvent:event]; + return; + } + + // Send to the menu, after converting the event into an action message using + // the content view. + if (type == NSKeyDown) + [[self contentView] keyDown:event]; + else + [[self contentView] keyUp:event]; +} + // Override display, since this is the first opportunity Cocoa gives to detect // a visibility change in some cases. For example, restoring from the dock first // calls -[NSWindow display] before any NSWindowDelegate functions and before diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc index d1547c4..b74ddce 100644 --- a/ui/views/controls/menu/menu_controller.cc +++ b/ui/views/controls/menu/menu_controller.cc @@ -804,6 +804,22 @@ void MenuController::OnDragComplete(bool should_close) { } } +ui::PostDispatchAction MenuController::OnWillDispatchKeyEvent( + base::char16 character, + ui::KeyboardCode key_code) { + if (exit_type() == MenuController::EXIT_ALL || + exit_type() == MenuController::EXIT_DESTROYED) { + TerminateNestedMessageLoop(); + return ui::POST_DISPATCH_PERFORM_DEFAULT; + } + + bool should_quit = character ? SelectByChar(character) : !OnKeyDown(key_code); + if (should_quit || exit_type() != MenuController::EXIT_NONE) + TerminateNestedMessageLoop(); + + return ui::POST_DISPATCH_NONE; +} + void MenuController::UpdateSubmenuSelection(SubmenuView* submenu) { if (submenu->IsShowing()) { gfx::Point point = GetScreen()->GetCursorScreenPoint(); @@ -1028,16 +1044,22 @@ bool MenuController::OnKeyDown(ui::KeyboardCode key_code) { CloseSubmenu(); break; +// On Mac, treat space the same as return. +#if !defined(OS_MACOSX) case ui::VKEY_SPACE: if (SendAcceleratorToHotTrackedView() == ACCELERATOR_PROCESSED_EXIT) return false; break; +#endif case ui::VKEY_F4: if (!is_combobox_) break; // Fallthrough to accept or dismiss combobox menus on F4, like windows. case ui::VKEY_RETURN: +#if defined(OS_MACOSX) + case ui::VKEY_SPACE: +#endif if (pending_state_.item) { if (pending_state_.item->HasSubmenu()) { if (key_code == ui::VKEY_F4 && diff --git a/ui/views/controls/menu/menu_controller.h b/ui/views/controls/menu/menu_controller.h index 92ea8c4..76060b0 100644 --- a/ui/views/controls/menu/menu_controller.h +++ b/ui/views/controls/menu/menu_controller.h @@ -160,6 +160,13 @@ class VIEWS_EXPORT MenuController : public WidgetObserver { // corresponds to whether or not the menu should close. void OnDragComplete(bool should_close); + // Called while dispatching messages to intercept key events. + // If |character| is other than 0, it is handled as a mnemonic. + // Otherwise, |key_code| is handled as a menu navigation command. + // Returns ui::POST_DISPATCH_NONE if the event was swallowed by the menu. + ui::PostDispatchAction OnWillDispatchKeyEvent(base::char16 character, + ui::KeyboardCode key_code); + // Update the submenu's selection based on the current mouse location void UpdateSubmenuSelection(SubmenuView* source); |