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: #ifndef nsIMM32Handler_h__ michael@0: #define nsIMM32Handler_h__ michael@0: michael@0: #include "nscore.h" michael@0: #include michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsIWidget.h" michael@0: #include "mozilla/EventForwards.h" michael@0: michael@0: class nsWindow; michael@0: struct nsIntRect; michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: struct MSGResult; michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla michael@0: michael@0: class nsIMEContext michael@0: { michael@0: public: michael@0: nsIMEContext(HWND aWnd) : mWnd(aWnd) michael@0: { michael@0: mIMC = ::ImmGetContext(mWnd); michael@0: } michael@0: michael@0: ~nsIMEContext() michael@0: { michael@0: if (mIMC) { michael@0: ::ImmReleaseContext(mWnd, mIMC); michael@0: mIMC = nullptr; michael@0: } michael@0: } michael@0: michael@0: HIMC get() const michael@0: { michael@0: return mIMC; michael@0: } michael@0: michael@0: bool IsValid() const michael@0: { michael@0: return !!mIMC; michael@0: } michael@0: michael@0: void SetOpenState(bool aOpen) const michael@0: { michael@0: if (!mIMC) { michael@0: return; michael@0: } michael@0: ::ImmSetOpenStatus(mIMC, aOpen); michael@0: } michael@0: michael@0: bool GetOpenState() const michael@0: { michael@0: if (!mIMC) { michael@0: return false; michael@0: } michael@0: return (::ImmGetOpenStatus(mIMC) != FALSE); michael@0: } michael@0: michael@0: bool AssociateDefaultContext() michael@0: { michael@0: // We assume that there is only default IMC, no new IMC has been created. michael@0: if (mIMC) { michael@0: return false; michael@0: } michael@0: if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) { michael@0: return false; michael@0: } michael@0: mIMC = ::ImmGetContext(mWnd); michael@0: return (mIMC != nullptr); michael@0: } michael@0: michael@0: bool Disassociate() michael@0: { michael@0: if (!mIMC) { michael@0: return false; michael@0: } michael@0: if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) { michael@0: return false; michael@0: } michael@0: ::ImmReleaseContext(mWnd, mIMC); michael@0: mIMC = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: protected: michael@0: nsIMEContext() michael@0: { michael@0: NS_ERROR("Don't create nsIMEContext without window handle"); michael@0: } michael@0: michael@0: nsIMEContext(const nsIMEContext &aSrc) : mWnd(nullptr), mIMC(nullptr) michael@0: { michael@0: NS_ERROR("Don't copy nsIMEContext"); michael@0: } michael@0: michael@0: HWND mWnd; michael@0: HIMC mIMC; michael@0: }; michael@0: michael@0: class nsIMM32Handler michael@0: { michael@0: typedef mozilla::widget::MSGResult MSGResult; michael@0: public: michael@0: static void Initialize(); michael@0: static void Terminate(); michael@0: michael@0: // If Process*() returns true, the caller shouldn't do anything anymore. michael@0: static bool ProcessMessage(nsWindow* aWindow, UINT msg, michael@0: WPARAM& wParam, LPARAM& lParam, michael@0: MSGResult& aResult); michael@0: static bool IsComposing() michael@0: { michael@0: return IsComposingOnOurEditor() || IsComposingOnPlugin(); michael@0: } michael@0: static bool IsComposingOn(nsWindow* aWindow) michael@0: { michael@0: return IsComposing() && IsComposingWindow(aWindow); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: /** michael@0: * IsIMEAvailable() returns TRUE when current keyboard layout has IME. michael@0: * Otherwise, FALSE. michael@0: */ michael@0: static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); } michael@0: #endif michael@0: michael@0: // If aForce is TRUE, these methods doesn't check whether we have composition michael@0: // or not. If you don't set it to TRUE, these method doesn't commit/cancel michael@0: // the composition on uexpected window. michael@0: static void CommitComposition(nsWindow* aWindow, bool aForce = false); michael@0: static void CancelComposition(nsWindow* aWindow, bool aForce = false); michael@0: static void OnUpdateComposition(nsWindow* aWindow); michael@0: michael@0: static nsIMEUpdatePreference GetIMEUpdatePreference(); michael@0: michael@0: protected: michael@0: static void EnsureHandlerInstance(); michael@0: michael@0: static bool IsComposingOnOurEditor(); michael@0: static bool IsComposingOnPlugin(); michael@0: static bool IsComposingWindow(nsWindow* aWindow); michael@0: michael@0: static bool ShouldDrawCompositionStringOurselves(); michael@0: static void InitKeyboardLayout(HKL aKeyboardLayout); michael@0: static UINT GetKeyboardCodePage(); michael@0: michael@0: /** michael@0: * Checks whether the window is top level window of the composing window. michael@0: * In this method, the top level window means in all windows, not only in all michael@0: * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. michael@0: */ michael@0: static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); michael@0: michael@0: static bool ProcessInputLangChangeMessage(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult); michael@0: static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg, michael@0: WPARAM &wParam, LPARAM &lParam, michael@0: MSGResult& aResult); michael@0: michael@0: nsIMM32Handler(); michael@0: ~nsIMM32Handler(); michael@0: michael@0: // On*() methods return true if the caller of message handler shouldn't do michael@0: // anything anymore. Otherwise, false. michael@0: bool OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction, michael@0: MSGResult& aResult); michael@0: static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: michael@0: bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult); michael@0: bool OnIMEStartCompositionOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult); michael@0: bool OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, michael@0: LPARAM lParam, MSGResult& aResult); michael@0: bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: bool OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: michael@0: // These message handlers don't use instance members, we should not create michael@0: // the instance by the messages. So, they should be static. michael@0: static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: static bool OnIMESetContextOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult); michael@0: static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult); michael@0: michael@0: // The result of Handle* method mean "Processed" when it's TRUE. michael@0: void HandleStartComposition(nsWindow* aWindow, michael@0: const nsIMEContext &aIMEContext); michael@0: bool HandleComposition(nsWindow* aWindow, const nsIMEContext &aIMEContext, michael@0: LPARAM lParam); michael@0: void HandleEndComposition(nsWindow* aWindow); michael@0: bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); michael@0: bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam, michael@0: LRESULT *oResult); michael@0: bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); michael@0: michael@0: /** michael@0: * When a window's IME context is activating but we have composition on michael@0: * another window, we should commit our composition because IME context is michael@0: * shared by all our windows (including plug-ins). michael@0: * @param aWindow is a new activated window. michael@0: * If aWindow is our composing window, this method does nothing. michael@0: * Otherwise, this commits the composition on the previous window. michael@0: * If this method did commit a composition, this returns TRUE. michael@0: */ michael@0: bool CommitCompositionOnPreviousWindow(nsWindow* aWindow); michael@0: michael@0: /** michael@0: * ResolveIMECaretPos michael@0: * Convert the caret rect of a composition event to another widget's michael@0: * coordinate system. michael@0: * michael@0: * @param aReferenceWidget The origin widget of aCursorRect. michael@0: * Typically, this is mReferenceWidget of the michael@0: * composing events. If the aCursorRect is in screen michael@0: * coordinates, set nullptr. michael@0: * @param aCursorRect The cursor rect. michael@0: * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If michael@0: * this is nullptr, aOutRect will be in screen michael@0: * coordinates. michael@0: * @param aOutRect The converted cursor rect. michael@0: */ michael@0: void ResolveIMECaretPos(nsIWidget* aReferenceWidget, michael@0: nsIntRect& aCursorRect, michael@0: nsIWidget* aNewOriginWidget, michael@0: nsIntRect& aOutRect); michael@0: michael@0: bool ConvertToANSIString(const nsAFlatString& aStr, michael@0: UINT aCodePage, michael@0: nsACString& aANSIStr); michael@0: michael@0: bool SetIMERelatedWindowsPos(nsWindow* aWindow, michael@0: const nsIMEContext& aIMEContext); michael@0: void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, michael@0: const nsIMEContext& aIMEContext); michael@0: bool GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, michael@0: uint32_t aOffset, michael@0: nsIntRect &aCharRect); michael@0: bool GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect); michael@0: void GetCompositionString(const nsIMEContext &aIMEContext, DWORD aIndex); michael@0: /** michael@0: * Get the current target clause of composition string. michael@0: * If there are one or more characters whose attribute is ATTR_TARGET_*, michael@0: * this returns the first character's offset and its length. michael@0: * Otherwise, e.g., the all characters are ATTR_INPUT, this returns michael@0: * the composition string range because the all is the current target. michael@0: * michael@0: * aLength can be null (default), but aOffset must not be null. michael@0: * michael@0: * The aOffset value is offset in the contents. So, when you need offset michael@0: * in the composition string, you need to subtract mCompositionStart from it. michael@0: */ michael@0: bool GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength = nullptr); michael@0: void DispatchTextEvent(nsWindow* aWindow, const nsIMEContext &aIMEContext, michael@0: bool aCheckAttr = true); michael@0: already_AddRefed CreateTextRangeArray(); michael@0: michael@0: nsresult EnsureClauseArray(int32_t aCount); michael@0: nsresult EnsureAttributeArray(int32_t aCount); michael@0: michael@0: /** michael@0: * When WM_IME_CHAR is received and passed to DefWindowProc, we need to michael@0: * record the messages. In other words, we should record the messages michael@0: * when we receive WM_IME_CHAR on windowless plug-in (if we have focus, michael@0: * we always eat them). When focus is moved from a windowless plug-in to michael@0: * our window during composition, WM_IME_CHAR messages were received when michael@0: * the plug-in has focus. However, WM_CHAR messages are received after the michael@0: * plug-in lost focus. So, we need to ignore the WM_CHAR messages because michael@0: * they make unexpected text input events on us. michael@0: */ michael@0: nsTArray mPassedIMEChar; michael@0: michael@0: bool IsIMECharRecordsEmpty() michael@0: { michael@0: return mPassedIMEChar.IsEmpty(); michael@0: } michael@0: void ResetIMECharRecords() michael@0: { michael@0: mPassedIMEChar.Clear(); michael@0: } michael@0: void DequeueIMECharRecords(WPARAM &wParam, LPARAM &lParam) michael@0: { michael@0: MSG msg = mPassedIMEChar.ElementAt(0); michael@0: wParam = msg.wParam; michael@0: lParam = msg.lParam; michael@0: mPassedIMEChar.RemoveElementAt(0); michael@0: } michael@0: void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) michael@0: { michael@0: MSG msg; michael@0: msg.wParam = wParam; michael@0: msg.lParam = lParam; michael@0: mPassedIMEChar.AppendElement(msg); michael@0: } michael@0: michael@0: nsWindow* mComposingWindow; michael@0: nsString mCompositionString; michael@0: nsString mLastDispatchedCompositionString; michael@0: InfallibleTArray mClauseArray; michael@0: InfallibleTArray mAttributeArray; michael@0: michael@0: int32_t mCursorPosition; michael@0: uint32_t mCompositionStart; michael@0: michael@0: bool mIsComposing; michael@0: bool mIsComposingOnPlugin; michael@0: bool mNativeCaretIsCreated; michael@0: michael@0: static UINT sCodePage; michael@0: static DWORD sIMEProperty; michael@0: }; michael@0: michael@0: #endif // nsIMM32Handler_h__