michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sts=2 sw=2 et cin: */ 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: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif // MOZ_LOGGING michael@0: #include "prlog.h" michael@0: michael@0: #include "nsIMM32Handler.h" michael@0: #include "nsWindow.h" michael@0: #include "nsWindowDefs.h" michael@0: #include "WinUtils.h" michael@0: #include "KeyboardLayout.h" michael@0: #include michael@0: michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: static nsIMM32Handler* gIMM32Handler = nullptr; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gIMM32Log = nullptr; michael@0: #endif michael@0: michael@0: static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000 michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // michael@0: // from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h michael@0: // The document for this has been removed from MSDN... michael@0: // michael@0: //------------------------------------------------------------------------- michael@0: michael@0: #define RWM_MOUSE TEXT("MSIMEMouseOperation") michael@0: michael@0: #define IMEMOUSE_NONE 0x00 // no mouse button was pushed michael@0: #define IMEMOUSE_LDOWN 0x01 michael@0: #define IMEMOUSE_RDOWN 0x02 michael@0: #define IMEMOUSE_MDOWN 0x04 michael@0: #define IMEMOUSE_WUP 0x10 // wheel up michael@0: #define IMEMOUSE_WDOWN 0x20 // wheel down michael@0: michael@0: UINT nsIMM32Handler::sCodePage = 0; michael@0: DWORD nsIMM32Handler::sIMEProperty = 0; michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::EnsureHandlerInstance() michael@0: { michael@0: if (!gIMM32Handler) { michael@0: gIMM32Handler = new nsIMM32Handler(); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::Initialize() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gIMM32Log) michael@0: gIMM32Log = PR_NewLogModule("nsIMM32HandlerWidgets"); michael@0: #endif michael@0: michael@0: if (!sWM_MSIME_MOUSE) { michael@0: sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE); michael@0: } michael@0: InitKeyboardLayout(::GetKeyboardLayout(0)); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::Terminate() michael@0: { michael@0: if (!gIMM32Handler) michael@0: return; michael@0: delete gIMM32Handler; michael@0: gIMM32Handler = nullptr; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::IsComposingOnOurEditor() michael@0: { michael@0: return gIMM32Handler && gIMM32Handler->mIsComposing; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::IsComposingOnPlugin() michael@0: { michael@0: return gIMM32Handler && gIMM32Handler->mIsComposingOnPlugin; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::IsComposingWindow(nsWindow* aWindow) michael@0: { michael@0: return gIMM32Handler && gIMM32Handler->mComposingWindow == aWindow; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::IsTopLevelWindowOfComposition(nsWindow* aWindow) michael@0: { michael@0: if (!gIMM32Handler || !gIMM32Handler->mComposingWindow) { michael@0: return false; michael@0: } michael@0: HWND wnd = gIMM32Handler->mComposingWindow->GetWindowHandle(); michael@0: return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::ShouldDrawCompositionStringOurselves() michael@0: { michael@0: // If current IME has special UI or its composition window should not michael@0: // positioned to caret position, we should now draw composition string michael@0: // ourselves. michael@0: return !(sIMEProperty & IME_PROP_SPECIAL_UI) && michael@0: (sIMEProperty & IME_PROP_AT_CARET); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::InitKeyboardLayout(HKL aKeyboardLayout) michael@0: { michael@0: WORD langID = LOWORD(aKeyboardLayout); michael@0: ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT), michael@0: LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, michael@0: (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); michael@0: sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x, sCodePage=%lu, " michael@0: "sIMEProperty=%08x", michael@0: aKeyboardLayout, sCodePage, sIMEProperty)); michael@0: } michael@0: michael@0: /* static */ UINT michael@0: nsIMM32Handler::GetKeyboardCodePage() michael@0: { michael@0: return sCodePage; michael@0: } michael@0: michael@0: /* static */ michael@0: nsIMEUpdatePreference michael@0: nsIMM32Handler::GetIMEUpdatePreference() michael@0: { michael@0: return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE); michael@0: } michael@0: michael@0: // used for checking the lParam of WM_IME_COMPOSITION michael@0: #define IS_COMPOSING_LPARAM(lParam) \ michael@0: ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) michael@0: #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR) michael@0: // Some IMEs (e.g., the standard IME for Korean) don't have caret position, michael@0: // then, we should not set caret position to text event. michael@0: #define NO_IME_CARET -1 michael@0: michael@0: nsIMM32Handler::nsIMM32Handler() : michael@0: mComposingWindow(nullptr), mCursorPosition(NO_IME_CARET), mCompositionStart(0), michael@0: mIsComposing(false), mIsComposingOnPlugin(false), michael@0: mNativeCaretIsCreated(false) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is created\n")); michael@0: } michael@0: michael@0: nsIMM32Handler::~nsIMM32Handler() michael@0: { michael@0: if (mIsComposing) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: ~nsIMM32Handler, ERROR, the instance is still composing\n")); michael@0: } michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: nsIMM32Handler is destroyed\n")); michael@0: } michael@0: michael@0: nsresult michael@0: nsIMM32Handler::EnsureClauseArray(int32_t aCount) michael@0: { michael@0: NS_ENSURE_ARG_MIN(aCount, 0); michael@0: mClauseArray.SetCapacity(aCount + 32); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsIMM32Handler::EnsureAttributeArray(int32_t aCount) michael@0: { michael@0: NS_ENSURE_ARG_MIN(aCount, 0); michael@0: mAttributeArray.SetCapacity(aCount + 64); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::CommitComposition(nsWindow* aWindow, bool aForce) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n", michael@0: aForce ? "TRUE" : "FALSE", michael@0: aWindow, aWindow->GetWindowHandle(), michael@0: gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr, michael@0: gIMM32Handler && gIMM32Handler->mComposingWindow ? michael@0: IsComposingOnOurEditor() ? " (composing on editor)" : michael@0: " (composing on plug-in)" : "")); michael@0: if (!aForce && !IsComposingWindow(aWindow)) { michael@0: return; michael@0: } michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: bool associated = IMEContext.AssociateDefaultContext(); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CommitComposition, associated=%s\n", michael@0: associated ? "YES" : "NO")); michael@0: michael@0: if (IMEContext.IsValid()) { michael@0: ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0); michael@0: ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); michael@0: } michael@0: michael@0: if (associated) { michael@0: IMEContext.Disassociate(); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsIMM32Handler::CancelComposition(nsWindow* aWindow, bool aForce) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, mComposingWindow=%p%s\n", michael@0: aForce ? "TRUE" : "FALSE", michael@0: aWindow, aWindow->GetWindowHandle(), michael@0: gIMM32Handler ? gIMM32Handler->mComposingWindow : nullptr, michael@0: gIMM32Handler && gIMM32Handler->mComposingWindow ? michael@0: IsComposingOnOurEditor() ? " (composing on editor)" : michael@0: " (composing on plug-in)" : "")); michael@0: if (!aForce && !IsComposingWindow(aWindow)) { michael@0: return; michael@0: } michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: bool associated = IMEContext.AssociateDefaultContext(); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CancelComposition, associated=%s\n", michael@0: associated ? "YES" : "NO")); michael@0: michael@0: if (IMEContext.IsValid()) { michael@0: ::ImmNotifyIME(IMEContext.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); michael@0: } michael@0: michael@0: if (associated) { michael@0: IMEContext.Disassociate(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsIMM32Handler::OnUpdateComposition(nsWindow* aWindow) michael@0: { michael@0: if (!gIMM32Handler) { michael@0: return; michael@0: } michael@0: michael@0: if (aWindow->PluginHasFocus()) { michael@0: return; michael@0: } michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: gIMM32Handler->SetIMERelatedWindowsPos(aWindow, IMEContext); michael@0: } michael@0: michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: aResult.mResult = 0; michael@0: aResult.mConsumed = false; michael@0: // We don't need to create the instance of the handler here. michael@0: if (gIMM32Handler) { michael@0: gIMM32Handler->OnInputLangChange(aWindow, wParam, lParam, aResult); michael@0: } michael@0: InitKeyboardLayout(reinterpret_cast(lParam)); michael@0: // We can release the instance here, because the instance may be never michael@0: // used. E.g., the new keyboard layout may not use IME, or it may use TSF. michael@0: Terminate(); michael@0: // Don't return as "processed", the messages should be processed on nsWindow michael@0: // too. michael@0: return false; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::ProcessMessage(nsWindow* aWindow, UINT msg, michael@0: WPARAM &wParam, LPARAM &lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: // XXX We store the composing window in mComposingWindow. If IME messages are michael@0: // sent to different window, we should commit the old transaction. And also michael@0: // if the new window handle is not focused, probably, we should not start michael@0: // the composition, however, such case should not be, it's just bad scenario. michael@0: michael@0: // When a plug-in has focus or compsition, we should dispatch the IME events michael@0: // to the plug-in. michael@0: if (aWindow->PluginHasFocus() || IsComposingOnPlugin()) { michael@0: return ProcessMessageForPlugin(aWindow, msg, wParam, lParam, aResult); michael@0: } michael@0: michael@0: aResult.mResult = 0; michael@0: switch (msg) { michael@0: case WM_LBUTTONDOWN: michael@0: case WM_MBUTTONDOWN: michael@0: case WM_RBUTTONDOWN: { michael@0: // We don't need to create the instance of the handler here. michael@0: if (!gIMM32Handler) { michael@0: return false; michael@0: } michael@0: return gIMM32Handler->OnMouseEvent(aWindow, lParam, michael@0: msg == WM_LBUTTONDOWN ? IMEMOUSE_LDOWN : michael@0: msg == WM_MBUTTONDOWN ? IMEMOUSE_MDOWN : michael@0: IMEMOUSE_RDOWN, aResult); michael@0: } michael@0: case WM_INPUTLANGCHANGE: michael@0: return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_STARTCOMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMEStartComposition(aWindow, aResult); michael@0: case WM_IME_COMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMEComposition(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_ENDCOMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMEEndComposition(aWindow, aResult); michael@0: case WM_IME_CHAR: michael@0: return OnIMEChar(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_NOTIFY: michael@0: return OnIMENotify(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_REQUEST: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMERequest(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_SELECT: michael@0: return OnIMESelect(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_SETCONTEXT: michael@0: return OnIMESetContext(aWindow, wParam, lParam, aResult); michael@0: case WM_KEYDOWN: michael@0: return OnKeyDownEvent(aWindow, wParam, lParam, aResult); michael@0: case WM_CHAR: michael@0: if (!gIMM32Handler) { michael@0: return false; michael@0: } michael@0: return gIMM32Handler->OnChar(aWindow, wParam, lParam, aResult); michael@0: default: michael@0: return false; michael@0: }; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::ProcessMessageForPlugin(nsWindow* aWindow, UINT msg, michael@0: WPARAM &wParam, LPARAM &lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: aResult.mResult = 0; michael@0: aResult.mConsumed = false; michael@0: switch (msg) { michael@0: case WM_INPUTLANGCHANGEREQUEST: michael@0: case WM_INPUTLANGCHANGE: michael@0: aWindow->DispatchPluginEvent(msg, wParam, lParam, false); michael@0: return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_COMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMECompositionOnPlugin(aWindow, wParam, lParam, michael@0: aResult); michael@0: case WM_IME_STARTCOMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMEStartCompositionOnPlugin(aWindow, wParam, michael@0: lParam, aResult); michael@0: case WM_IME_ENDCOMPOSITION: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMEEndCompositionOnPlugin(aWindow, wParam, lParam, michael@0: aResult); michael@0: case WM_IME_CHAR: michael@0: EnsureHandlerInstance(); michael@0: return gIMM32Handler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_SETCONTEXT: michael@0: return OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult); michael@0: case WM_CHAR: michael@0: if (!gIMM32Handler) { michael@0: return false; michael@0: } michael@0: return gIMM32Handler->OnCharOnPlugin(aWindow, wParam, lParam, aResult); michael@0: case WM_IME_COMPOSITIONFULL: michael@0: case WM_IME_CONTROL: michael@0: case WM_IME_KEYDOWN: michael@0: case WM_IME_KEYUP: michael@0: case WM_IME_REQUEST: michael@0: case WM_IME_SELECT: michael@0: aResult.mConsumed = michael@0: aWindow->DispatchPluginEvent(msg, wParam, lParam, false); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /**************************************************************************** michael@0: * message handlers michael@0: ****************************************************************************/ michael@0: michael@0: void michael@0: nsIMM32Handler::OnInputLangChange(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: michael@0: aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); michael@0: NS_ASSERTION(!mIsComposing, "ResetInputState failed"); michael@0: michael@0: if (mIsComposing) { michael@0: HandleEndComposition(aWindow); michael@0: } michael@0: michael@0: aResult.mConsumed = false; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMEStartComposition(nsWindow* aWindow, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEStartComposition, hWnd=%08x, mIsComposing=%s\n", michael@0: aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE")); michael@0: aResult.mConsumed = ShouldDrawCompositionStringOurselves(); michael@0: if (mIsComposing) { michael@0: NS_WARNING("Composition has been already started"); michael@0: return true; michael@0: } michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: HandleStartComposition(aWindow, IMEContext); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMEComposition(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s\n", michael@0: aWindow->GetWindowHandle(), lParam, mIsComposing ? "TRUE" : "FALSE")); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEComposition, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n", michael@0: lParam & GCS_RESULTSTR ? "YES" : "no", michael@0: lParam & GCS_COMPSTR ? "YES" : "no", michael@0: lParam & GCS_COMPATTR ? "YES" : "no", michael@0: lParam & GCS_COMPCLAUSE ? "YES" : "no", michael@0: lParam & GCS_CURSORPOS ? "YES" : "no")); michael@0: michael@0: NS_PRECONDITION(!aWindow->PluginHasFocus(), michael@0: "OnIMEComposition should not be called when a plug-in has focus"); michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: aResult.mConsumed = HandleComposition(aWindow, IMEContext, lParam); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMEEndComposition(nsWindow* aWindow, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEEndComposition, hWnd=%08x, mIsComposing=%s\n", michael@0: aWindow->GetWindowHandle(), mIsComposing ? "TRUE" : "FALSE")); michael@0: michael@0: aResult.mConsumed = ShouldDrawCompositionStringOurselves(); michael@0: if (!mIsComposing) { michael@0: return true; michael@0: } michael@0: michael@0: // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during michael@0: // composition. Then, we should ignore the message and commit the composition michael@0: // string at following WM_IME_COMPOSITION. michael@0: MSG compositionMsg; michael@0: if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(), michael@0: WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION, michael@0: PM_NOREMOVE) && michael@0: compositionMsg.message == WM_IME_COMPOSITION && michael@0: IS_COMMITTING_LPARAM(compositionMsg.lParam)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by " michael@0: "WM_IME_COMPOSITION, ignoring the message...")); michael@0: return true; michael@0: } michael@0: michael@0: // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before michael@0: // WM_IME_ENDCOMPOSITION when composition string becomes empty. michael@0: // Then, we should dispatch a compositionupdate event, a text event and michael@0: // a compositionend event. michael@0: // XXX Shouldn't we dispatch the text event with actual or latest composition michael@0: // string? michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEEndComposition, mCompositionString=\"%s\"%s", michael@0: NS_ConvertUTF16toUTF8(mCompositionString).get(), michael@0: mCompositionString.IsEmpty() ? "" : ", but canceling it...")); michael@0: michael@0: mCompositionString.Truncate(); michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: DispatchTextEvent(aWindow, IMEContext, false); michael@0: michael@0: HandleEndComposition(aWindow); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMEChar(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEChar, hWnd=%08x, char=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam)); michael@0: michael@0: // We don't need to fire any text events from here. This method will be michael@0: // called when the composition string of the current IME is not drawn by us michael@0: // and some characters are committed. In that case, the committed string was michael@0: // processed in nsWindow::OnIMEComposition already. michael@0: michael@0: // We need to consume the message so that Windows don't send two WM_CHAR msgs michael@0: aResult.mConsumed = true; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMECompositionFull(nsWindow* aWindow, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMECompositionFull, hWnd=%08x\n", michael@0: aWindow->GetWindowHandle())); michael@0: michael@0: // not implement yet michael@0: aResult.mConsumed = false; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMENotify(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: switch (wParam) { michael@0: case IMN_CHANGECANDIDATE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), lParam)); michael@0: break; michael@0: case IMN_CLOSECANDIDATE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), lParam)); michael@0: break; michael@0: case IMN_CLOSESTATUSWINDOW: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_GUIDELINE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_GUIDELINE\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_OPENCANDIDATE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), lParam)); michael@0: break; michael@0: case IMN_OPENSTATUSWINDOW: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETCANDIDATEPOS: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), lParam)); michael@0: break; michael@0: case IMN_SETCOMPOSITIONFONT: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETCOMPOSITIONWINDOW: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETCONVERSIONMODE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETOPENSTATUS: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETSENTENCEMODE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_SETSTATUSWINDOWPOS: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: case IMN_PRIVATE: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMENotify, hWnd=%08x, IMN_PRIVATE\n", michael@0: aWindow->GetWindowHandle())); michael@0: break; michael@0: } michael@0: #endif // PR_LOGGING michael@0: michael@0: // not implement yet michael@0: aResult.mConsumed = false; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMERequest(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: switch (wParam) { michael@0: case IMR_RECONVERTSTRING: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING\n", michael@0: aWindow->GetWindowHandle())); michael@0: aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult); michael@0: return true; michael@0: case IMR_QUERYCHARPOSITION: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION\n", michael@0: aWindow->GetWindowHandle())); michael@0: aResult.mConsumed = michael@0: HandleQueryCharPosition(aWindow, lParam, &aResult.mResult); michael@0: return true; michael@0: case IMR_DOCUMENTFEED: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED\n", michael@0: aWindow->GetWindowHandle())); michael@0: aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult); michael@0: return true; michael@0: default: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMERequest, hWnd=%08x, wParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam)); michael@0: aResult.mConsumed = false; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMESelect(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: michael@0: // not implement yet michael@0: aResult.mConsumed = false; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMESetContext(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMESetContext, hWnd=%08x, %s, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); michael@0: michael@0: aResult.mConsumed = false; michael@0: michael@0: // NOTE: If the aWindow is top level window of the composing window because michael@0: // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is michael@0: // TRUE) is sent to the top level window first. After that, michael@0: // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window. michael@0: // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window. michael@0: // The top level window never becomes composing window, so, we can ignore michael@0: // the WM_IME_SETCONTEXT on the top level window. michael@0: if (IsTopLevelWindowOfComposition(aWindow)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMESetContext, hWnd=%08x is top level window\n")); michael@0: return true; michael@0: } michael@0: michael@0: // When IME context is activating on another window, michael@0: // we should commit the old composition on the old window. michael@0: bool cancelComposition = false; michael@0: if (wParam && gIMM32Handler) { michael@0: cancelComposition = michael@0: gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow); michael@0: } michael@0: michael@0: if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) && michael@0: ShouldDrawCompositionStringOurselves()) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed\n")); michael@0: lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; michael@0: } michael@0: michael@0: // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the michael@0: // ancestor windows shouldn't receive this message. If they receive the michael@0: // message, we cannot know whether which window is the target of the message. michael@0: aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), michael@0: WM_IME_SETCONTEXT, wParam, lParam); michael@0: michael@0: // Cancel composition on the new window if we committed our composition on michael@0: // another window. michael@0: if (cancelComposition) { michael@0: CancelComposition(aWindow, true); michael@0: } michael@0: michael@0: aResult.mConsumed = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnChar(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: // The return value must be same as aResult.mConsumed because only when we michael@0: // consume the message, the caller shouldn't do anything anymore but michael@0: // otherwise, the caller should handle the message. michael@0: aResult.mConsumed = false; michael@0: if (IsIMECharRecordsEmpty()) { michael@0: return aResult.mConsumed; michael@0: } michael@0: WPARAM recWParam; michael@0: LPARAM recLParam; michael@0: DequeueIMECharRecords(recWParam, recLParam); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnChar, aWindow=%p, wParam=%08x, lParam=%08x,\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: (" recorded: wParam=%08x, lParam=%08x\n", michael@0: recWParam, recLParam)); michael@0: // If an unexpected char message comes, we should reset the records, michael@0: // of course, this shouldn't happen. michael@0: if (recWParam != wParam || recLParam != lParam) { michael@0: ResetIMECharRecords(); michael@0: return aResult.mConsumed; michael@0: } michael@0: // Eat the char message which is caused by WM_IME_CHAR because we should michael@0: // have processed the IME messages, so, this message could be come from michael@0: // a windowless plug-in. michael@0: aResult.mConsumed = true; michael@0: return aResult.mConsumed; michael@0: } michael@0: michael@0: /**************************************************************************** michael@0: * message handlers for plug-in michael@0: ****************************************************************************/ michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n", michael@0: aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE")); michael@0: mIsComposingOnPlugin = true; michael@0: mComposingWindow = aWindow; michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext); michael@0: aResult.mConsumed = michael@0: aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam, michael@0: false); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, mIsComposingOnPlugin=%s\n", michael@0: aWindow->GetWindowHandle(), lParam, michael@0: mIsComposingOnPlugin ? "TRUE" : "FALSE")); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMECompositionOnPlugin, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s\n", michael@0: lParam & GCS_RESULTSTR ? "YES" : "no", michael@0: lParam & GCS_COMPSTR ? "YES" : "no", michael@0: lParam & GCS_COMPATTR ? "YES" : "no", michael@0: lParam & GCS_COMPCLAUSE ? "YES" : "no", michael@0: lParam & GCS_CURSORPOS ? "YES" : "no")); michael@0: // We should end composition if there is a committed string. michael@0: if (IS_COMMITTING_LPARAM(lParam)) { michael@0: mIsComposingOnPlugin = false; michael@0: mComposingWindow = nullptr; michael@0: } michael@0: // Continue composition if there is still a string being composed. michael@0: if (IS_COMPOSING_LPARAM(lParam)) { michael@0: mIsComposingOnPlugin = true; michael@0: mComposingWindow = aWindow; michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext); michael@0: } michael@0: aResult.mConsumed = michael@0: aWindow->DispatchPluginEvent(WM_IME_COMPOSITION, wParam, lParam, true); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n", michael@0: aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE")); michael@0: michael@0: mIsComposingOnPlugin = false; michael@0: mComposingWindow = nullptr; michael@0: michael@0: if (mNativeCaretIsCreated) { michael@0: ::DestroyCaret(); michael@0: mNativeCaretIsCreated = false; michael@0: } michael@0: michael@0: aResult.mConsumed = michael@0: aWindow->DispatchPluginEvent(WM_IME_ENDCOMPOSITION, wParam, lParam, michael@0: false); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnIMECharOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: michael@0: aResult.mConsumed = michael@0: aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true); michael@0: michael@0: if (!aResult.mConsumed) { michael@0: // Record the WM_CHAR messages which are going to be coming. michael@0: EnsureHandlerInstance(); michael@0: EnqueueIMECharRecords(wParam, lParam); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnIMESetContextOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); michael@0: michael@0: // If the IME context becomes active on a plug-in, we should commit michael@0: // our composition. And also we should cancel the composition on new michael@0: // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns michael@0: // true, we should ignore the message here, see the comment in michael@0: // OnIMESetContext() for the detail. michael@0: if (wParam && gIMM32Handler && !IsTopLevelWindowOfComposition(aWindow)) { michael@0: if (gIMM32Handler->CommitCompositionOnPreviousWindow(aWindow)) { michael@0: CancelComposition(aWindow); michael@0: } michael@0: } michael@0: michael@0: // Dispatch message to the plug-in. michael@0: // XXX When a windowless plug-in gets focus, we should send michael@0: // WM_IME_SETCONTEXT michael@0: aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false); michael@0: michael@0: // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't michael@0: // be received on ancestor windows, see OnIMESetContext() for the detail. michael@0: aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), michael@0: WM_IME_SETCONTEXT, wParam, lParam); michael@0: michael@0: // Don't synchronously dispatch the pending events when we receive michael@0: // WM_IME_SETCONTEXT because we get it during plugin destruction. michael@0: // (bug 491848) michael@0: aResult.mConsumed = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnCharOnPlugin(nsWindow* aWindow, michael@0: WPARAM wParam, michael@0: LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: // We should never consume char message on windowless plugin. michael@0: aResult.mConsumed = false; michael@0: if (IsIMECharRecordsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: WPARAM recWParam; michael@0: LPARAM recLParam; michael@0: DequeueIMECharRecords(recWParam, recLParam); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x,\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: (" recorded: wParam=%08x, lParam=%08x\n", michael@0: recWParam, recLParam)); michael@0: // If an unexpected char message comes, we should reset the records, michael@0: // of course, this shouldn't happen. michael@0: if (recWParam != wParam || recLParam != lParam) { michael@0: ResetIMECharRecords(); michael@0: } michael@0: // WM_CHAR on plug-in is always handled by nsWindow. michael@0: return false; michael@0: } michael@0: michael@0: /**************************************************************************** michael@0: * others michael@0: ****************************************************************************/ michael@0: michael@0: void michael@0: nsIMM32Handler::HandleStartComposition(nsWindow* aWindow, michael@0: const nsIMEContext &aIMEContext) michael@0: { michael@0: NS_PRECONDITION(!mIsComposing, michael@0: "HandleStartComposition is called but mIsComposing is TRUE"); michael@0: NS_PRECONDITION(!aWindow->PluginHasFocus(), michael@0: "HandleStartComposition should not be called when a plug-in has focus"); michael@0: michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); michael@0: nsIntPoint point(0, 0); michael@0: aWindow->InitEvent(selection, &point); michael@0: aWindow->DispatchWindowEvent(&selection); michael@0: if (!selection.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n")); michael@0: return; michael@0: } michael@0: michael@0: mCompositionStart = selection.mReply.mOffset; michael@0: mLastDispatchedCompositionString.Truncate(); michael@0: michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_START, aWindow); michael@0: aWindow->InitEvent(event, &point); michael@0: aWindow->DispatchWindowEvent(&event); michael@0: michael@0: mIsComposing = true; michael@0: mComposingWindow = aWindow; michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleStartComposition, START composition, mCompositionStart=%ld\n", michael@0: mCompositionStart)); michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::HandleComposition(nsWindow* aWindow, michael@0: const nsIMEContext &aIMEContext, michael@0: LPARAM lParam) michael@0: { michael@0: NS_PRECONDITION(!aWindow->PluginHasFocus(), michael@0: "HandleComposition should not be called when a plug-in has focus"); michael@0: michael@0: // for bug #60050 michael@0: // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion michael@0: // mode before it send WM_IME_STARTCOMPOSITION. michael@0: // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION, michael@0: // and if we access ATOK via some APIs, ATOK will sometimes fail to michael@0: // initialize its state. If WM_IME_STARTCOMPOSITION is already in the michael@0: // message queue, we should ignore the strange WM_IME_COMPOSITION message and michael@0: // skip to the next. So, we should look for next composition message michael@0: // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION), michael@0: // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message michael@0: // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we michael@0: // should start composition forcibly. michael@0: if (!mIsComposing) { michael@0: MSG msg1, msg2; michael@0: HWND wnd = aWindow->GetWindowHandle(); michael@0: if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION, michael@0: WM_IME_COMPOSITION, PM_NOREMOVE) && michael@0: msg1.message == WM_IME_STARTCOMPOSITION && michael@0: WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION, michael@0: WM_IME_COMPOSITION, PM_NOREMOVE) && michael@0: msg2.message == WM_IME_COMPOSITION) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, Ignores due to find a WM_IME_STARTCOMPOSITION\n")); michael@0: return ShouldDrawCompositionStringOurselves(); michael@0: } michael@0: } michael@0: michael@0: bool startCompositionMessageHasBeenSent = mIsComposing; michael@0: michael@0: // michael@0: // This catches a fixed result michael@0: // michael@0: if (IS_COMMITTING_LPARAM(lParam)) { michael@0: if (!mIsComposing) { michael@0: HandleStartComposition(aWindow, aIMEContext); michael@0: } michael@0: michael@0: GetCompositionString(aIMEContext, GCS_RESULTSTR); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_RESULTSTR\n")); michael@0: michael@0: DispatchTextEvent(aWindow, aIMEContext, false); michael@0: HandleEndComposition(aWindow); michael@0: michael@0: if (!IS_COMPOSING_LPARAM(lParam)) { michael@0: return ShouldDrawCompositionStringOurselves(); michael@0: } michael@0: } michael@0: michael@0: michael@0: // michael@0: // This provides us with a composition string michael@0: // michael@0: if (!mIsComposing) { michael@0: HandleStartComposition(aWindow, aIMEContext); michael@0: } michael@0: michael@0: //-------------------------------------------------------- michael@0: // 1. Get GCS_COMPSTR michael@0: //-------------------------------------------------------- michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_COMPSTR\n")); michael@0: michael@0: GetCompositionString(aIMEContext, GCS_COMPSTR); michael@0: michael@0: if (!IS_COMPOSING_LPARAM(lParam)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, lParam doesn't indicate composing, " michael@0: "mCompositionString=\"%s\", mLastDispatchedCompositionString=\"%s\"", michael@0: NS_ConvertUTF16toUTF8(mCompositionString).get(), michael@0: NS_ConvertUTF16toUTF8(mLastDispatchedCompositionString).get())); michael@0: michael@0: // If composition string isn't changed, we can trust the lParam. michael@0: // So, we need to do nothing. michael@0: if (mLastDispatchedCompositionString == mCompositionString) { michael@0: return ShouldDrawCompositionStringOurselves(); michael@0: } michael@0: michael@0: // IME may send WM_IME_COMPOSITION without composing lParam values michael@0: // when composition string becomes empty (e.g., using Backspace key). michael@0: // If composition string is empty, we should dispatch a text event with michael@0: // empty string. michael@0: if (mCompositionString.IsEmpty()) { michael@0: DispatchTextEvent(aWindow, aIMEContext, false); michael@0: return ShouldDrawCompositionStringOurselves(); michael@0: } michael@0: michael@0: // Otherwise, we cannot trust the lParam value. We might need to michael@0: // dispatch text event with the latest composition string information. michael@0: } michael@0: michael@0: // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339 michael@0: if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) { michael@0: // In this case, maybe, the sender is MSPinYin. That sends *only* michael@0: // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when michael@0: // user inputted the Chinese full stop. So, that doesn't send michael@0: // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION. michael@0: // If WM_IME_STARTCOMPOSITION was not sent and the composition michael@0: // string is null (it indicates the composition transaction ended), michael@0: // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run michael@0: // HandleEndComposition() in other place. michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, Aborting GCS_COMPSTR\n")); michael@0: HandleEndComposition(aWindow); michael@0: return IS_COMMITTING_LPARAM(lParam); michael@0: } michael@0: michael@0: //-------------------------------------------------------- michael@0: // 2. Get GCS_COMPCLAUSE michael@0: //-------------------------------------------------------- michael@0: long clauseArrayLength = michael@0: ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE, nullptr, 0); michael@0: clauseArrayLength /= sizeof(uint32_t); michael@0: michael@0: if (clauseArrayLength > 0) { michael@0: nsresult rv = EnsureClauseArray(clauseArrayLength); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Intelligent ABC IME (Simplified Chinese IME, the code page is 936) michael@0: // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663). michael@0: // See comment 35 of the bug for the detail. Therefore, we should use A michael@0: // API for it, however, we should not kill Unicode support on all IMEs. michael@0: bool useA_API = !(sIMEProperty & IME_PROP_UNICODE); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_COMPCLAUSE, useA_API=%s\n", michael@0: useA_API ? "TRUE" : "FALSE")); michael@0: michael@0: long clauseArrayLength2 = michael@0: useA_API ? michael@0: ::ImmGetCompositionStringA(aIMEContext.get(), GCS_COMPCLAUSE, michael@0: mClauseArray.Elements(), michael@0: mClauseArray.Capacity() * sizeof(uint32_t)) : michael@0: ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPCLAUSE, michael@0: mClauseArray.Elements(), michael@0: mClauseArray.Capacity() * sizeof(uint32_t)); michael@0: clauseArrayLength2 /= sizeof(uint32_t); michael@0: michael@0: if (clauseArrayLength != clauseArrayLength2) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but clauseArrayLength2=%ld\n", michael@0: clauseArrayLength, clauseArrayLength2)); michael@0: if (clauseArrayLength > clauseArrayLength2) michael@0: clauseArrayLength = clauseArrayLength2; michael@0: } michael@0: michael@0: if (useA_API) { michael@0: // Convert each values of sIMECompClauseArray. The values mean offset of michael@0: // the clauses in ANSI string. But we need the values in Unicode string. michael@0: nsAutoCString compANSIStr; michael@0: if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(), michael@0: compANSIStr)) { michael@0: uint32_t maxlen = compANSIStr.Length(); michael@0: mClauseArray[0] = 0; // first value must be 0 michael@0: for (int32_t i = 1; i < clauseArrayLength; i++) { michael@0: uint32_t len = std::min(mClauseArray[i], maxlen); michael@0: mClauseArray[i] = ::MultiByteToWideChar(GetKeyboardCodePage(), michael@0: MB_PRECOMPOSED, michael@0: (LPCSTR)compANSIStr.get(), michael@0: len, nullptr, 0); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW michael@0: // may return an error code. michael@0: mClauseArray.SetLength(std::max(0, clauseArrayLength)); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld\n", michael@0: mClauseArray.Length())); michael@0: michael@0: //-------------------------------------------------------- michael@0: // 3. Get GCS_COMPATTR michael@0: //-------------------------------------------------------- michael@0: // This provides us with the attribute string necessary michael@0: // for doing hiliting michael@0: long attrArrayLength = michael@0: ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR, nullptr, 0); michael@0: attrArrayLength /= sizeof(uint8_t); michael@0: michael@0: if (attrArrayLength > 0) { michael@0: nsresult rv = EnsureAttributeArray(attrArrayLength); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: attrArrayLength = michael@0: ::ImmGetCompositionStringW(aIMEContext.get(), GCS_COMPATTR, michael@0: mAttributeArray.Elements(), michael@0: mAttributeArray.Capacity() * sizeof(uint8_t)); michael@0: } michael@0: michael@0: // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an michael@0: // error code. michael@0: mAttributeArray.SetLength(std::max(0, attrArrayLength)); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_COMPATTR, mAttributeLength=%ld\n", michael@0: mAttributeArray.Length())); michael@0: michael@0: //-------------------------------------------------------- michael@0: // 4. Get GCS_CURSOPOS michael@0: //-------------------------------------------------------- michael@0: // Some IMEs (e.g., the standard IME for Korean) don't have caret position. michael@0: if (lParam & GCS_CURSORPOS) { michael@0: mCursorPosition = michael@0: ::ImmGetCompositionStringW(aIMEContext.get(), GCS_CURSORPOS, nullptr, 0); michael@0: if (mCursorPosition < 0) { michael@0: mCursorPosition = NO_IME_CARET; // The result is error michael@0: } michael@0: } else { michael@0: mCursorPosition = NO_IME_CARET; michael@0: } michael@0: michael@0: NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(), michael@0: "illegal pos"); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d\n", michael@0: mCursorPosition)); michael@0: michael@0: //-------------------------------------------------------- michael@0: // 5. Send the text event michael@0: //-------------------------------------------------------- michael@0: DispatchTextEvent(aWindow, aIMEContext); michael@0: michael@0: return ShouldDrawCompositionStringOurselves(); michael@0: } michael@0: michael@0: void michael@0: nsIMM32Handler::HandleEndComposition(nsWindow* aWindow) michael@0: { michael@0: NS_PRECONDITION(mIsComposing, michael@0: "HandleEndComposition is called but mIsComposing is FALSE"); michael@0: NS_PRECONDITION(!aWindow->PluginHasFocus(), michael@0: "HandleComposition should not be called when a plug-in has focus"); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleEndComposition\n")); michael@0: michael@0: WidgetCompositionEvent event(true, NS_COMPOSITION_END, aWindow); michael@0: nsIntPoint point(0, 0); michael@0: michael@0: if (mNativeCaretIsCreated) { michael@0: ::DestroyCaret(); michael@0: mNativeCaretIsCreated = false; michael@0: } michael@0: michael@0: aWindow->InitEvent(event, &point); michael@0: // The last dispatched composition string must be the committed string. michael@0: event.data = mLastDispatchedCompositionString; michael@0: aWindow->DispatchWindowEvent(&event); michael@0: mIsComposing = false; michael@0: mComposingWindow = nullptr; michael@0: mLastDispatchedCompositionString.Truncate(); michael@0: } michael@0: michael@0: static void michael@0: DumpReconvertString(RECONVERTSTRING* aReconv) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: (" dwSize=%ld, dwVersion=%ld, dwStrLen=%ld, dwStrOffset=%ld\n", michael@0: aReconv->dwSize, aReconv->dwVersion, michael@0: aReconv->dwStrLen, aReconv->dwStrOffset)); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: (" dwCompStrLen=%ld, dwCompStrOffset=%ld, dwTargetStrLen=%ld, dwTargetStrOffset=%ld\n", michael@0: aReconv->dwCompStrLen, aReconv->dwCompStrOffset, michael@0: aReconv->dwTargetStrLen, aReconv->dwTargetStrOffset)); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: (" result str=\"%s\"\n", michael@0: NS_ConvertUTF16toUTF8( michael@0: nsAutoString((char16_t*)((char*)(aReconv) + aReconv->dwStrOffset), michael@0: aReconv->dwStrLen)).get())); michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::HandleReconvert(nsWindow* aWindow, michael@0: LPARAM lParam, michael@0: LRESULT *oResult) michael@0: { michael@0: *oResult = 0; michael@0: RECONVERTSTRING* pReconv = reinterpret_cast(lParam); michael@0: michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); michael@0: nsIntPoint point(0, 0); michael@0: aWindow->InitEvent(selection, &point); michael@0: aWindow->DispatchWindowEvent(&selection); michael@0: if (!selection.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, FAILED (NS_QUERY_SELECTED_TEXT)\n")); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t len = selection.mReply.mString.Length(); michael@0: uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); michael@0: michael@0: if (!pReconv) { michael@0: // Return need size to reconvert. michael@0: if (len == 0) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, There are not selected text\n")); michael@0: return false; michael@0: } michael@0: *oResult = needSize; michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n", michael@0: *oResult)); michael@0: return true; michael@0: } michael@0: michael@0: if (pReconv->dwSize < needSize) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, FAILED pReconv->dwSize=%ld, needSize=%ld\n", michael@0: pReconv->dwSize, needSize)); michael@0: return false; michael@0: } michael@0: michael@0: *oResult = needSize; michael@0: michael@0: // Fill reconvert struct michael@0: pReconv->dwVersion = 0; michael@0: pReconv->dwStrLen = len; michael@0: pReconv->dwStrOffset = sizeof(RECONVERTSTRING); michael@0: pReconv->dwCompStrLen = len; michael@0: pReconv->dwCompStrOffset = 0; michael@0: pReconv->dwTargetStrLen = len; michael@0: pReconv->dwTargetStrOffset = 0; michael@0: michael@0: ::CopyMemory(reinterpret_cast(lParam + sizeof(RECONVERTSTRING)), michael@0: selection.mReply.mString.get(), len * sizeof(WCHAR)); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, SUCCEEDED result=%ld\n", michael@0: *oResult)); michael@0: DumpReconvertString(pReconv); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::HandleQueryCharPosition(nsWindow* aWindow, michael@0: LPARAM lParam, michael@0: LRESULT *oResult) michael@0: { michael@0: uint32_t len = mIsComposing ? mCompositionString.Length() : 0; michael@0: *oResult = false; michael@0: IMECHARPOSITION* pCharPosition = reinterpret_cast(lParam); michael@0: if (!pCharPosition) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleQueryCharPosition, FAILED (pCharPosition is null)\n")); michael@0: return false; michael@0: } michael@0: if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, FAILED, pCharPosition->dwSize=%ld, sizeof(IMECHARPOSITION)=%ld\n", michael@0: pCharPosition->dwSize, sizeof(IMECHARPOSITION))); michael@0: return false; michael@0: } michael@0: if (::GetFocus() != aWindow->GetWindowHandle()) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x\n", michael@0: ::GetFocus(), aWindow->GetWindowHandle())); michael@0: return false; michael@0: } michael@0: if (pCharPosition->dwCharPos > len) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, len=%ld\n", michael@0: pCharPosition->dwCharPos, len)); michael@0: return false; michael@0: } michael@0: michael@0: nsIntRect r; michael@0: bool ret = michael@0: GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r); michael@0: NS_ENSURE_TRUE(ret, false); michael@0: michael@0: nsIntRect screenRect; michael@0: // We always need top level window that is owner window of the popup window michael@0: // even if the content of the popup window has focus. michael@0: ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), michael@0: r, nullptr, screenRect); michael@0: pCharPosition->pt.x = screenRect.x; michael@0: pCharPosition->pt.y = screenRect.y; michael@0: michael@0: pCharPosition->cLineHeight = r.height; michael@0: michael@0: // XXX we should use NS_QUERY_EDITOR_RECT event here. michael@0: ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); michael@0: michael@0: *oResult = TRUE; michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleQueryCharPosition, SUCCEEDED\n")); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow, michael@0: LPARAM lParam, michael@0: LRESULT *oResult) michael@0: { michael@0: *oResult = 0; michael@0: RECONVERTSTRING* pReconv = reinterpret_cast(lParam); michael@0: michael@0: nsIntPoint point(0, 0); michael@0: michael@0: bool hasCompositionString = michael@0: mIsComposing && ShouldDrawCompositionStringOurselves(); michael@0: michael@0: int32_t targetOffset, targetLength; michael@0: if (!hasCompositionString) { michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); michael@0: aWindow->InitEvent(selection, &point); michael@0: aWindow->DispatchWindowEvent(&selection); michael@0: if (!selection.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_SELECTED_TEXT)\n")); michael@0: return false; michael@0: } michael@0: targetOffset = int32_t(selection.mReply.mOffset); michael@0: targetLength = int32_t(selection.mReply.mString.Length()); michael@0: } else { michael@0: targetOffset = int32_t(mCompositionStart); michael@0: targetLength = int32_t(mCompositionString.Length()); michael@0: } michael@0: michael@0: // XXX nsString::Find and nsString::RFind take int32_t for offset, so, michael@0: // we cannot support this message when the current offset is larger than michael@0: // INT32_MAX. michael@0: if (targetOffset < 0 || targetLength < 0 || michael@0: targetOffset + targetLength < 0) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED (The selection is out of range)\n")); michael@0: return false; michael@0: } michael@0: michael@0: // Get all contents of the focused editor. michael@0: WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, aWindow); michael@0: textContent.InitForQueryTextContent(0, UINT32_MAX); michael@0: aWindow->InitEvent(textContent, &point); michael@0: aWindow->DispatchWindowEvent(&textContent); michael@0: if (!textContent.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED (NS_QUERY_TEXT_CONTENT)\n")); michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString str(textContent.mReply.mString); michael@0: if (targetOffset > int32_t(str.Length())) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED (The caret offset is invalid)\n")); michael@0: return false; michael@0: } michael@0: michael@0: // Get the focused paragraph, we decide that it starts from the previous CRLF michael@0: // (or start of the editor) to the next one (or the end of the editor). michael@0: int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1; michael@0: int32_t paragraphEnd = michael@0: str.Find("\r", false, targetOffset + targetLength, -1); michael@0: if (paragraphEnd < 0) { michael@0: paragraphEnd = str.Length(); michael@0: } michael@0: nsDependentSubstring paragraph(str, paragraphStart, michael@0: paragraphEnd - paragraphStart); michael@0: michael@0: uint32_t len = paragraph.Length(); michael@0: uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); michael@0: michael@0: if (!pReconv) { michael@0: *oResult = needSize; michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n", michael@0: *oResult)); michael@0: return true; michael@0: } michael@0: michael@0: if (pReconv->dwSize < needSize) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED pReconv->dwSize=%ld, needSize=%ld\n", michael@0: pReconv->dwSize, needSize)); michael@0: return false; michael@0: } michael@0: michael@0: // Fill reconvert struct michael@0: pReconv->dwVersion = 0; michael@0: pReconv->dwStrLen = len; michael@0: pReconv->dwStrOffset = sizeof(RECONVERTSTRING); michael@0: if (hasCompositionString) { michael@0: pReconv->dwCompStrLen = targetLength; michael@0: pReconv->dwCompStrOffset = michael@0: (targetOffset - paragraphStart) * sizeof(WCHAR); michael@0: // Set composition target clause information michael@0: uint32_t offset, length; michael@0: if (!GetTargetClauseRange(&offset, &length)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, FAILED, by GetTargetClauseRange\n")); michael@0: return false; michael@0: } michael@0: pReconv->dwTargetStrLen = length; michael@0: pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR); michael@0: } else { michael@0: pReconv->dwTargetStrLen = targetLength; michael@0: pReconv->dwTargetStrOffset = michael@0: (targetOffset - paragraphStart) * sizeof(WCHAR); michael@0: // There is no composition string, so, the length is zero but we should michael@0: // set the cursor offset to the composition str offset. michael@0: pReconv->dwCompStrLen = 0; michael@0: pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset; michael@0: } michael@0: michael@0: *oResult = needSize; michael@0: ::CopyMemory(reinterpret_cast(lParam + sizeof(RECONVERTSTRING)), michael@0: paragraph.BeginReading(), len * sizeof(WCHAR)); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: HandleDocumentFeed, SUCCEEDED result=%ld\n", michael@0: *oResult)); michael@0: DumpReconvertString(pReconv); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) michael@0: { michael@0: if (!mComposingWindow || mComposingWindow == aWindow) { michael@0: return false; michael@0: } michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CommitCompositionOnPreviousWindow, mIsComposing=%s, mIsComposingOnPlugin=%s\n", michael@0: mIsComposing ? "TRUE" : "FALSE", mIsComposingOnPlugin ? "TRUE" : "FALSE")); michael@0: michael@0: // If we have composition, we should dispatch composition events internally. michael@0: if (mIsComposing) { michael@0: nsIMEContext IMEContext(mComposingWindow->GetWindowHandle()); michael@0: NS_ASSERTION(IMEContext.IsValid(), "IME context must be valid"); michael@0: michael@0: DispatchTextEvent(mComposingWindow, IMEContext, false); michael@0: HandleEndComposition(mComposingWindow); michael@0: return true; michael@0: } michael@0: michael@0: // XXX When plug-in has composition, we should commit composition on the michael@0: // plug-in. However, we need some more work for that. michael@0: return mIsComposingOnPlugin; michael@0: } michael@0: michael@0: static uint32_t michael@0: PlatformToNSAttr(uint8_t aAttr) michael@0: { michael@0: switch (aAttr) michael@0: { michael@0: case ATTR_INPUT_ERROR: michael@0: // case ATTR_FIXEDCONVERTED: michael@0: case ATTR_INPUT: michael@0: return NS_TEXTRANGE_RAWINPUT; michael@0: case ATTR_CONVERTED: michael@0: return NS_TEXTRANGE_CONVERTEDTEXT; michael@0: case ATTR_TARGET_NOTCONVERTED: michael@0: return NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: case ATTR_TARGET_CONVERTED: michael@0: return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; michael@0: default: michael@0: NS_ASSERTION(false, "unknown attribute"); michael@0: return NS_TEXTRANGE_CARETPOSITION; michael@0: } michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: static const char* michael@0: GetRangeTypeName(uint32_t aRangeType) michael@0: { michael@0: switch (aRangeType) { michael@0: case NS_TEXTRANGE_RAWINPUT: michael@0: return "NS_TEXTRANGE_RAWINPUT"; michael@0: case NS_TEXTRANGE_CONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_CONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDRAWTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDRAWTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_CARETPOSITION: michael@0: return "NS_TEXTRANGE_CARETPOSITION"; michael@0: default: michael@0: return "UNKNOWN SELECTION TYPE!!"; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsIMM32Handler::DispatchTextEvent(nsWindow* aWindow, michael@0: const nsIMEContext &aIMEContext, michael@0: bool aCheckAttr) michael@0: { michael@0: NS_ASSERTION(mIsComposing, "conflict state"); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: DispatchTextEvent, aCheckAttr=%s\n", michael@0: aCheckAttr ? "TRUE": "FALSE")); michael@0: michael@0: // If we don't need to draw composition string ourselves and this is not michael@0: // commit event (i.e., under composing), we don't need to fire text event michael@0: // during composing. michael@0: if (aCheckAttr && !ShouldDrawCompositionStringOurselves()) { michael@0: // But we need to adjust composition window pos and native caret pos, here. michael@0: SetIMERelatedWindowsPos(aWindow, aIMEContext); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(aWindow); michael@0: michael@0: nsIntPoint point(0, 0); michael@0: michael@0: if (mCompositionString != mLastDispatchedCompositionString) { michael@0: WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, michael@0: aWindow); michael@0: aWindow->InitEvent(compositionUpdate, &point); michael@0: compositionUpdate.data = mCompositionString; michael@0: mLastDispatchedCompositionString = mCompositionString; michael@0: michael@0: aWindow->DispatchWindowEvent(&compositionUpdate); michael@0: michael@0: if (!mIsComposing || aWindow->Destroyed()) { michael@0: return; michael@0: } michael@0: SetIMERelatedWindowsPos(aWindow, aIMEContext); michael@0: } michael@0: michael@0: WidgetTextEvent event(true, NS_TEXT_TEXT, aWindow); michael@0: michael@0: aWindow->InitEvent(event, &point); michael@0: michael@0: if (aCheckAttr) { michael@0: event.mRanges = CreateTextRangeArray(); michael@0: } michael@0: michael@0: event.theText = mCompositionString.get(); michael@0: michael@0: aWindow->DispatchWindowEvent(&event); michael@0: michael@0: // Calling SetIMERelatedWindowsPos will be failure on e10s at this point. michael@0: // text event will notify NOTIFY_IME_OF_COMPOSITION_UPDATE, then michael@0: // it will call SetIMERelatedWindowsPos. michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsIMM32Handler::CreateTextRangeArray() michael@0: { michael@0: // Sogou (Simplified Chinese IME) returns contradictory values: The cursor michael@0: // position is actual cursor position. However, other values (composition michael@0: // string and attributes) are empty. So, if you want to remove following michael@0: // assertion, be careful. michael@0: NS_ASSERTION(ShouldDrawCompositionStringOurselves(), michael@0: "CreateTextRangeArray is called when we don't need to fire text event"); michael@0: michael@0: nsRefPtr textRangeArray = new TextRangeArray(); michael@0: michael@0: TextRange range; michael@0: if (mClauseArray.Length() == 0) { michael@0: // Some IMEs don't return clause array information, then, we assume that michael@0: // all characters in the composition string are in one clause. michael@0: range.mStartOffset = 0; michael@0: range.mEndOffset = mCompositionString.Length(); michael@0: range.mRangeType = NS_TEXTRANGE_RAWINPUT; michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, mClauseLength=0\n")); michael@0: } else { michael@0: // iterate over the attributes michael@0: uint32_t lastOffset = 0; michael@0: for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) { michael@0: uint32_t current = mClauseArray[i + 1]; michael@0: if (current > mCompositionString.Length()) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, mClauseArray[%ld]=%lu. " michael@0: "This is larger than mCompositionString.Length()=%lu\n", michael@0: i + 1, current, mCompositionString.Length())); michael@0: current = int32_t(mCompositionString.Length()); michael@0: } michael@0: michael@0: range.mRangeType = PlatformToNSAttr(mAttributeArray[lastOffset]); michael@0: range.mStartOffset = lastOffset; michael@0: range.mEndOffset = current; michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: lastOffset = current; michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, index=%ld, rangeType=%s, range=[%lu-%lu]\n", michael@0: i, GetRangeTypeName(range.mRangeType), range.mStartOffset, michael@0: range.mEndOffset)); michael@0: } michael@0: } michael@0: michael@0: if (mCursorPosition == NO_IME_CARET) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, no caret\n")); michael@0: return textRangeArray.forget(); michael@0: } michael@0: michael@0: int32_t cursor = mCursorPosition; michael@0: if (uint32_t(cursor) > mCompositionString.Length()) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, mCursorPosition=%ld. " michael@0: "This is larger than mCompositionString.Length()=%lu\n", michael@0: mCursorPosition, mCompositionString.Length())); michael@0: cursor = mCompositionString.Length(); michael@0: } michael@0: michael@0: range.mStartOffset = range.mEndOffset = cursor; michael@0: range.mRangeType = NS_TEXTRANGE_CARETPOSITION; michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: CreateTextRangeArray, caret position=%ld\n", michael@0: range.mStartOffset)); michael@0: michael@0: return textRangeArray.forget(); michael@0: } michael@0: michael@0: void michael@0: nsIMM32Handler::GetCompositionString(const nsIMEContext &aIMEContext, michael@0: DWORD aIndex) michael@0: { michael@0: // Retrieve the size of the required output buffer. michael@0: long lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, nullptr, 0); michael@0: if (lRtn < 0 || michael@0: !mCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1, mozilla::fallible_t())) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCompositionString, FAILED by OOM\n")); michael@0: return; // Error or out of memory. michael@0: } michael@0: michael@0: // Actually retrieve the composition string information. michael@0: lRtn = ::ImmGetCompositionStringW(aIMEContext.get(), aIndex, michael@0: (LPVOID)mCompositionString.BeginWriting(), michael@0: lRtn + sizeof(WCHAR)); michael@0: mCompositionString.SetLength(lRtn / sizeof(WCHAR)); michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCompositionString, SUCCEEDED mCompositionString=\"%s\"\n", michael@0: NS_ConvertUTF16toUTF8(mCompositionString).get())); michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength) michael@0: { michael@0: NS_ENSURE_TRUE(aOffset, false); michael@0: NS_ENSURE_TRUE(mIsComposing, false); michael@0: NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false); michael@0: michael@0: bool found = false; michael@0: *aOffset = mCompositionStart; michael@0: for (uint32_t i = 0; i < mAttributeArray.Length(); i++) { michael@0: if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED || michael@0: mAttributeArray[i] == ATTR_TARGET_CONVERTED) { michael@0: *aOffset = mCompositionStart + i; michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!aLength) { michael@0: return true; michael@0: } michael@0: michael@0: if (!found) { michael@0: // The all composition string is targetted when there is no ATTR_TARGET_* michael@0: // clause. E.g., there is only ATTR_INPUT michael@0: *aLength = mCompositionString.Length(); michael@0: return true; michael@0: } michael@0: michael@0: uint32_t offsetInComposition = *aOffset - mCompositionStart; michael@0: *aLength = mCompositionString.Length() - offsetInComposition; michael@0: for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) { michael@0: if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED && michael@0: mAttributeArray[i] != ATTR_TARGET_CONVERTED) { michael@0: *aLength = i - offsetInComposition; michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::ConvertToANSIString(const nsAFlatString& aStr, UINT aCodePage, michael@0: nsACString& aANSIStr) michael@0: { michael@0: int len = ::WideCharToMultiByte(aCodePage, 0, michael@0: (LPCWSTR)aStr.get(), aStr.Length(), michael@0: nullptr, 0, nullptr, nullptr); michael@0: NS_ENSURE_TRUE(len >= 0, false); michael@0: michael@0: if (!aANSIStr.SetLength(len, mozilla::fallible_t())) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: ConvertToANSIString, FAILED by OOM\n")); michael@0: return false; michael@0: } michael@0: ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(), michael@0: (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, michael@0: uint32_t aOffset, michael@0: nsIntRect &aCharRect) michael@0: { michael@0: nsIntPoint point(0, 0); michael@0: michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); michael@0: aWindow->InitEvent(selection, &point); michael@0: aWindow->DispatchWindowEvent(&selection); michael@0: if (!selection.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, FAILED (NS_QUERY_SELECTED_TEXT)\n", michael@0: aOffset)); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t offset = selection.mReply.mOffset + aOffset; michael@0: bool useCaretRect = selection.mReply.mString.IsEmpty(); michael@0: if (useCaretRect && ShouldDrawCompositionStringOurselves() && michael@0: mIsComposing && !mCompositionString.IsEmpty()) { michael@0: // There is not a normal selection, but we have composition string. michael@0: // XXX mnakano - Should we implement NS_QUERY_IME_SELECTED_TEXT? michael@0: useCaretRect = false; michael@0: if (mCursorPosition != NO_IME_CARET) { michael@0: uint32_t cursorPosition = michael@0: std::min(mCursorPosition, mCompositionString.Length()); michael@0: NS_ASSERTION(offset >= cursorPosition, "offset is less than cursorPosition!"); michael@0: offset -= cursorPosition; michael@0: } michael@0: } michael@0: michael@0: nsIntRect r; michael@0: if (!useCaretRect) { michael@0: WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, aWindow); michael@0: charRect.InitForQueryTextRect(offset, 1); michael@0: aWindow->InitEvent(charRect, &point); michael@0: aWindow->DispatchWindowEvent(&charRect); michael@0: if (charRect.mSucceeded) { michael@0: aCharRect = charRect.mReply.mRect; michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n", michael@0: aOffset)); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", michael@0: aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height)); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return GetCaretRect(aWindow, aCharRect); michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect) michael@0: { michael@0: nsIntPoint point(0, 0); michael@0: michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); michael@0: aWindow->InitEvent(selection, &point); michael@0: aWindow->DispatchWindowEvent(&selection); michael@0: if (!selection.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCaretRect, FAILED (NS_QUERY_SELECTED_TEXT)\n")); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t offset = selection.mReply.mOffset; michael@0: michael@0: WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, aWindow); michael@0: caretRect.InitForQueryCaretRect(offset); michael@0: aWindow->InitEvent(caretRect, &point); michael@0: aWindow->DispatchWindowEvent(&caretRect); michael@0: if (!caretRect.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCaretRect, FAILED (NS_QUERY_CARET_RECT)\n")); michael@0: return false; michael@0: } michael@0: aCaretRect = caretRect.mReply.mRect; michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", michael@0: aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow, michael@0: const nsIMEContext &aIMEContext) michael@0: { michael@0: nsIntRect r; michael@0: // Get first character rect of current a normal selected text or a composing michael@0: // string. michael@0: bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); michael@0: NS_ENSURE_TRUE(ret, false); michael@0: nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); michael@0: nsIntRect firstSelectedCharRect; michael@0: ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect); michael@0: michael@0: // Set native caret size/position to our caret. Some IMEs honor it. E.g., michael@0: // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified michael@0: // Chinese) on XP. michael@0: nsIntRect caretRect(firstSelectedCharRect); michael@0: if (GetCaretRect(aWindow, r)) { michael@0: ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect); michael@0: } else { michael@0: NS_WARNING("failed to get caret rect"); michael@0: caretRect.width = 1; michael@0: } michael@0: if (!mNativeCaretIsCreated) { michael@0: mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr, michael@0: caretRect.width, caretRect.height); michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, width=%ld height=%ld\n", michael@0: mNativeCaretIsCreated ? "TRUE" : "FALSE", michael@0: caretRect.width, caretRect.height)); michael@0: } michael@0: ::SetCaretPos(caretRect.x, caretRect.y); michael@0: michael@0: if (ShouldDrawCompositionStringOurselves()) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n")); michael@0: michael@0: // Get a rect of first character in current target in composition string. michael@0: if (mIsComposing && !mCompositionString.IsEmpty()) { michael@0: // If there are no targetted selection, we should use it's first character michael@0: // rect instead. michael@0: uint32_t offset; michael@0: if (!GetTargetClauseRange(&offset)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n")); michael@0: return false; michael@0: } michael@0: ret = GetCharacterRectOfSelectedTextAt(aWindow, michael@0: offset - mCompositionStart, r); michael@0: NS_ENSURE_TRUE(ret, false); michael@0: } else { michael@0: // If there are no composition string, we should use a first character michael@0: // rect. michael@0: ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); michael@0: NS_ENSURE_TRUE(ret, false); michael@0: } michael@0: nsIntRect firstTargetCharRect; michael@0: ResolveIMECaretPos(toplevelWindow, r, aWindow, firstTargetCharRect); michael@0: michael@0: // Move the candidate window to first character position of the target. michael@0: CANDIDATEFORM candForm; michael@0: candForm.dwIndex = 0; michael@0: candForm.dwStyle = CFS_EXCLUDE; michael@0: candForm.ptCurrentPos.x = firstTargetCharRect.x; michael@0: candForm.ptCurrentPos.y = firstTargetCharRect.y; michael@0: candForm.rcArea.right = candForm.rcArea.left = candForm.ptCurrentPos.x; michael@0: candForm.rcArea.top = candForm.ptCurrentPos.y; michael@0: candForm.rcArea.bottom = candForm.ptCurrentPos.y + michael@0: firstTargetCharRect.height; michael@0: ::ImmSetCandidateWindow(aIMEContext.get(), &candForm); michael@0: } else { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPos, Set composition window\n")); michael@0: michael@0: // Move the composition window to caret position (if selected some michael@0: // characters, we should use first character rect of them). michael@0: // And in this mode, IME adjusts the candidate window position michael@0: // automatically. So, we don't need to set it. michael@0: COMPOSITIONFORM compForm; michael@0: compForm.dwStyle = CFS_POINT; michael@0: compForm.ptCurrentPos.x = firstSelectedCharRect.x; michael@0: compForm.ptCurrentPos.y = firstSelectedCharRect.y; michael@0: ::ImmSetCompositionWindow(aIMEContext.get(), &compForm); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsIMM32Handler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, michael@0: const nsIMEContext& aIMEContext) michael@0: { michael@0: WidgetQueryContentEvent editorRectEvent(true, NS_QUERY_EDITOR_RECT, aWindow); michael@0: aWindow->InitEvent(editorRectEvent); michael@0: aWindow->DispatchWindowEvent(&editorRectEvent); michael@0: if (!editorRectEvent.mSucceeded) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPosOnPlugin, " michael@0: "FAILED (NS_QUERY_EDITOR_RECT)")); michael@0: return; michael@0: } michael@0: michael@0: // Clip the plugin rect by the client rect of the window because composition michael@0: // window needs to be specified the position in the client area. michael@0: nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); michael@0: nsIntRect pluginRectInScreen = michael@0: editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset(); michael@0: nsIntRect winRectInScreen; michael@0: aWindow->GetClientBounds(winRectInScreen); michael@0: // composition window cannot be positioned on the edge of client area. michael@0: winRectInScreen.width--; michael@0: winRectInScreen.height--; michael@0: nsIntRect clippedPluginRect; michael@0: clippedPluginRect.x = michael@0: std::min(std::max(pluginRectInScreen.x, winRectInScreen.x), michael@0: winRectInScreen.XMost()); michael@0: clippedPluginRect.y = michael@0: std::min(std::max(pluginRectInScreen.y, winRectInScreen.y), michael@0: winRectInScreen.YMost()); michael@0: int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost()); michael@0: int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost()); michael@0: clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x); michael@0: clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y); michael@0: clippedPluginRect -= aWindow->WidgetToScreenOffset(); michael@0: michael@0: // Cover the plugin with native caret. This prevents IME's window and plugin michael@0: // overlap. michael@0: if (mNativeCaretIsCreated) { michael@0: ::DestroyCaret(); michael@0: } michael@0: mNativeCaretIsCreated = michael@0: ::CreateCaret(aWindow->GetWindowHandle(), nullptr, michael@0: clippedPluginRect.width, clippedPluginRect.height); michael@0: ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y); michael@0: michael@0: // Set the composition window to bottom-left of the clipped plugin. michael@0: // As far as we know, there is no IME for RTL language. Therefore, this code michael@0: // must not need to take care of RTL environment. michael@0: COMPOSITIONFORM compForm; michael@0: compForm.dwStyle = CFS_POINT; michael@0: compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x; michael@0: compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y; michael@0: if (!::ImmSetCompositionWindow(aIMEContext.get(), &compForm)) { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: SetIMERelatedWindowsPosOnPlugin, " michael@0: "FAILED to set composition window")); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsIMM32Handler::ResolveIMECaretPos(nsIWidget* aReferenceWidget, michael@0: nsIntRect& aCursorRect, michael@0: nsIWidget* aNewOriginWidget, michael@0: nsIntRect& aOutRect) michael@0: { michael@0: aOutRect = aCursorRect; michael@0: michael@0: if (aReferenceWidget == aNewOriginWidget) michael@0: return; michael@0: michael@0: if (aReferenceWidget) michael@0: aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset()); michael@0: michael@0: if (aNewOriginWidget) michael@0: aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset()); michael@0: } michael@0: michael@0: bool michael@0: nsIMM32Handler::OnMouseEvent(nsWindow* aWindow, LPARAM lParam, int aAction, michael@0: MSGResult& aResult) michael@0: { michael@0: aResult.mConsumed = false; // always call next wndprc michael@0: michael@0: if (!sWM_MSIME_MOUSE || !mIsComposing || michael@0: !ShouldDrawCompositionStringOurselves()) { michael@0: return false; michael@0: } michael@0: michael@0: nsIntPoint cursor(LOWORD(lParam), HIWORD(lParam)); michael@0: WidgetQueryContentEvent charAtPt(true, NS_QUERY_CHARACTER_AT_POINT, aWindow); michael@0: aWindow->InitEvent(charAtPt, &cursor); michael@0: aWindow->DispatchWindowEvent(&charAtPt); michael@0: if (!charAtPt.mSucceeded || michael@0: charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND || michael@0: charAtPt.mReply.mOffset < mCompositionStart || michael@0: charAtPt.mReply.mOffset > michael@0: mCompositionStart + mCompositionString.Length()) { michael@0: return false; michael@0: } michael@0: michael@0: // calcurate positioning and offset michael@0: // char : JCH1|JCH2|JCH3 michael@0: // offset: 0011 1122 2233 michael@0: // positioning: 2301 2301 2301 michael@0: nsIntRect cursorInTopLevel, cursorRect(cursor, nsIntSize(0, 0)); michael@0: ResolveIMECaretPos(aWindow, cursorRect, michael@0: aWindow->GetTopLevelWindow(false), cursorInTopLevel); michael@0: int32_t cursorXInChar = cursorInTopLevel.x - charAtPt.mReply.mRect.x; michael@0: // The event might hit to zero-width character, see bug 694913. michael@0: // The reason might be: michael@0: // * There are some zero-width characters are actually. michael@0: // * font-size is specified zero. michael@0: // But nobody reproduced this bug actually... michael@0: // We should assume that user clicked on right most of the zero-width michael@0: // character in such case. michael@0: int positioning = 1; michael@0: if (charAtPt.mReply.mRect.width > 0) { michael@0: positioning = cursorXInChar * 4 / charAtPt.mReply.mRect.width; michael@0: positioning = (positioning + 2) % 4; michael@0: } michael@0: michael@0: int offset = charAtPt.mReply.mOffset - mCompositionStart; michael@0: if (positioning < 2) { michael@0: offset++; michael@0: } michael@0: michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnMouseEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld\n", michael@0: cursor.x, cursor.y, offset, positioning)); michael@0: michael@0: // send MS_MSIME_MOUSE message to default IME window. michael@0: HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle()); michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: return ::SendMessageW(imeWnd, sWM_MSIME_MOUSE, michael@0: MAKELONG(MAKEWORD(aAction, positioning), offset), michael@0: (LPARAM) IMEContext.get()) == 1; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsIMM32Handler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, michael@0: MSGResult& aResult) michael@0: { michael@0: PR_LOG(gIMM32Log, PR_LOG_ALWAYS, michael@0: ("IMM32: OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x\n", michael@0: aWindow->GetWindowHandle(), wParam, lParam)); michael@0: aResult.mConsumed = false; michael@0: switch (wParam) { michael@0: case VK_TAB: michael@0: case VK_PRIOR: michael@0: case VK_NEXT: michael@0: case VK_END: michael@0: case VK_HOME: michael@0: case VK_LEFT: michael@0: case VK_UP: michael@0: case VK_RIGHT: michael@0: case VK_DOWN: michael@0: // If IME didn't process the key message (the virtual key code wasn't michael@0: // converted to VK_PROCESSKEY), and the virtual key code event causes michael@0: // to move caret, we should cancel the composition here. Then, this michael@0: // event will be dispatched. michael@0: // XXX I think that we should dispatch all key events during composition, michael@0: // and nsEditor should cancel/commit the composition if it *thinks* michael@0: // it's needed. michael@0: if (IsComposingOnOurEditor()) { michael@0: // NOTE: We don't need to cancel the composition on another window. michael@0: CancelComposition(aWindow, false); michael@0: } michael@0: return false; michael@0: default: michael@0: return false; michael@0: } michael@0: }