/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "NativeKeyBindings.h" #include "nsTArray.h" #include "nsCocoaUtils.h" #include "mozilla/Logging.h" #include "mozilla/TextEvents.h" namespace mozilla { namespace widget { PRLogModuleInfo* gNativeKeyBindingsLog = nullptr; NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr; NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr; // static NativeKeyBindings* NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) { switch (aType) { case nsIWidget::NativeKeyBindingsForSingleLineEditor: if (!sInstanceForSingleLineEditor) { sInstanceForSingleLineEditor = new NativeKeyBindings(); sInstanceForSingleLineEditor->Init(aType); } return sInstanceForSingleLineEditor; case nsIWidget::NativeKeyBindingsForMultiLineEditor: case nsIWidget::NativeKeyBindingsForRichTextEditor: if (!sInstanceForMultiLineEditor) { sInstanceForMultiLineEditor = new NativeKeyBindings(); sInstanceForMultiLineEditor->Init(aType); } return sInstanceForMultiLineEditor; default: MOZ_CRASH("Not implemented"); return nullptr; } } // static void NativeKeyBindings::Shutdown() { delete sInstanceForSingleLineEditor; sInstanceForSingleLineEditor = nullptr; delete sInstanceForMultiLineEditor; sInstanceForMultiLineEditor = nullptr; } NativeKeyBindings::NativeKeyBindings() { } #define SEL_TO_COMMAND(aSel, aCommand) \ mSelectorToCommand.Put( \ reinterpret_cast<struct objc_selector *>(@selector(aSel)), aCommand) void NativeKeyBindings::Init(NativeKeyBindingsType aType) { if (!gNativeKeyBindingsLog) { gNativeKeyBindingsLog = PR_NewLogModule("NativeKeyBindings"); } MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::Init", this)); // Many selectors have a one-to-one mapping to a Gecko command. Those mappings // are registered in mSelectorToCommand. // Selectors from NSResponder's "Responding to Action Messages" section and // from NSText's "Action Methods for Editing" section // TODO: Improves correctness of left / right meaning // TODO: Add real paragraph motions // SEL_TO_COMMAND(cancelOperation:, ); // SEL_TO_COMMAND(capitalizeWord:, ); // SEL_TO_COMMAND(centerSelectionInVisibleArea:, ); // SEL_TO_COMMAND(changeCaseOfLetter:, ); // SEL_TO_COMMAND(complete:, ); SEL_TO_COMMAND(copy:, CommandCopy); // SEL_TO_COMMAND(copyFont:, ); // SEL_TO_COMMAND(copyRuler:, ); SEL_TO_COMMAND(cut:, CommandCut); SEL_TO_COMMAND(delete:, CommandDelete); SEL_TO_COMMAND(deleteBackward:, CommandDeleteCharBackward); // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, ); SEL_TO_COMMAND(deleteForward:, CommandDeleteCharForward); // TODO: deleteTo* selectors are also supposed to add text to a kill buffer SEL_TO_COMMAND(deleteToBeginningOfLine:, CommandDeleteToBeginningOfLine); SEL_TO_COMMAND(deleteToBeginningOfParagraph:, CommandDeleteToBeginningOfLine); SEL_TO_COMMAND(deleteToEndOfLine:, CommandDeleteToEndOfLine); SEL_TO_COMMAND(deleteToEndOfParagraph:, CommandDeleteToEndOfLine); // SEL_TO_COMMAND(deleteToMark:, ); SEL_TO_COMMAND(deleteWordBackward:, CommandDeleteWordBackward); SEL_TO_COMMAND(deleteWordForward:, CommandDeleteWordForward); // SEL_TO_COMMAND(indent:, ); // SEL_TO_COMMAND(insertBacktab:, ); // SEL_TO_COMMAND(insertContainerBreak:, ); // SEL_TO_COMMAND(insertLineBreak:, ); // SEL_TO_COMMAND(insertNewline:, ); // SEL_TO_COMMAND(insertNewlineIgnoringFieldEditor:, ); // SEL_TO_COMMAND(insertParagraphSeparator:, ); // SEL_TO_COMMAND(insertTab:, ); // SEL_TO_COMMAND(insertTabIgnoringFieldEditor:, ); // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, ); // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, ); // SEL_TO_COMMAND(lowercaseWord:, ); SEL_TO_COMMAND(moveBackward:, CommandCharPrevious); SEL_TO_COMMAND(moveBackwardAndModifySelection:, CommandSelectCharPrevious); if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { SEL_TO_COMMAND(moveDown:, CommandEndLine); } else { SEL_TO_COMMAND(moveDown:, CommandLineNext); } SEL_TO_COMMAND(moveDownAndModifySelection:, CommandSelectLineNext); SEL_TO_COMMAND(moveForward:, CommandCharNext); SEL_TO_COMMAND(moveForwardAndModifySelection:, CommandSelectCharNext); SEL_TO_COMMAND(moveLeft:, CommandCharPrevious); SEL_TO_COMMAND(moveLeftAndModifySelection:, CommandSelectCharPrevious); SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:, CommandSelectBeginLine); SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, CommandSelectEndLine); SEL_TO_COMMAND(moveRight:, CommandCharNext); SEL_TO_COMMAND(moveRightAndModifySelection:, CommandSelectCharNext); SEL_TO_COMMAND(moveToBeginningOfDocument:, CommandMoveTop); SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:, CommandSelectTop); SEL_TO_COMMAND(moveToBeginningOfLine:, CommandBeginLine); SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:, CommandSelectBeginLine); SEL_TO_COMMAND(moveToBeginningOfParagraph:, CommandBeginLine); SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:, CommandSelectBeginLine); SEL_TO_COMMAND(moveToEndOfDocument:, CommandMoveBottom); SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, CommandSelectBottom); SEL_TO_COMMAND(moveToEndOfLine:, CommandEndLine); SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, CommandSelectEndLine); SEL_TO_COMMAND(moveToEndOfParagraph:, CommandEndLine); SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, CommandSelectEndLine); SEL_TO_COMMAND(moveToLeftEndOfLine:, CommandBeginLine); SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:, CommandSelectBeginLine); SEL_TO_COMMAND(moveToRightEndOfLine:, CommandEndLine); SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, CommandSelectEndLine); if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { SEL_TO_COMMAND(moveUp:, CommandBeginLine); } else { SEL_TO_COMMAND(moveUp:, CommandLinePrevious); } SEL_TO_COMMAND(moveUpAndModifySelection:, CommandSelectLinePrevious); SEL_TO_COMMAND(moveWordBackward:, CommandWordPrevious); SEL_TO_COMMAND(moveWordBackwardAndModifySelection:, CommandSelectWordPrevious); SEL_TO_COMMAND(moveWordForward:, CommandWordNext); SEL_TO_COMMAND(moveWordForwardAndModifySelection:, CommandSelectWordNext); SEL_TO_COMMAND(moveWordLeft:, CommandWordPrevious); SEL_TO_COMMAND(moveWordLeftAndModifySelection:, CommandSelectWordPrevious); SEL_TO_COMMAND(moveWordRight:, CommandWordNext); SEL_TO_COMMAND(moveWordRightAndModifySelection:, CommandSelectWordNext); SEL_TO_COMMAND(pageDown:, CommandMovePageDown); SEL_TO_COMMAND(pageDownAndModifySelection:, CommandSelectPageDown); SEL_TO_COMMAND(pageUp:, CommandMovePageUp); SEL_TO_COMMAND(pageUpAndModifySelection:, CommandSelectPageUp); SEL_TO_COMMAND(paste:, CommandPaste); // SEL_TO_COMMAND(pasteFont:, ); // SEL_TO_COMMAND(pasteRuler:, ); SEL_TO_COMMAND(scrollLineDown:, CommandScrollLineDown); SEL_TO_COMMAND(scrollLineUp:, CommandScrollLineUp); SEL_TO_COMMAND(scrollPageDown:, CommandScrollPageDown); SEL_TO_COMMAND(scrollPageUp:, CommandScrollPageUp); SEL_TO_COMMAND(scrollToBeginningOfDocument:, CommandScrollTop); SEL_TO_COMMAND(scrollToEndOfDocument:, CommandScrollBottom); SEL_TO_COMMAND(selectAll:, CommandSelectAll); // selectLine: is complex, see KeyDown if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { SEL_TO_COMMAND(selectParagraph:, CommandSelectAll); } // SEL_TO_COMMAND(selectToMark:, ); // selectWord: is complex, see KeyDown // SEL_TO_COMMAND(setMark:, ); // SEL_TO_COMMAND(showContextHelp:, ); // SEL_TO_COMMAND(supplementalTargetForAction:sender:, ); // SEL_TO_COMMAND(swapWithMark:, ); // SEL_TO_COMMAND(transpose:, ); // SEL_TO_COMMAND(transposeWords:, ); // SEL_TO_COMMAND(uppercaseWord:, ); // SEL_TO_COMMAND(yank:, ); } #undef SEL_TO_COMMAND bool NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent, DoCommandCallback aCallback, void* aCallbackData) { MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress", this)); // Recover the current event, which should always be the key down we are // responding to. NSEvent* cocoaEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent); if (!cocoaEvent || [cocoaEvent type] != NSKeyDown) { MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, no Cocoa key down event", this)); return false; } MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, interpreting", this)); AutoTArray<KeyBindingsCommand, 2> bindingCommands; nsCocoaUtils::GetCommandsFromKeyEvent(cocoaEvent, bindingCommands); MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, bindingCommands=%u", this, bindingCommands.Length())); AutoTArray<Command, 4> geckoCommands; for (uint32_t i = 0; i < bindingCommands.Length(); i++) { SEL selector = bindingCommands[i].selector; if (MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) { NSString* selectorString = NSStringFromSelector(selector); nsAutoString nsSelectorString; nsCocoaUtils::GetStringForNSString(selectorString, nsSelectorString); MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, selector=%s", this, NS_LossyConvertUTF16toASCII(nsSelectorString).get())); } // Try to find a simple mapping in the hashtable Command geckoCommand = static_cast<Command>(mSelectorToCommand.Get( reinterpret_cast<struct objc_selector*>(selector))); if (geckoCommand) { geckoCommands.AppendElement(geckoCommand); } else if (selector == @selector(selectLine:)) { // This is functional, but Cocoa's version is direction-less in that // selection direction is not determined until some future directed action // is taken. See bug 282097, comment 79 for more details. geckoCommands.AppendElement(CommandBeginLine); geckoCommands.AppendElement(CommandSelectEndLine); } else if (selector == @selector(selectWord:)) { // This is functional, but Cocoa's version is direction-less in that // selection direction is not determined until some future directed action // is taken. See bug 282097, comment 79 for more details. geckoCommands.AppendElement(CommandWordPrevious); geckoCommands.AppendElement(CommandSelectWordNext); } } if (geckoCommands.IsEmpty()) { MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, handled=false", this)); return false; } for (uint32_t i = 0; i < geckoCommands.Length(); i++) { Command geckoCommand = geckoCommands[i]; MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, command=%s", this, WidgetKeyboardEvent::GetCommandStr(geckoCommand))); // Execute the Gecko command aCallback(geckoCommand, aCallbackData); } MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::KeyPress, handled=true", this)); return true; } } // namespace widget } // namespace mozilla