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