/* -*- 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/. */ #ifndef TextInputHandler_h_ #define TextInputHandler_h_ #include "nsCocoaUtils.h" #import #import #include "mozView.h" #include "nsString.h" #include "nsCOMPtr.h" #include "nsITimer.h" #include "nsTArray.h" #include "mozilla/EventForwards.h" #include "mozilla/TextEventDispatcherListener.h" #include "WritingModes.h" class nsChildView; namespace mozilla { namespace widget { // Key code constants enum { #if !defined(MAC_OS_X_VERSION_10_12) || \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 kVK_RightCommand = 0x36, // right command key #endif kVK_PC_PrintScreen = kVK_F13, kVK_PC_ScrollLock = kVK_F14, kVK_PC_Pause = kVK_F15, kVK_PC_Insert = kVK_Help, kVK_PC_Backspace = kVK_Delete, kVK_PC_Delete = kVK_ForwardDelete, kVK_PC_ContextMenu = 0x6E, kVK_Powerbook_KeypadEnter = 0x34 // Enter on Powerbook's keyboard is different }; /** * TISInputSourceWrapper is a wrapper for the TISInputSourceRef. If we get the * TISInputSourceRef from InputSourceID, we need to release the CFArray instance * which is returned by TISCreateInputSourceList. However, when we release the * list, we cannot access the TISInputSourceRef. So, it's not usable, and it * may cause the memory leak bugs. nsTISInputSource automatically releases the * list when the instance is destroyed. */ class TISInputSourceWrapper { public: static TISInputSourceWrapper& CurrentInputSource(); /** * Shutdown() should be called when nobody doesn't need to use this class. */ static void Shutdown(); TISInputSourceWrapper() { mInputSourceList = nullptr; Clear(); } explicit TISInputSourceWrapper(const char* aID) { mInputSourceList = nullptr; InitByInputSourceID(aID); } explicit TISInputSourceWrapper(SInt32 aLayoutID) { mInputSourceList = nullptr; InitByLayoutID(aLayoutID); } explicit TISInputSourceWrapper(TISInputSourceRef aInputSource) { mInputSourceList = nullptr; InitByTISInputSourceRef(aInputSource); } ~TISInputSourceWrapper() { Clear(); } void InitByInputSourceID(const char* aID); void InitByInputSourceID(const nsAFlatString &aID); void InitByInputSourceID(const CFStringRef aID); /** * InitByLayoutID() initializes the keyboard layout by the layout ID. * * @param aLayoutID An ID of keyboard layout. * 0: US * 1: Greek * 2: German * 3: Swedish-Pro * 4: Dvorak-Qwerty Cmd * 5: Thai * 6: Arabic * 7: French * 8: Hebrew * 9: Lithuanian * 10: Norwegian * 11: Spanish * @param aOverrideKeyboard When testing set to TRUE, otherwise, set to * FALSE. When TRUE, we use an ANSI keyboard * instead of the actual keyboard. */ void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false); void InitByCurrentInputSource(); void InitByCurrentKeyboardLayout(); void InitByCurrentASCIICapableInputSource(); void InitByCurrentASCIICapableKeyboardLayout(); void InitByCurrentInputMethodKeyboardLayoutOverride(); void InitByTISInputSourceRef(TISInputSourceRef aInputSource); void InitByLanguage(CFStringRef aLanguage); /** * If the instance is initialized with a keyboard layout input source, * returns it. * If the instance is initialized with an IME mode input source, the result * references the keyboard layout for the IME mode. However, this can be * initialized only when the IME mode is actually selected. I.e, if IME mode * input source is initialized with LayoutID or SourceID, this returns null. */ TISInputSourceRef GetKeyboardLayoutInputSource() const { return mKeyboardLayout; } const UCKeyboardLayout* GetUCKeyboardLayout(); bool IsOpenedIMEMode(); bool IsIMEMode(); bool IsKeyboardLayout(); bool IsASCIICapable() { NS_ENSURE_TRUE(mInputSource, false); return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable); } bool IsEnabled() { NS_ENSURE_TRUE(mInputSource, false); return GetBoolProperty(kTISPropertyInputSourceIsEnabled); } bool GetLanguageList(CFArrayRef &aLanguageList); bool GetPrimaryLanguage(CFStringRef &aPrimaryLanguage); bool GetPrimaryLanguage(nsAString &aPrimaryLanguage); bool GetLocalizedName(CFStringRef &aName) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyLocalizedName, aName); } bool GetLocalizedName(nsAString &aName) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyLocalizedName, aName); } bool GetInputSourceID(CFStringRef &aID) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyInputSourceID, aID); } bool GetInputSourceID(nsAString &aID) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyInputSourceID, aID); } bool GetBundleID(CFStringRef &aBundleID) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyBundleID, aBundleID); } bool GetBundleID(nsAString &aBundleID) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyBundleID, aBundleID); } bool GetInputSourceType(CFStringRef &aType) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyInputSourceType, aType); } bool GetInputSourceType(nsAString &aType) { NS_ENSURE_TRUE(mInputSource, false); return GetStringProperty(kTISPropertyInputSourceType, aType); } bool IsForRTLLanguage(); bool IsInitializedByCurrentInputSource(); enum { // 40 is an actual result of the ::LMGetKbdType() when we connect an // unknown keyboard and set the keyboard type to ANSI manually on the // set up dialog. eKbdType_ANSI = 40 }; void Select(); void Clear(); /** * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent. * * @param aNativeKeyEvent A native key event for which you want to * dispatch a Gecko key event. * @param aKeyEvent The result -- a Gecko key event initialized * from the native key event. * @param aInsertString If caller expects that the event will cause * a character to be input (say in an editor), * the caller should set this. Otherwise, * if caller sets null to this, this method will * compute the character to be input from * characters of aNativeKeyEvent. */ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent, const nsAString *aInsertString = nullptr); /** * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and * recompute aKeyEvent.mCharCode if it's necessary. * * @param aNativeKeyEvent A native key event for which you want to * dispatch a Gecko key event. * @param aInsertString If caller expects that the event will cause * a character to be input (say in an editor), * the caller should set this. Otherwise, * if caller sets null to this, this method will * compute the character to be input from * characters of aNativeKeyEvent. * @param aKeyEvent The result -- a Gecko key event initialized * from the native key event. This must be * eKeyPress event. */ void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent, const nsAString* aInsertString, WidgetKeyboardEvent& aKeyEvent); /** * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current * keyboard layout. * * @param aNativeKeyCode A native keycode. * @param aKbType A native Keyboard Type value. Typically, * this is a result of ::LMGetKbdType(). * @param aCmdIsPressed TRUE if Cmd key is pressed. Otherwise, FALSE. * @return The computed Gecko keycode. */ uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType, bool aCmdIsPressed); /** * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key. * * @param aNativeKeyCode A native keycode. */ static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode); /** * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key. * * @param aNativeKeyCode A native keycode. */ static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode); protected: /** * TranslateToString() computes the inputted text from the native keyCode, * modifier flags and keyboard type. * * @param aKeyCode A native keyCode. * @param aModifiers Combination of native modifier flags. * @param aKbType A native Keyboard Type value. Typically, * this is a result of ::LMGetKbdType(). * @param aStr Result, i.e., inputted text. * The result can be two or more characters. * @return If succeeded, TRUE. Otherwise, FALSE. * Even if TRUE, aStr can be empty string. */ bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType, nsAString &aStr); /** * TranslateToChar() computes the inputted character from the native keyCode, * modifier flags and keyboard type. If two or more characters would be * input, this returns 0. * * @param aKeyCode A native keyCode. * @param aModifiers Combination of native modifier flags. * @param aKbType A native Keyboard Type value. Typically, * this is a result of ::LMGetKbdType(). * @return If succeeded and the result is one character, * returns the charCode of it. Otherwise, * returns 0. */ uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType); /** * ComputeInsertString() computes string to be inserted with the key event. * * @param aNativeKeyEvent The native key event which causes our keyboard * event(s). * @param aKeyEvent A Gecko key event which was partially * initialized with aNativeKeyEvent. * @param aInsertString The string to be inputting by aNativeKeyEvent. * This should be specified by InsertText(). * In other words, if the key event doesn't cause * a call of InsertText(), this can be nullptr. * @param aResult The string which should be set to charCode of * keypress event(s). */ void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent, const WidgetKeyboardEvent& aKeyEvent, const nsAString* aInsertString, nsAString& aResult); /** * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by * a printable key. Otherwise, returns false. */ bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const; /** * GetKbdType() returns physical keyboard type. */ UInt32 GetKbdType() const; bool GetBoolProperty(const CFStringRef aKey); bool GetStringProperty(const CFStringRef aKey, CFStringRef &aStr); bool GetStringProperty(const CFStringRef aKey, nsAString &aStr); TISInputSourceRef mInputSource; TISInputSourceRef mKeyboardLayout; CFArrayRef mInputSourceList; const UCKeyboardLayout* mUCKeyboardLayout; int8_t mIsRTL; bool mOverrideKeyboard; static TISInputSourceWrapper* sCurrentInputSource; }; /** * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler. * Utility methods should be implemented this level. */ class TextInputHandlerBase : public TextEventDispatcherListener { public: /** * Other TextEventDispatcherListener methods should be implemented in * IMEInputHandler. */ NS_DECL_ISUPPORTS /** * DispatchEvent() dispatches aEvent on mWidget. * * @param aEvent An event which you want to dispatch. * @return TRUE if the event is consumed by web contents * or chrome contents. Otherwise, FALSE. */ bool DispatchEvent(WidgetGUIEvent& aEvent); /** * SetSelection() dispatches eSetSelection event for the aRange. * * @param aRange The range which will be selected. * @return TRUE if setting selection is succeeded and * the widget hasn't been destroyed. * Otherwise, FALSE. */ bool SetSelection(NSRange& aRange); /** * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent. * * @param aNativeKeyEvent A native key event for which you want to * dispatch a Gecko key event. * @param aKeyEvent The result -- a Gecko key event initialized * from the native key event. * @param aInsertString If caller expects that the event will cause * a character to be input (say in an editor), * the caller should set this. Otherwise, * if caller sets null to this, this method will * compute the character to be input from * characters of aNativeKeyEvent. */ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent, const nsAString *aInsertString = nullptr); /** * SynthesizeNativeKeyEvent() is an implementation of * nsIWidget::SynthesizeNativeKeyEvent(). See the document in nsIWidget.h * for the detail. */ nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, uint32_t aModifierFlags, const nsAString& aCharacters, const nsAString& aUnmodifiedCharacters); /** * Utility method intended for testing. Attempts to construct a native key * event that would have been generated during an actual key press. This * *does not dispatch* the native event. Instead, it is attached to the * |mNativeKeyEvent| field of the Gecko event that is passed in. * @param aKeyEvent Gecko key event to attach the native event to */ NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent); /** * GetWindowLevel() returns the window level of current focused (in Gecko) * window. E.g., if an element in XUL panel has focus, this returns * the XUL panel's window level. */ NSInteger GetWindowLevel(); /** * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special * Gecko keyCode. A key is "special" if it isn't used for text input. * * @param aNativeKeyCode A native keycode. * @return If the keycode is mapped to a special key, * TRUE. Otherwise, FALSE. */ static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode); /** * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon * Event Manager APIs with the same names. In addition they keep track of * how many times we've called them (in the same process) -- unlike the * Carbon Event Manager APIs, which only keep track of how many times they've * been called from any and all processes. * * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether * secure event input mode is enabled (in any process). This class's * IsSecureEventInputEnabled() returns whether we've made any calls to * EnableSecureEventInput() that are not (yet) offset by the calls we've * made to DisableSecureEventInput(). */ static void EnableSecureEventInput(); static void DisableSecureEventInput(); static bool IsSecureEventInputEnabled(); /** * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until * our call count becomes 0. */ static void EnsureSecureEventInputDisabled(); public: /** * mWidget must not be destroyed without OnDestroyWidget being called. * * @param aDestroyingWidget Destroying widget. This might not be mWidget. * @return This result doesn't have any meaning for * callers. When aDstroyingWidget isn't the same * as mWidget, FALSE. Then, inherited methods in * sub classes should return from this method * without cleaning up. */ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget); protected: // The creator of this instance, client and its text event dispatcher. // These members must not be nullptr after initialized until // OnDestroyWidget() is called. nsChildView* mWidget; // [WEAK] RefPtr mDispatcher; // The native view for mWidget. // This view handles the actual text inputting. NSView* mView; // [STRONG] TextInputHandlerBase(nsChildView* aWidget, NSView *aNativeView); virtual ~TextInputHandlerBase(); bool Destroyed() { return !mWidget; } /** * mCurrentKeyEvent indicates what key event we are handling. While * handling a native keydown event, we need to store the event for insertText, * doCommandBySelector and various action message handlers of NSResponder * such as [NSResponder insertNewline:sender]. */ struct KeyEventState { // Handling native key event NSEvent* mKeyEvent; // String specified by InsertText(). This is not null only during a // call of InsertText(). nsAString* mInsertString; // String which are included in [mKeyEvent characters] and already handled // by InsertText() call(s). nsString mInsertedString; // Whether keydown event was consumed by web contents or chrome contents. bool mKeyDownHandled; // Whether keypress event was dispatched for mKeyEvent. bool mKeyPressDispatched; // Whether keypress event was consumed by web contents or chrome contents. bool mKeyPressHandled; // Whether the key event causes other key events via IME or something. bool mCausedOtherKeyEvents; // Whether the key event causes composition change or committing // composition. So, even if InsertText() is called, this may be false // if it dispatches keypress event. bool mCompositionDispatched; KeyEventState() : mKeyEvent(nullptr) { Clear(); } explicit KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nullptr) { Clear(); Set(aNativeKeyEvent); } KeyEventState(const KeyEventState &aOther) = delete; ~KeyEventState() { Clear(); } void Set(NSEvent* aNativeKeyEvent) { NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL"); Clear(); mKeyEvent = [aNativeKeyEvent retain]; } void Clear() { if (mKeyEvent) { [mKeyEvent release]; mKeyEvent = nullptr; } mInsertString = nullptr; mInsertedString.Truncate(); mKeyDownHandled = false; mKeyPressDispatched = false; mKeyPressHandled = false; mCausedOtherKeyEvents = false; mCompositionDispatched = false; } bool IsDefaultPrevented() const { return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents || mCompositionDispatched; } bool CanDispatchKeyPressEvent() const { return !mKeyPressDispatched && !IsDefaultPrevented(); } void InitKeyEvent(TextInputHandlerBase* aHandler, WidgetKeyboardEvent& aKeyEvent); /** * GetUnhandledString() returns characters of the event which have not been * handled with InsertText() yet. For example, if there is a composition * caused by a dead key press like '`' and it's committed by some key * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters| * is |`v|. Then, after |`| is committed with a call of InsertString(), * this returns only 'v'. */ void GetUnhandledString(nsAString& aUnhandledString) const; }; /** * Helper classes for guaranteeing cleaning mCurrentKeyEvent */ class AutoKeyEventStateCleaner { public: explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) : mHandler(aHandler) { } ~AutoKeyEventStateCleaner() { mHandler->RemoveCurrentKeyEvent(); } private: RefPtr mHandler; }; class MOZ_STACK_CLASS AutoInsertStringClearer { public: explicit AutoInsertStringClearer(KeyEventState* aState) : mState(aState) { } ~AutoInsertStringClearer(); private: KeyEventState* mState; }; /** * mCurrentKeyEvents stores all key events which are being processed. * When we call interpretKeyEvents, IME may generate other key events. * mCurrentKeyEvents[0] is the latest key event. */ nsTArray mCurrentKeyEvents; /** * mFirstKeyEvent must be used for first key event. This member prevents * memory fragmentation for most key events. */ KeyEventState mFirstKeyEvent; /** * PushKeyEvent() adds the current key event to mCurrentKeyEvents. */ KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent) { uint32_t nestCount = mCurrentKeyEvents.Length(); for (uint32_t i = 0; i < nestCount; i++) { // When the key event is caused by another key event, all key events // which are being handled should be marked as "consumed". mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true; } KeyEventState* keyEvent = nullptr; if (nestCount == 0) { mFirstKeyEvent.Set(aNativeKeyEvent); keyEvent = &mFirstKeyEvent; } else { keyEvent = new KeyEventState(aNativeKeyEvent); } return *mCurrentKeyEvents.AppendElement(keyEvent); } /** * RemoveCurrentKeyEvent() removes the current key event from * mCurrentKeyEvents. */ void RemoveCurrentKeyEvent() { NS_ASSERTION(mCurrentKeyEvents.Length() > 0, "RemoveCurrentKeyEvent() is called unexpectedly"); KeyEventState* keyEvent = GetCurrentKeyEvent(); mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1); if (keyEvent == &mFirstKeyEvent) { keyEvent->Clear(); } else { delete keyEvent; } } /** * GetCurrentKeyEvent() returns current processing key event. */ KeyEventState* GetCurrentKeyEvent() { if (mCurrentKeyEvents.Length() == 0) { return nullptr; } return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1]; } struct KeyboardLayoutOverride final { int32_t mKeyboardLayout; bool mOverrideEnabled; KeyboardLayoutOverride() : mKeyboardLayout(0), mOverrideEnabled(false) { } }; const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const { return mKeyboardOverride; } /** * IsPrintableChar() checks whether the unicode character is * a non-printable ASCII character or not. Note that this returns * TRUE even if aChar is a non-printable UNICODE character. * * @param aChar A unicode character. * @return TRUE if aChar is a printable ASCII character * or a unicode character. Otherwise, i.e, * if aChar is a non-printable ASCII character, * FALSE. */ static bool IsPrintableChar(char16_t aChar); /** * IsNormalCharInputtingEvent() checks whether aKeyEvent causes text input. * * @param aKeyEvent A key event. * @return TRUE if the key event causes text input. * Otherwise, FALSE. */ static bool IsNormalCharInputtingEvent(const WidgetKeyboardEvent& aKeyEvent); /** * IsModifierKey() checks whether the native keyCode is for a modifier key. * * @param aNativeKeyCode A native keyCode. * @return TRUE if aNativeKeyCode is for a modifier key. * Otherwise, FALSE. */ static bool IsModifierKey(UInt32 aNativeKeyCode); private: KeyboardLayoutOverride mKeyboardOverride; static int32_t sSecureEventInputCount; }; /** * IMEInputHandler manages: * 1. The IME/keyboard layout statement of nsChildView. * 2. The IME composition statement of nsChildView. * And also provides the methods which controls the current IME transaction of * the instance. * * Note that an nsChildView handles one or more NSView's events. E.g., even if * a text editor on XUL panel element, the input events handled on the parent * (or its ancestor) widget handles it (the native focus is set to it). The * actual focused view is notified by OnFocusChangeInGecko. */ class IMEInputHandler : public TextInputHandlerBase { public: // TextEventDispatcherListener methods NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, const IMENotification& aNotification) override; NS_IMETHOD_(void) OnRemovedFrom( TextEventDispatcher* aTextEventDispatcher) override; NS_IMETHOD_(void) WillDispatchKeyboardEvent( TextEventDispatcher* aTextEventDispatcher, WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, void* aData) override; public: virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override; virtual void OnFocusChangeInGecko(bool aFocus); void OnSelectionChange(const IMENotification& aIMENotification); /** * Call [NSTextInputContext handleEvent] for mouse event support of IME */ bool OnHandleEvent(NSEvent* aEvent); /** * SetMarkedText() is a handler of setMarkedText of NSTextInput. * * @param aAttrString This mut be an instance of NSAttributedString. * If the aString parameter to * [ChildView setMarkedText:setSelectedRange:] * isn't an instance of NSAttributedString, * create an NSAttributedString from it and pass * that instead. * @param aSelectedRange Current selected range (or caret position). * @param aReplacementRange The range which will be replaced with the * aAttrString instead of current marked range. */ void SetMarkedText(NSAttributedString* aAttrString, NSRange& aSelectedRange, NSRange* aReplacementRange = nullptr); /** * GetAttributedSubstringFromRange() returns an NSAttributedString instance * which is allocated as autorelease for aRange. * * @param aRange The range of string which you want. * @param aActualRange The actual range of the result. * @return The string in aRange. If the string is empty, * this returns nil. If succeeded, this returns * an instance which is allocated as autorelease. * If this has some troubles, returns nil. */ NSAttributedString* GetAttributedSubstringFromRange( NSRange& aRange, NSRange* aActualRange = nullptr); /** * SelectedRange() returns current selected range. * * @return If an editor has focus, this returns selection * range in the editor. Otherwise, this returns * selection range in the focused document. */ NSRange SelectedRange(); /** * DrawsVerticallyForCharacterAtIndex() returns whether the character at * the given index is being rendered vertically. * * @param aCharIndex The character offset to query. * * @return True if writing-mode is vertical at the given * character offset; otherwise false. */ bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex); /** * FirstRectForCharacterRange() returns first *character* rect in the range. * Cocoa needs the first line rect in the range, but we cannot compute it * on current implementation. * * @param aRange A range of text to examine. Its position is * an offset from the beginning of the focused * editor or document. * @param aActualRange If this is not null, this returns the actual * range used for computing the result. * @return An NSRect containing the first character in * aRange, in screen coordinates. * If the length of aRange is 0, the width will * be 0. */ NSRect FirstRectForCharacterRange(NSRange& aRange, NSRange* aActualRange = nullptr); /** * CharacterIndexForPoint() returns an offset of a character at aPoint. * XXX This isn't implemented, always returns 0. * * @param The point in screen coordinates. * @return The offset of the character at aPoint from * the beginning of the focused editor or * document. */ NSUInteger CharacterIndexForPoint(NSPoint& aPoint); /** * GetValidAttributesForMarkedText() returns attributes which we support. * * @return Always empty array for now. */ NSArray* GetValidAttributesForMarkedText(); bool HasMarkedText(); NSRange MarkedRange(); bool IsIMEComposing() { return mIsIMEComposing; } bool IsIMEOpened(); bool IsIMEEnabled() { return mIsIMEEnabled; } bool IsASCIICapableOnly() { return mIsASCIICapableOnly; } bool IgnoreIMECommit() { return mIgnoreIMECommit; } bool IgnoreIMEComposition() { // Ignore the IME composition events when we're pending to discard the // composition and we are not to handle the IME composition now. return (mPendingMethods & kDiscardIMEComposition) && (mIsInFocusProcessing || !IsFocused()); } void CommitIMEComposition(); void CancelIMEComposition(); void EnableIME(bool aEnableIME); void SetIMEOpenState(bool aOpen); void SetASCIICapableOnly(bool aASCIICapableOnly); /** * True if OSX believes that our view has keyboard focus. */ bool IsFocused(); static CFArrayRef CreateAllIMEModeList(); static void DebugPrintAllIMEModes(); // Don't use ::TSMGetActiveDocument() API directly, the document may not // be what you want. static TSMDocumentID GetCurrentTSMDocumentID(); protected: // We cannot do some jobs in the given stack by some reasons. // Following flags and the timer provide the execution pending mechanism, // See the comment in nsCocoaTextInputHandler.mm. nsCOMPtr mTimer; enum { kNotifyIMEOfFocusChangeInGecko = 1, kDiscardIMEComposition = 2, kSyncASCIICapableOnly = 4 }; uint32_t mPendingMethods; IMEInputHandler(nsChildView* aWidget, NSView *aNativeView); virtual ~IMEInputHandler(); void ResetTimer(); virtual void ExecutePendingMethods(); /** * InsertTextAsCommittingComposition() commits current composition. If there * is no composition, this starts a composition and commits it immediately. * * @param aAttrString A string which is committed. * @param aReplacementRange The range which will be replaced with the * aAttrString instead of current selection. */ void InsertTextAsCommittingComposition(NSAttributedString* aAttrString, NSRange* aReplacementRange); private: // If mIsIMEComposing is true, the composition string is stored here. NSString* mIMECompositionString; // If mIsIMEComposing is true, the start offset of the composition string. uint32_t mIMECompositionStart; NSRange mMarkedRange; NSRange mSelectedRange; NSRange mRangeForWritingMode; // range within which mWritingMode applies mozilla::WritingMode mWritingMode; bool mIsIMEComposing; bool mIsIMEEnabled; bool mIsASCIICapableOnly; bool mIgnoreIMECommit; // This flag is enabled by OnFocusChangeInGecko, and will be cleared by // ExecutePendingMethods. When this is true, IsFocus() returns TRUE. At // that time, the focus processing in Gecko might not be finished yet. So, // you cannot use WidgetQueryContentEvent or something. bool mIsInFocusProcessing; bool mIMEHasFocus; void KillIMEComposition(); void SendCommittedText(NSString *aString); void OpenSystemPreferredLanguageIME(); // Pending methods void NotifyIMEOfFocusChangeInGecko(); void DiscardIMEComposition(); void SyncASCIICapableOnly(); static bool sStaticMembersInitialized; static CFStringRef sLatestIMEOpenedModeInputSourceID; static void InitStaticMembers(); static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter, void* aObserver, CFStringRef aName, const void* aObject, CFDictionaryRef aUserInfo); static void FlushPendingMethods(nsITimer* aTimer, void* aClosure); /** * ConvertToTextRangeStyle converts the given native underline style to * our defined text range type. * * @param aUnderlineStyle NSUnderlineStyleSingle or * NSUnderlineStyleThick. * @param aSelectedRange Current selected range (or caret position). * @return NS_TEXTRANGE_*. */ TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle, NSRange& aSelectedRange); /** * GetRangeCount() computes the range count of aAttrString. * * @param aAttrString An NSAttributedString instance whose number of * NSUnderlineStyleAttributeName ranges you with * to know. * @return The count of NSUnderlineStyleAttributeName * ranges in aAttrString. */ uint32_t GetRangeCount(NSAttributedString *aString); /** * CreateTextRangeArray() returns text ranges for clauses and/or caret. * * @param aAttrString An NSAttributedString instance which indicates * current composition string. * @param aSelectedRange Current selected range (or caret position). * @return The result is set to the * NSUnderlineStyleAttributeName ranges in * aAttrString. */ already_AddRefed CreateTextRangeArray(NSAttributedString *aAttrString, NSRange& aSelectedRange); /** * DispatchCompositionStartEvent() dispatches a compositionstart event and * initializes the members indicating composition state. * * @return true if it can continues handling composition. * Otherwise, e.g., canceled by the web page, * this returns false. */ bool DispatchCompositionStartEvent(); /** * DispatchCompositionChangeEvent() dispatches a compositionchange event on * mWidget and modifies the members indicating composition state. * * @param aText User text input. * @param aAttrString An NSAttributedString instance which indicates * current composition string. * @param aSelectedRange Current selected range (or caret position). * * @return true if it can continues handling composition. * Otherwise, e.g., canceled by the web page, * this returns false. */ bool DispatchCompositionChangeEvent(const nsString& aText, NSAttributedString* aAttrString, NSRange& aSelectedRange); /** * DispatchCompositionCommitEvent() dispatches a compositioncommit event or * compositioncommitasis event. If aCommitString is null, dispatches * compositioncommitasis event. I.e., if aCommitString is null, this * commits the composition with the last data. Otherwise, commits the * composition with aCommitString value. * * @return true if the widget isn't destroyed. * Otherwise, false. */ bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr); // The focused IME handler. Please note that the handler might lost the // actual focus by deactivating the application. If we are active, this // must have the actual focused handle. // We cannot access to the NSInputManager during we aren't active, so, the // focused handler can have an IME transaction even if we are deactive. static IMEInputHandler* sFocusedIMEHandler; static bool sCachedIsForRTLLangage; }; /** * TextInputHandler implements the NSTextInput protocol. */ class TextInputHandler : public IMEInputHandler { public: static NSUInteger sLastModifierState; static CFArrayRef CreateAllKeyboardLayoutList(); static void DebugPrintAllKeyboardLayouts(); TextInputHandler(nsChildView* aWidget, NSView *aNativeView); virtual ~TextInputHandler(); /** * KeyDown event handler. * * @param aNativeEvent A native NSKeyDown event. * @return TRUE if the event is consumed by web contents * or chrome contents. Otherwise, FALSE. */ bool HandleKeyDownEvent(NSEvent* aNativeEvent); /** * KeyUp event handler. * * @param aNativeEvent A native NSKeyUp event. */ void HandleKeyUpEvent(NSEvent* aNativeEvent); /** * FlagsChanged event handler. * * @param aNativeEvent A native NSFlagsChanged event. */ void HandleFlagsChanged(NSEvent* aNativeEvent); /** * Insert the string to content. I.e., this is a text input event handler. * If this is called during keydown event handling, this may dispatch a * eKeyPress event. If this is called during composition, this commits * the composition by the aAttrString. * * @param aAttrString An inserted string. * @param aReplacementRange The range which will be replaced with the * aAttrString instead of current selection. */ void InsertText(NSAttributedString *aAttrString, NSRange* aReplacementRange = nullptr); /** * doCommandBySelector event handler. * * @param aSelector A selector of the command. * @return TRUE if the command is consumed. Otherwise, * FALSE. */ bool DoCommandBySelector(const char* aSelector); /** * KeyPressWasHandled() checks whether keypress event was handled or not. * * @return TRUE if keypress event for latest native key * event was handled. Otherwise, FALSE. * If this handler isn't handling any key events, * always returns FALSE. */ bool KeyPressWasHandled() { KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); return currentKeyEvent && currentKeyEvent->mKeyPressHandled; } protected: // Stores the association of device dependent modifier flags with a modifier // keyCode. Being device dependent, this association may differ from one kind // of hardware to the next. struct ModifierKey { NSUInteger flags; unsigned short keyCode; ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) : flags(aFlags), keyCode(aKeyCode) { } NSUInteger GetDeviceDependentFlags() const { return (flags & ~NSDeviceIndependentModifierFlagsMask); } NSUInteger GetDeviceIndependentFlags() const { return (flags & NSDeviceIndependentModifierFlagsMask); } }; typedef nsTArray ModifierKeyArray; ModifierKeyArray mModifierKeys; /** * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for * the key. */ const ModifierKey* GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const; /** * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for * the device dependent flags. */ const ModifierKey* GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const; /** * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event * for the aNativeEvent. * * @param aNativeEvent A native flagschanged event which you want to * dispatch our key event for. * @param aDispatchKeyDown TRUE if you want to dispatch a keydown event. * Otherwise, i.e., to dispatch keyup event, * FALSE. */ void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent, bool aDispatchKeyDown); }; } // namespace widget } // namespace mozilla #endif // TextInputHandler_h_