Commit 4a95d096 authored by tapted's avatar tapted Committed by Commit bot

MacViews: Convert Cocoa action messages into editing commands for text fields

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). Action messages are also the manner by which a user on Mac can
add custom keybindings, by making entries in
~/Library/KeyBindings/DefaultKeyBinding.dict

ActionMessages map well to editing commands for text fields, but not as
well for other controls. For example, MenuController needs to catch the
action message "cancel:" to handle the escape key.

The approach in this CL is for BridgedContentView, an NSResponder, to
map Action Messages to the editing command as well as the KeyCode that
toolkit-views expects for that action.

To test, textfield_unittests are updated to allow two kinds of event
dispatch. Before this CL, all the TextfieldTest views_unittests were
already passing on a toolkit-views Mac build. This is because the tests
send KeyCodes directly to the InputMethod rather than to the window.
However, this meant that action messages didn't come into play.

To test "real" keystrokes on Mac and get good coverage of the action
message overloads, the ui::test::EventGenerator is now used in
textfield_unittest.cc to generate platform-specific events that are
dispatched to the Window. Previously, all keyboard events were
redirected to the InputMethod.

BUG=378134

Review URL: https://codereview.chromium.org/879253002

Cr-Commit-Position: refs/heads/master@{#314268}
parent 441c6a56
......@@ -8,6 +8,9 @@
#import "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/base/ime/text_input_client.h"
#import "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/events/keycodes/dom3/dom_code.h"
#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
#include "ui/gfx/canvas_paint_mac.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/strings/grit/ui_strings.h"
......@@ -42,9 +45,22 @@ gfx::Point MovePointToWindow(const NSPoint& point,
// the event to NativeWidgetMac for handling.
- (void)handleMouseEvent:(NSEvent*)theEvent;
// Execute a command on the currently focused TextInputClient.
// |commandId| should be a resource ID from ui_strings.grd.
- (void)doCommandByID:(int)commandId;
// Handles an NSResponder Action Message by mapping it to a corresponding text
// editing command from ui_strings.grd and, when not being sent to a
// TextInputClient, the keyCode that toolkit-views expects internally.
// For example, moveToLeftEndOfLine: would pass ui::VKEY_HOME in non-RTL locales
// even though the Home key on Mac defaults to moveToBeginningOfDocument:.
// This approach also allows action messages a user
// may have remapped in ~/Library/KeyBindings/DefaultKeyBinding.dict to be
// catered for.
// Note: default key bindings in Mac can be read from StandardKeyBinding.dict
// which lives in /System/Library/Frameworks/AppKit.framework/Resources. Do
// `plutil -convert xml1 -o StandardKeyBinding.xml StandardKeyBinding.dict` to
// get something readable.
- (void)handleAction:(int)commandId
keyCode:(ui::KeyboardCode)keyCode
domCode:(ui::DomCode)domCode
eventFlags:(int)eventFlags;
@end
......@@ -111,9 +127,24 @@ gfx::Point MovePointToWindow(const NSPoint& point,
hostedView_->GetWidget()->OnMouseEvent(&event);
}
- (void)doCommandByID:(int)commandId {
if (textInputClient_ && textInputClient_->IsEditingCommandEnabled(commandId))
- (void)handleAction:(int)commandId
keyCode:(ui::KeyboardCode)keyCode
domCode:(ui::DomCode)domCode
eventFlags:(int)eventFlags {
if (!hostedView_)
return;
// If there's an active TextInputClient, it ignores the key and processes the
// logical editing action.
if (commandId && textInputClient_ &&
textInputClient_->IsEditingCommandEnabled(commandId)) {
textInputClient_->ExecuteEditingCommand(commandId);
return;
}
// Otherwise, process the action as a regular key event.
ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags);
hostedView_->GetWidget()->OnKeyEvent(&event);
}
// NSView implementation.
......@@ -147,10 +178,8 @@ gfx::Point MovePointToWindow(const NSPoint& point,
// NSResponder implementation.
- (void)keyDown:(NSEvent*)theEvent {
if (textInputClient_)
[self interpretKeyEvents:@[ theEvent ]];
else
[super keyDown:theEvent];
// Convert the event into an action message, according to OSX key mappings.
[self interpretKeyEvents:@[ theEvent ]];
}
- (void)mouseDown:(NSEvent*)theEvent {
......@@ -204,25 +233,164 @@ gfx::Point MovePointToWindow(const NSPoint& point,
hostedView_->GetWidget()->OnMouseEvent(&event);
}
- (void)deleteBackward:(id)sender {
[self doCommandByID:IDS_DELETE_BACKWARD];
////////////////////////////////////////////////////////////////////////////////
// NSResponder Action Messages. Keep sorted according NSResponder.h (from the
// 10.9 SDK). The list should eventually be complete. Anything not defined will
// beep when interpretKeyEvents: would otherwise call it.
// TODO(tapted): Make this list complete.
// The insertText action message forwards to the TextInputClient unless a menu
// is active.
- (void)insertText:(id)text {
[self insertText:text replacementRange:NSMakeRange(NSNotFound, 0)];
}
- (void)deleteForward:(id)sender {
[self doCommandByID:IDS_DELETE_FORWARD];
// Selection movement and scrolling.
- (void)moveRight:(id)sender {
[self handleAction:IDS_MOVE_RIGHT
keyCode:ui::VKEY_RIGHT
domCode:ui::DomCode::ARROW_RIGHT
eventFlags:0];
}
- (void)moveLeft:(id)sender {
[self doCommandByID:IDS_MOVE_LEFT];
[self handleAction:IDS_MOVE_LEFT
keyCode:ui::VKEY_LEFT
domCode:ui::DomCode::ARROW_LEFT
eventFlags:0];
}
- (void)moveRight:(id)sender {
[self doCommandByID:IDS_MOVE_RIGHT];
- (void)moveUp:(id)sender {
[self handleAction:0
keyCode:ui::VKEY_UP
domCode:ui::DomCode::ARROW_UP
eventFlags:0];
}
- (void)insertText:(id)text {
if (textInputClient_)
textInputClient_->InsertText(base::SysNSStringToUTF16(text));
- (void)moveDown:(id)sender {
[self handleAction:0
keyCode:ui::VKEY_DOWN
domCode:ui::DomCode::ARROW_DOWN
eventFlags:0];
}
- (void)moveWordRight:(id)sender {
[self handleAction:IDS_MOVE_WORD_RIGHT
keyCode:ui::VKEY_RIGHT
domCode:ui::DomCode::ARROW_RIGHT
eventFlags:ui::EF_CONTROL_DOWN];
}
- (void)moveWordLeft:(id)sender {
[self handleAction:IDS_MOVE_WORD_LEFT
keyCode:ui::VKEY_LEFT
domCode:ui::DomCode::ARROW_LEFT
eventFlags:ui::EF_CONTROL_DOWN];
}
- (void)moveLeftAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_LEFT_AND_MODIFY_SELECTION
keyCode:ui::VKEY_LEFT
domCode:ui::DomCode::ARROW_LEFT
eventFlags:ui::EF_SHIFT_DOWN];
}
- (void)moveRightAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_RIGHT_AND_MODIFY_SELECTION
keyCode:ui::VKEY_RIGHT
domCode:ui::DomCode::ARROW_RIGHT
eventFlags:ui::EF_SHIFT_DOWN];
}
- (void)moveWordRightAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
keyCode:ui::VKEY_RIGHT
domCode:ui::DomCode::ARROW_RIGHT
eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
}
- (void)moveWordLeftAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION
keyCode:ui::VKEY_LEFT
domCode:ui::DomCode::ARROW_LEFT
eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
}
- (void)moveToLeftEndOfLine:(id)sender {
[self handleAction:IDS_MOVE_TO_BEGINNING_OF_LINE
keyCode:ui::VKEY_HOME
domCode:ui::DomCode::HOME
eventFlags:0];
}
- (void)moveToRightEndOfLine:(id)sender {
[self handleAction:IDS_MOVE_TO_END_OF_LINE
keyCode:ui::VKEY_END
domCode:ui::DomCode::END
eventFlags:0];
}
- (void)moveToLeftEndOfLineAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
keyCode:ui::VKEY_HOME
domCode:ui::DomCode::HOME
eventFlags:ui::EF_SHIFT_DOWN];
}
- (void)moveToRightEndOfLineAndModifySelection:(id)sender {
[self handleAction:IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
keyCode:ui::VKEY_END
domCode:ui::DomCode::END
eventFlags:ui::EF_SHIFT_DOWN];
}
// Insertions and Indentations.
- (void)insertNewline:(id)sender {
[self handleAction:0
keyCode:ui::VKEY_RETURN
domCode:ui::DomCode::ENTER
eventFlags:0];
}
// Deletions.
- (void)deleteForward:(id)sender {
[self handleAction:IDS_DELETE_FORWARD
keyCode:ui::VKEY_DELETE
domCode:ui::DomCode::DEL
eventFlags:0];
}
- (void)deleteBackward:(id)sender {
[self handleAction:IDS_DELETE_BACKWARD
keyCode:ui::VKEY_BACK
domCode:ui::DomCode::BACKSPACE
eventFlags:0];
}
- (void)deleteWordForward:(id)sender {
[self handleAction:IDS_DELETE_WORD_FORWARD
keyCode:ui::VKEY_DELETE
domCode:ui::DomCode::DEL
eventFlags:ui::EF_CONTROL_DOWN];
}
- (void)deleteWordBackward:(id)sender {
[self handleAction:IDS_DELETE_WORD_BACKWARD
keyCode:ui::VKEY_BACK
domCode:ui::DomCode::BACKSPACE
eventFlags:ui::EF_CONTROL_DOWN];
}
// Cancellation.
- (void)cancelOperation:(id)sender {
[self handleAction:0
keyCode:ui::VKEY_ESCAPE
domCode:ui::DomCode::ESCAPE
eventFlags:0];
}
// Support for Services in context menus.
......
......@@ -10,6 +10,7 @@
#include "ui/events/event_target.h"
#include "ui/events/event_target_iterator.h"
#include "ui/events/event_targeter.h"
#import "ui/events/test/cocoa_test_event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/mac/coordinate_conversion.h"
......@@ -249,6 +250,7 @@ class EventGeneratorDelegateMac : public ui::EventTarget,
// Overridden from ui::EventHandler (via ui::EventTarget):
void OnMouseEvent(ui::MouseEvent* event) override;
void OnKeyEvent(ui::KeyEvent* event) override;
// Overridden from ui::EventSource:
ui::EventProcessor* GetEventProcessor() override { return this; }
......@@ -323,6 +325,17 @@ void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
EmulateSendEvent(window_, ns_event);
}
void EventGeneratorDelegateMac::OnKeyEvent(ui::KeyEvent* event) {
NSUInteger modifiers = EventFlagsToModifiers(event->flags());
NSEvent* ns_event = cocoa_test_event_utils::SynthesizeKeyEvent(
window_, event->type() == ui::ET_KEY_PRESSED, event->key_code(),
modifiers);
if (owner_->targeting_application())
[NSApp sendEvent:ns_event];
else
EmulateSendEvent(window_, ns_event);
}
void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner,
gfx::NativeWindow root_window,
gfx::NativeWindow window) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment