summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authortapted <tapted@chromium.org>2015-02-04 17:38:27 -0800
committerCommit bot <commit-bot@chromium.org>2015-02-05 01:40:44 +0000
commit370605a7ffb0392e48729591dc8f6fca79cdc987 (patch)
tree0f2fe70a549a2d178b643de84dc73e7a17aa2cc0 /ui
parentf4cfda6132b4a8fa36b0dbe2d91638edebf362f5 (diff)
downloadchromium_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.mm53
-rw-r--r--ui/views/cocoa/native_widget_mac_nswindow.mm26
-rw-r--r--ui/views/controls/menu/menu_controller.cc22
-rw-r--r--ui/views/controls/menu/menu_controller.h7
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);