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