mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-29 21:25:35 +00:00
79b5a1a459
MozReview-Commit-ID: 52ccdKvzVbK
2948 lines
100 KiB
C++
2948 lines
100 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/. */
|
|
|
|
#include "mozilla/Logging.h"
|
|
|
|
#include "IMMHandler.h"
|
|
#include "nsWindow.h"
|
|
#include "nsWindowDefs.h"
|
|
#include "WinUtils.h"
|
|
#include "KeyboardLayout.h"
|
|
#include <algorithm>
|
|
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/MiscEvents.h"
|
|
#include "mozilla/TextEvents.h"
|
|
|
|
#ifndef IME_PROP_ACCEPT_WIDE_VKEY
|
|
#define IME_PROP_ACCEPT_WIDE_VKEY 0x20
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
// 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
|
|
|
|
static const char*
|
|
GetBoolName(bool aBool)
|
|
{
|
|
return aBool ? "true" : "false";
|
|
}
|
|
|
|
static void
|
|
HandleSeparator(nsACString& aDesc)
|
|
{
|
|
if (!aDesc.IsEmpty()) {
|
|
aDesc.AppendLiteral(" | ");
|
|
}
|
|
}
|
|
|
|
class GetIMEGeneralPropertyName : public nsAutoCString
|
|
{
|
|
public:
|
|
GetIMEGeneralPropertyName(DWORD aFlags)
|
|
{
|
|
if (!aFlags) {
|
|
AppendLiteral("no flags");
|
|
return;
|
|
}
|
|
if (aFlags & IME_PROP_AT_CARET) {
|
|
AppendLiteral("IME_PROP_AT_CARET");
|
|
}
|
|
if (aFlags & IME_PROP_SPECIAL_UI) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("IME_PROP_SPECIAL_UI");
|
|
}
|
|
if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
|
|
}
|
|
if (aFlags & IME_PROP_UNICODE) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("IME_PROP_UNICODE");
|
|
}
|
|
if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
|
|
}
|
|
if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
|
|
}
|
|
}
|
|
virtual ~GetIMEGeneralPropertyName() {}
|
|
};
|
|
|
|
class GetIMEUIPropertyName : public nsAutoCString
|
|
{
|
|
public:
|
|
GetIMEUIPropertyName(DWORD aFlags)
|
|
{
|
|
if (!aFlags) {
|
|
AppendLiteral("no flags");
|
|
return;
|
|
}
|
|
if (aFlags & UI_CAP_2700) {
|
|
AppendLiteral("UI_CAP_2700");
|
|
}
|
|
if (aFlags & UI_CAP_ROT90) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("UI_CAP_ROT90");
|
|
}
|
|
if (aFlags & UI_CAP_ROTANY) {
|
|
HandleSeparator(*this);
|
|
AppendLiteral("UI_CAP_ROTANY");
|
|
}
|
|
}
|
|
virtual ~GetIMEUIPropertyName() {}
|
|
};
|
|
|
|
class GetWritingModeName : public nsAutoCString
|
|
{
|
|
public:
|
|
GetWritingModeName(const WritingMode& aWritingMode)
|
|
{
|
|
if (!aWritingMode.IsVertical()) {
|
|
Assign("Horizontal");
|
|
return;
|
|
}
|
|
if (aWritingMode.IsVerticalLR()) {
|
|
Assign("Vertical (LR)");
|
|
return;
|
|
}
|
|
Assign("Vertical (RL)");
|
|
}
|
|
virtual ~GetWritingModeName() {}
|
|
};
|
|
|
|
class GetReconvertStringLog : public nsAutoCString
|
|
{
|
|
public:
|
|
GetReconvertStringLog(RECONVERTSTRING* aReconv)
|
|
{
|
|
AssignLiteral("{ dwSize=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwSize));
|
|
AppendLiteral(", dwVersion=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwVersion));
|
|
AppendLiteral(", dwStrLen=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwStrLen));
|
|
AppendLiteral(", dwStrOffset=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset));
|
|
AppendLiteral(", dwCompStrLen=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen));
|
|
AppendLiteral(", dwCompStrOffset=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset));
|
|
AppendLiteral(", dwTargetStrLen=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen));
|
|
AppendLiteral(", dwTargetStrOffset=");
|
|
AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset));
|
|
AppendLiteral(", result str=\"");
|
|
if (aReconv->dwStrLen) {
|
|
char16_t* strStart =
|
|
reinterpret_cast<char16_t*>(
|
|
reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset);
|
|
nsDependentString str(strStart, aReconv->dwStrLen);
|
|
Append(NS_ConvertUTF16toUTF8(str));
|
|
}
|
|
AppendLiteral("\" }");
|
|
}
|
|
virtual ~GetReconvertStringLog() {}
|
|
};
|
|
|
|
namespace mozilla {
|
|
namespace widget {
|
|
|
|
static IMMHandler* gIMMHandler = nullptr;
|
|
|
|
PRLogModuleInfo* gIMMLog = nullptr;
|
|
|
|
/******************************************************************************
|
|
* IMEContext
|
|
******************************************************************************/
|
|
|
|
IMEContext::IMEContext(HWND aWnd)
|
|
: mWnd(aWnd)
|
|
, mIMC(::ImmGetContext(aWnd))
|
|
{
|
|
}
|
|
|
|
IMEContext::IMEContext(nsWindow* aWindow)
|
|
: mWnd(aWindow->GetWindowHandle())
|
|
, mIMC(::ImmGetContext(aWindow->GetWindowHandle()))
|
|
{
|
|
}
|
|
|
|
void
|
|
IMEContext::Init(HWND aWnd)
|
|
{
|
|
Clear();
|
|
mWnd = aWnd;
|
|
mIMC = ::ImmGetContext(mWnd);
|
|
}
|
|
|
|
void
|
|
IMEContext::Init(nsWindow* aWindow)
|
|
{
|
|
Init(aWindow->GetWindowHandle());
|
|
}
|
|
|
|
void
|
|
IMEContext::Clear()
|
|
{
|
|
if (mWnd && mIMC) {
|
|
::ImmReleaseContext(mWnd, mIMC);
|
|
}
|
|
mWnd = nullptr;
|
|
mIMC = nullptr;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* IMMHandler
|
|
******************************************************************************/
|
|
|
|
static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000
|
|
|
|
WritingMode IMMHandler::sWritingModeOfCompositionFont;
|
|
nsString IMMHandler::sIMEName;
|
|
UINT IMMHandler::sCodePage = 0;
|
|
DWORD IMMHandler::sIMEProperty = 0;
|
|
DWORD IMMHandler::sIMEUIProperty = 0;
|
|
bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false;
|
|
bool IMMHandler::sHasFocus = false;
|
|
bool IMMHandler::sNativeCaretIsCreatedForPlugin = false;
|
|
|
|
// static
|
|
void
|
|
IMMHandler::EnsureHandlerInstance()
|
|
{
|
|
if (!gIMMHandler) {
|
|
gIMMHandler = new IMMHandler();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::Initialize()
|
|
{
|
|
if (!gIMMLog) {
|
|
gIMMLog = PR_NewLogModule("nsIMM32HandlerWidgets");
|
|
}
|
|
if (!sWM_MSIME_MOUSE) {
|
|
sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
|
|
}
|
|
sAssumeVerticalWritingModeNotSupported =
|
|
Preferences::GetBool(
|
|
"intl.imm.vertical_writing.always_assume_not_supported", false);
|
|
InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::Terminate()
|
|
{
|
|
if (!gIMMHandler)
|
|
return;
|
|
delete gIMMHandler;
|
|
gIMMHandler = nullptr;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsComposingOnOurEditor()
|
|
{
|
|
return gIMMHandler && gIMMHandler->mIsComposing;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsComposingOnPlugin()
|
|
{
|
|
return gIMMHandler && gIMMHandler->mIsComposingOnPlugin;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsComposingWindow(nsWindow* aWindow)
|
|
{
|
|
return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow)
|
|
{
|
|
if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
|
|
return false;
|
|
}
|
|
HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
|
|
return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsJapanist2003Active()
|
|
{
|
|
return sIMEName.EqualsLiteral("Japanist 2003");
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::IsGoogleJapaneseInputActive()
|
|
{
|
|
// NOTE: Even on Windows for en-US, the name of Google Japanese Input is
|
|
// written in Japanese.
|
|
return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B "
|
|
L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB");
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::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
|
|
bool
|
|
IMMHandler::IsVerticalWritingSupported()
|
|
{
|
|
// Even if IME claims that they support vertical writing mode but it may not
|
|
// support vertical writing mode for its candidate window.
|
|
if (sAssumeVerticalWritingModeNotSupported) {
|
|
return false;
|
|
}
|
|
// Google Japanese Input doesn't support vertical writing mode. We should
|
|
// return false if it's active IME.
|
|
if (IsGoogleJapaneseInputActive()) {
|
|
return false;
|
|
}
|
|
return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::InitKeyboardLayout(nsWindow* aWindow,
|
|
HKL aKeyboardLayout)
|
|
{
|
|
UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
|
|
if (IMENameLength) {
|
|
// Add room for the terminating null character
|
|
sIMEName.SetLength(++IMENameLength);
|
|
IMENameLength =
|
|
::ImmGetDescriptionW(aKeyboardLayout, wwc(sIMEName.BeginWriting()),
|
|
IMENameLength);
|
|
// Adjust the length to ignore the terminating null character
|
|
sIMEName.SetLength(IMENameLength);
|
|
} else {
|
|
sIMEName.Truncate();
|
|
}
|
|
|
|
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);
|
|
sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
|
|
|
|
// If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
|
|
// For hacking some bugs of some TIP, we should set an IME name from the
|
|
// pref.
|
|
if (sCodePage == 932 && sIMEName.IsEmpty()) {
|
|
sIMEName =
|
|
Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as");
|
|
}
|
|
|
|
// Whether the IME supports vertical writing mode might be changed or
|
|
// some IMEs may need specific font for their UI. Therefore, we should
|
|
// update composition font forcibly here.
|
|
if (aWindow) {
|
|
MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
|
|
}
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
|
|
"sIMEProperty=%s, sIMEUIProperty=%s",
|
|
aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(),
|
|
sCodePage, GetIMEGeneralPropertyName(sIMEProperty).get(),
|
|
GetIMEUIPropertyName(sIMEUIProperty).get()));
|
|
}
|
|
|
|
// static
|
|
UINT
|
|
IMMHandler::GetKeyboardCodePage()
|
|
{
|
|
return sCodePage;
|
|
}
|
|
|
|
// static
|
|
nsIMEUpdatePreference
|
|
IMMHandler::GetIMEUpdatePreference()
|
|
{
|
|
return nsIMEUpdatePreference(
|
|
nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
|
|
nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
|
|
nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
|
|
}
|
|
|
|
// 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 compositionchange event.
|
|
#define NO_IME_CARET -1
|
|
|
|
IMMHandler::IMMHandler()
|
|
: mComposingWindow(nullptr)
|
|
, mCursorPosition(NO_IME_CARET)
|
|
, mCompositionStart(0)
|
|
, mIsComposing(false)
|
|
, mIsComposingOnPlugin(false)
|
|
, mNativeCaretIsCreated(false)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMM: IMMHandler is created"));
|
|
}
|
|
|
|
IMMHandler::~IMMHandler()
|
|
{
|
|
if (mIsComposing) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: ~IMMHandler, ERROR, the instance is still composing"));
|
|
}
|
|
MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMM: IMMHandler is destroyed"));
|
|
}
|
|
|
|
nsresult
|
|
IMMHandler::EnsureClauseArray(int32_t aCount)
|
|
{
|
|
NS_ENSURE_ARG_MIN(aCount, 0);
|
|
mClauseArray.SetCapacity(aCount + 32);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
IMMHandler::EnsureAttributeArray(int32_t aCount)
|
|
{
|
|
NS_ENSURE_ARG_MIN(aCount, 0);
|
|
mAttributeArray.SetCapacity(aCount + 64);
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
|
|
"mComposingWindow=%p%s",
|
|
GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
|
|
gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
|
|
gIMMHandler && gIMMHandler->mComposingWindow ?
|
|
IsComposingOnOurEditor() ? " (composing on editor)" :
|
|
" (composing on plug-in)" : ""));
|
|
if (!aForce && !IsComposingWindow(aWindow)) {
|
|
return;
|
|
}
|
|
|
|
IMEContext context(aWindow);
|
|
bool associated = context.AssociateDefaultContext();
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CommitComposition, associated=%s",
|
|
GetBoolName(associated)));
|
|
|
|
if (context.IsValid()) {
|
|
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
|
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
|
}
|
|
|
|
if (associated) {
|
|
context.Disassociate();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
|
|
"mComposingWindow=%p%s",
|
|
GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
|
|
gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
|
|
gIMMHandler && gIMMHandler->mComposingWindow ?
|
|
IsComposingOnOurEditor() ? " (composing on editor)" :
|
|
" (composing on plug-in)" : ""));
|
|
if (!aForce && !IsComposingWindow(aWindow)) {
|
|
return;
|
|
}
|
|
|
|
IMEContext context(aWindow);
|
|
bool associated = context.AssociateDefaultContext();
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CancelComposition, associated=%s",
|
|
GetBoolName(associated)));
|
|
|
|
if (context.IsValid()) {
|
|
::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
|
}
|
|
|
|
if (associated) {
|
|
context.Disassociate();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
|
|
"IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s, "
|
|
"sNativeCaretIsCreatedForPlugin=%s",
|
|
GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
|
|
GetBoolName(IsComposingWindow(aWindow)),
|
|
GetBoolName(aWindow->Destroyed()),
|
|
GetBoolName(sNativeCaretIsCreatedForPlugin)));
|
|
|
|
if (!aFocus) {
|
|
if (sNativeCaretIsCreatedForPlugin) {
|
|
::DestroyCaret();
|
|
sNativeCaretIsCreatedForPlugin = false;
|
|
}
|
|
if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
|
|
CancelComposition(aWindow);
|
|
}
|
|
}
|
|
if (gIMMHandler) {
|
|
gIMMHandler->mSelection.Clear();
|
|
}
|
|
sHasFocus = aFocus;
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::OnUpdateComposition(nsWindow* aWindow)
|
|
{
|
|
if (!gIMMHandler) {
|
|
return;
|
|
}
|
|
|
|
if (aWindow->PluginHasFocus()) {
|
|
return;
|
|
}
|
|
|
|
IMEContext context(aWindow);
|
|
gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::OnSelectionChange(nsWindow* aWindow,
|
|
const IMENotification& aIMENotification,
|
|
bool aIsIMMActive)
|
|
{
|
|
if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
|
|
aIsIMMActive) {
|
|
MaybeAdjustCompositionFont(aWindow,
|
|
aIMENotification.mSelectionChangeData.GetWritingMode());
|
|
}
|
|
// MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
|
|
// after a call of MaybeAdjustCompositionFont().
|
|
if (gIMMHandler) {
|
|
gIMMHandler->mSelection.Update(aIMENotification);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
|
|
const WritingMode& aWritingMode,
|
|
bool aForceUpdate)
|
|
{
|
|
switch (sCodePage) {
|
|
case 932: // Japanese Shift-JIS
|
|
case 936: // Simlified Chinese GBK
|
|
case 949: // Korean
|
|
case 950: // Traditional Chinese Big5
|
|
EnsureHandlerInstance();
|
|
break;
|
|
default:
|
|
// If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
|
|
if (!gIMMHandler) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Like Navi-Bar of ATOK, some IMEs may require proper composition font even
|
|
// before sending WM_IME_STARTCOMPOSITION.
|
|
IMEContext context(aWindow);
|
|
gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
|
|
aForceUpdate);
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
aResult.mResult = 0;
|
|
aResult.mConsumed = false;
|
|
// We don't need to create the instance of the handler here.
|
|
if (gIMMHandler) {
|
|
gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
|
|
}
|
|
InitKeyboardLayout(aWindow, 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
|
|
IMMHandler::ProcessMessage(nsWindow* aWindow,
|
|
UINT msg,
|
|
WPARAM& wParam,
|
|
LPARAM& lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
// 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.
|
|
|
|
// When a plug-in has focus, we should dispatch the IME events to
|
|
// the plug-in at first.
|
|
if (aWindow->PluginHasFocus()) {
|
|
bool ret = false;
|
|
if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
aResult.mResult = 0;
|
|
switch (msg) {
|
|
case WM_INPUTLANGCHANGE:
|
|
return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_STARTCOMPOSITION:
|
|
EnsureHandlerInstance();
|
|
return gIMMHandler->OnIMEStartComposition(aWindow, aResult);
|
|
case WM_IME_COMPOSITION:
|
|
EnsureHandlerInstance();
|
|
return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_ENDCOMPOSITION:
|
|
EnsureHandlerInstance();
|
|
return gIMMHandler->OnIMEEndComposition(aWindow, aResult);
|
|
case WM_IME_CHAR:
|
|
return OnIMEChar(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_NOTIFY:
|
|
return OnIMENotify(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_REQUEST:
|
|
EnsureHandlerInstance();
|
|
return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_SELECT:
|
|
return OnIMESelect(aWindow, wParam, lParam, aResult);
|
|
case WM_IME_SETCONTEXT:
|
|
return OnIMESetContext(aWindow, wParam, lParam, aResult);
|
|
case WM_KEYDOWN:
|
|
return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
|
|
case WM_CHAR:
|
|
if (!gIMMHandler) {
|
|
return false;
|
|
}
|
|
return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
|
|
default:
|
|
return false;
|
|
};
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow,
|
|
UINT msg,
|
|
WPARAM& wParam,
|
|
LPARAM& lParam,
|
|
bool& aRet,
|
|
MSGResult& aResult)
|
|
{
|
|
aResult.mResult = 0;
|
|
aResult.mConsumed = false;
|
|
switch (msg) {
|
|
case WM_INPUTLANGCHANGEREQUEST:
|
|
case WM_INPUTLANGCHANGE:
|
|
aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
|
|
aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
|
|
return true;
|
|
case WM_IME_CHAR:
|
|
EnsureHandlerInstance();
|
|
aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
|
|
return true;
|
|
case WM_IME_SETCONTEXT:
|
|
aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
|
|
return true;
|
|
case WM_CHAR:
|
|
if (!gIMMHandler) {
|
|
return true;
|
|
}
|
|
aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
|
|
return true;
|
|
case WM_IME_COMPOSITIONFULL:
|
|
case WM_IME_CONTROL:
|
|
case WM_IME_KEYDOWN:
|
|
case WM_IME_KEYUP:
|
|
case WM_IME_SELECT:
|
|
aResult.mConsumed =
|
|
aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
|
|
aRet = true;
|
|
return true;
|
|
case WM_IME_REQUEST:
|
|
// Our plugin implementation is alwasy OOP. So WM_IME_REQUEST doesn't
|
|
// allow that parameter is pointer and shouldn't handle into Gecko.
|
|
aRet = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* message handlers
|
|
****************************************************************************/
|
|
|
|
void
|
|
IMMHandler::OnInputLangChange(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam));
|
|
|
|
aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
|
|
NS_ASSERTION(!mIsComposing, "ResetInputState failed");
|
|
|
|
if (mIsComposing) {
|
|
HandleEndComposition(aWindow);
|
|
}
|
|
|
|
aResult.mConsumed = false;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnIMEStartComposition(nsWindow* aWindow,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEStartComposition, hWnd=%08x, mIsComposing=%s",
|
|
aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
|
|
aResult.mConsumed = ShouldDrawCompositionStringOurselves();
|
|
if (mIsComposing) {
|
|
NS_WARNING("Composition has been already started");
|
|
return true;
|
|
}
|
|
|
|
IMEContext context(aWindow);
|
|
HandleStartComposition(aWindow, context);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnIMEComposition(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, "
|
|
"GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
|
|
"GCS_CURSORPOS=%s,",
|
|
aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing),
|
|
GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
|
|
GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
|
|
GetBoolName(lParam & GCS_CURSORPOS)));
|
|
|
|
IMEContext context(aWindow);
|
|
aResult.mConsumed = HandleComposition(aWindow, context, lParam);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnIMEEndComposition(nsWindow* aWindow,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEEndComposition, hWnd=%08x, mIsComposing=%s",
|
|
aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
|
|
|
|
aResult.mConsumed = ShouldDrawCompositionStringOurselves();
|
|
if (!mIsComposing) {
|
|
return true;
|
|
}
|
|
|
|
// 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 (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
|
|
WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
|
|
PM_NOREMOVE) &&
|
|
compositionMsg.message == WM_IME_COMPOSITION &&
|
|
IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
|
|
"WM_IME_COMPOSITION, ignoring the message..."));
|
|
return true;
|
|
}
|
|
|
|
// 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 compositionchange
|
|
// event and a compositionend event.
|
|
// XXX Shouldn't we dispatch the compositionchange event with actual or
|
|
// latest composition string?
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEEndComposition, mCompositionString=\"%s\"%s",
|
|
NS_ConvertUTF16toUTF8(mCompositionString).get(),
|
|
mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
|
|
|
|
HandleEndComposition(aWindow, &EmptyString());
|
|
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMEChar(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEChar, hWnd=%08x, char=%08x",
|
|
aWindow->GetWindowHandle(), wParam));
|
|
|
|
// We don't need to fire any compositionchange 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 consume the message so that Windows don't send two WM_CHAR msgs
|
|
aResult.mConsumed = true;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMECompositionFull(nsWindow* aWindow,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMECompositionFull, hWnd=%08x",
|
|
aWindow->GetWindowHandle()));
|
|
|
|
// not implement yet
|
|
aResult.mConsumed = false;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMENotify(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
switch (wParam) {
|
|
case IMN_CHANGECANDIDATE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x",
|
|
aWindow->GetWindowHandle(), lParam));
|
|
break;
|
|
case IMN_CLOSECANDIDATE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x",
|
|
aWindow->GetWindowHandle(), lParam));
|
|
break;
|
|
case IMN_CLOSESTATUSWINDOW:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_GUIDELINE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_OPENCANDIDATE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
|
|
aWindow->GetWindowHandle(), lParam));
|
|
break;
|
|
case IMN_OPENSTATUSWINDOW:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETCANDIDATEPOS:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x",
|
|
aWindow->GetWindowHandle(), lParam));
|
|
break;
|
|
case IMN_SETCOMPOSITIONFONT:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETCOMPOSITIONWINDOW:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETCONVERSIONMODE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETOPENSTATUS:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETSENTENCEMODE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_SETSTATUSWINDOWPOS:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
case IMN_PRIVATE:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMENotify, hWnd=%08x, IMN_PRIVATE",
|
|
aWindow->GetWindowHandle()));
|
|
break;
|
|
}
|
|
|
|
// not implement yet
|
|
aResult.mConsumed = false;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnIMERequest(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
switch (wParam) {
|
|
case IMR_RECONVERTSTRING:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
|
|
aWindow->GetWindowHandle()));
|
|
aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
|
|
return true;
|
|
case IMR_QUERYCHARPOSITION:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
|
|
aWindow->GetWindowHandle()));
|
|
aResult.mConsumed =
|
|
HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
|
|
return true;
|
|
case IMR_DOCUMENTFEED:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
|
|
aWindow->GetWindowHandle()));
|
|
aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
|
|
return true;
|
|
default:
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMERequest, hWnd=%08x, wParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam));
|
|
aResult.mConsumed = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMESelect(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam));
|
|
|
|
// not implement yet
|
|
aResult.mConsumed = false;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMESetContext(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
|
|
|
|
aResult.mConsumed = false;
|
|
|
|
// 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)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMESetContext, hWnd=%08x is top level window"));
|
|
return true;
|
|
}
|
|
|
|
// When IME context is activating on another window,
|
|
// we should commit the old composition on the old window.
|
|
bool cancelComposition = false;
|
|
if (wParam && gIMMHandler) {
|
|
cancelComposition =
|
|
gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
|
|
}
|
|
|
|
if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
|
|
ShouldDrawCompositionStringOurselves()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed"));
|
|
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.mResult = ::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);
|
|
}
|
|
|
|
aResult.mConsumed = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnChar(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
// The return value must be same as aResult.mConsumed because only when we
|
|
// consume the message, the caller shouldn't do anything anymore but
|
|
// otherwise, the caller should handle the message.
|
|
aResult.mConsumed = false;
|
|
if (IsIMECharRecordsEmpty()) {
|
|
return aResult.mConsumed;
|
|
}
|
|
WPARAM recWParam;
|
|
LPARAM recLParam;
|
|
DequeueIMECharRecords(recWParam, recLParam);
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
|
|
"recorded: wParam=%08x, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam, 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 aResult.mConsumed;
|
|
}
|
|
// 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.
|
|
aResult.mConsumed = true;
|
|
return aResult.mConsumed;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* message handlers for plug-in
|
|
****************************************************************************/
|
|
|
|
void
|
|
IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
|
|
aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
|
|
mIsComposingOnPlugin = true;
|
|
mDispatcher = GetTextEventDispatcherFor(aWindow);
|
|
mComposingWindow = aWindow;
|
|
IMEContext context(aWindow);
|
|
SetIMERelatedWindowsPosOnPlugin(aWindow, context);
|
|
// On widnowless plugin, we should assume that the focused editor is always
|
|
// in horizontal writing mode.
|
|
AdjustCompositionFont(aWindow, context, WritingMode());
|
|
}
|
|
|
|
void
|
|
IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, "
|
|
"mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, "
|
|
"GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s",
|
|
aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin),
|
|
GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
|
|
GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
|
|
GetBoolName(lParam & GCS_CURSORPOS)));
|
|
// We should end composition if there is a committed string.
|
|
if (IS_COMMITTING_LPARAM(lParam)) {
|
|
mIsComposingOnPlugin = false;
|
|
mComposingWindow = nullptr;
|
|
mDispatcher = nullptr;
|
|
return;
|
|
}
|
|
// Continue composition if there is still a string being composed.
|
|
if (IS_COMPOSING_LPARAM(lParam)) {
|
|
mIsComposingOnPlugin = true;
|
|
mDispatcher = GetTextEventDispatcherFor(aWindow);
|
|
mComposingWindow = aWindow;
|
|
IMEContext context(aWindow);
|
|
SetIMERelatedWindowsPosOnPlugin(aWindow, context);
|
|
}
|
|
}
|
|
|
|
void
|
|
IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
|
|
aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
|
|
|
|
mIsComposingOnPlugin = false;
|
|
mComposingWindow = nullptr;
|
|
mDispatcher = nullptr;
|
|
|
|
if (mNativeCaretIsCreated) {
|
|
::DestroyCaret();
|
|
mNativeCaretIsCreated = false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam));
|
|
|
|
aResult.mConsumed =
|
|
aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
|
|
|
|
if (!aResult.mConsumed) {
|
|
// Record the WM_CHAR messages which are going to be coming.
|
|
EnsureHandlerInstance();
|
|
EnqueueIMECharRecords(wParam, lParam);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x",
|
|
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 && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) {
|
|
if (gIMMHandler->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.mResult = ::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)
|
|
aResult.mConsumed = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::OnCharOnPlugin(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
NS_WARNING("OnCharOnPlugin");
|
|
if (mIsComposing) {
|
|
aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
|
|
return true;
|
|
}
|
|
|
|
// We should never consume char message on windowless plugin.
|
|
aResult.mConsumed = false;
|
|
if (IsIMECharRecordsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
WPARAM recWParam;
|
|
LPARAM recLParam;
|
|
DequeueIMECharRecords(recWParam, recLParam);
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x, "
|
|
"recorded: wParam=%08x, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam, 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
|
|
****************************************************************************/
|
|
|
|
TextEventDispatcher*
|
|
IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow)
|
|
{
|
|
return aWindow == mComposingWindow && mDispatcher ?
|
|
mDispatcher.get() : aWindow->GetTextEventDispatcher();
|
|
}
|
|
|
|
void
|
|
IMMHandler::HandleStartComposition(nsWindow* aWindow,
|
|
const IMEContext& aContext)
|
|
{
|
|
NS_PRECONDITION(!mIsComposing,
|
|
"HandleStartComposition is called but mIsComposing is TRUE");
|
|
|
|
Selection& selection = GetSelection();
|
|
if (!selection.EnsureValidSelection(aWindow)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleStartComposition, FAILED, due to "
|
|
"Selection::EnsureValidSelection() failure"));
|
|
return;
|
|
}
|
|
|
|
AdjustCompositionFont(aWindow, aContext, selection.mWritingMode);
|
|
|
|
mCompositionStart = selection.mOffset;
|
|
mCursorPosition = NO_IME_CARET;
|
|
|
|
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
|
|
nsresult rv = dispatcher->BeginNativeInputTransaction();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleStartComposition, FAILED due to "
|
|
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
|
|
return;
|
|
}
|
|
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
|
|
nsEventStatus status;
|
|
rv = dispatcher->StartComposition(status, &eventTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleStartComposition, FAILED, due to "
|
|
"TextEventDispatcher::StartComposition() failure"));
|
|
return;
|
|
}
|
|
|
|
mIsComposing = true;
|
|
mComposingWindow = aWindow;
|
|
mDispatcher = dispatcher;
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleStartComposition, START composition, mCompositionStart=%ld",
|
|
mCompositionStart));
|
|
}
|
|
|
|
bool
|
|
IMMHandler::HandleComposition(nsWindow* aWindow,
|
|
const IMEContext& aContext,
|
|
LPARAM lParam)
|
|
{
|
|
// 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 (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
|
|
WM_IME_COMPOSITION, PM_NOREMOVE) &&
|
|
msg1.message == WM_IME_STARTCOMPOSITION &&
|
|
WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
|
|
WM_IME_COMPOSITION, PM_NOREMOVE) &&
|
|
msg2.message == WM_IME_COMPOSITION) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, Ignores due to find a "
|
|
"WM_IME_STARTCOMPOSITION"));
|
|
return ShouldDrawCompositionStringOurselves();
|
|
}
|
|
}
|
|
|
|
bool startCompositionMessageHasBeenSent = mIsComposing;
|
|
|
|
//
|
|
// This catches a fixed result
|
|
//
|
|
if (IS_COMMITTING_LPARAM(lParam)) {
|
|
if (!mIsComposing) {
|
|
HandleStartComposition(aWindow, aContext);
|
|
}
|
|
|
|
GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString);
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_RESULTSTR"));
|
|
|
|
HandleEndComposition(aWindow, &mCompositionString);
|
|
|
|
if (!IS_COMPOSING_LPARAM(lParam)) {
|
|
return ShouldDrawCompositionStringOurselves();
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// This provides us with a composition string
|
|
//
|
|
if (!mIsComposing) {
|
|
HandleStartComposition(aWindow, aContext);
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// 1. Get GCS_COMPSTR
|
|
//--------------------------------------------------------
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_COMPSTR"));
|
|
|
|
nsAutoString previousCompositionString(mCompositionString);
|
|
GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
|
|
|
|
if (!IS_COMPOSING_LPARAM(lParam)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, lParam doesn't indicate composing, "
|
|
"mCompositionString=\"%s\", previousCompositionString=\"%s\"",
|
|
NS_ConvertUTF16toUTF8(mCompositionString).get(),
|
|
NS_ConvertUTF16toUTF8(previousCompositionString).get()));
|
|
|
|
// If composition string isn't changed, we can trust the lParam.
|
|
// So, we need to do nothing.
|
|
if (previousCompositionString == 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 compositionchange
|
|
// event with empty string and clear the clause information.
|
|
if (mCompositionString.IsEmpty()) {
|
|
mClauseArray.Clear();
|
|
mAttributeArray.Clear();
|
|
mCursorPosition = 0;
|
|
DispatchCompositionChangeEvent(aWindow, aContext);
|
|
return ShouldDrawCompositionStringOurselves();
|
|
}
|
|
|
|
// Otherwise, we cannot trust the lParam value. We might need to
|
|
// dispatch compositionchange 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.
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, Aborting GCS_COMPSTR"));
|
|
HandleEndComposition(aWindow);
|
|
return IS_COMMITTING_LPARAM(lParam);
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// 2. Get GCS_COMPCLAUSE
|
|
//--------------------------------------------------------
|
|
long clauseArrayLength =
|
|
::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 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);
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
|
|
useA_API ? "TRUE" : "FALSE"));
|
|
|
|
long clauseArrayLength2 =
|
|
useA_API ?
|
|
::ImmGetCompositionStringA(aContext.get(), GCS_COMPCLAUSE,
|
|
mClauseArray.Elements(),
|
|
mClauseArray.Capacity() * sizeof(uint32_t)) :
|
|
::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE,
|
|
mClauseArray.Elements(),
|
|
mClauseArray.Capacity() * sizeof(uint32_t));
|
|
clauseArrayLength2 /= sizeof(uint32_t);
|
|
|
|
if (clauseArrayLength != clauseArrayLength2) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but "
|
|
"clauseArrayLength2=%ld",
|
|
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, nullptr, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
|
|
// may return an error code.
|
|
mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
|
|
mClauseArray.Length()));
|
|
|
|
//--------------------------------------------------------
|
|
// 3. Get GCS_COMPATTR
|
|
//--------------------------------------------------------
|
|
// This provides us with the attribute string necessary
|
|
// for doing hiliting
|
|
long attrArrayLength =
|
|
::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
|
|
attrArrayLength /= sizeof(uint8_t);
|
|
|
|
if (attrArrayLength > 0) {
|
|
nsresult rv = EnsureAttributeArray(attrArrayLength);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
attrArrayLength =
|
|
::ImmGetCompositionStringW(aContext.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));
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
|
|
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(aContext.get(), GCS_CURSORPOS, nullptr, 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");
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
|
|
mCursorPosition));
|
|
|
|
//--------------------------------------------------------
|
|
// 5. Send the compositionchange event
|
|
//--------------------------------------------------------
|
|
DispatchCompositionChangeEvent(aWindow, aContext);
|
|
|
|
return ShouldDrawCompositionStringOurselves();
|
|
}
|
|
|
|
void
|
|
IMMHandler::HandleEndComposition(nsWindow* aWindow,
|
|
const nsAString* aCommitString)
|
|
{
|
|
MOZ_ASSERT(mIsComposing,
|
|
"HandleEndComposition is called but mIsComposing is FALSE");
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))",
|
|
aWindow, aCommitString,
|
|
aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
|
|
|
|
if (mNativeCaretIsCreated) {
|
|
::DestroyCaret();
|
|
mNativeCaretIsCreated = false;
|
|
}
|
|
|
|
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
|
|
nsresult rv = dispatcher->BeginNativeInputTransaction();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleEndComposition, FAILED due to "
|
|
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
|
|
return;
|
|
}
|
|
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
|
|
nsEventStatus status;
|
|
rv = dispatcher->CommitComposition(status, aCommitString, &eventTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleStartComposition, FAILED, due to "
|
|
"TextEventDispatcher::CommitComposition() failure"));
|
|
return;
|
|
}
|
|
mIsComposing = false;
|
|
// XXX aWindow and mComposingWindow are always same??
|
|
mComposingWindow = nullptr;
|
|
mDispatcher = nullptr;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::HandleReconvert(nsWindow* aWindow,
|
|
LPARAM lParam,
|
|
LRESULT* oResult)
|
|
{
|
|
*oResult = 0;
|
|
RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
|
|
|
|
Selection& selection = GetSelection();
|
|
if (!selection.EnsureValidSelection(aWindow)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleReconvert, FAILED, due to "
|
|
"Selection::EnsureValidSelection() failure"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t len = selection.Length();
|
|
uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
|
|
|
|
if (!pReconv) {
|
|
// Return need size to reconvert.
|
|
if (len == 0) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleReconvert, There are not selected text"));
|
|
return false;
|
|
}
|
|
*oResult = needSize;
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleReconvert, succeeded, result=%ld",
|
|
*oResult));
|
|
return true;
|
|
}
|
|
|
|
if (pReconv->dwSize < needSize) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld",
|
|
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.mString.get(), len * sizeof(WCHAR));
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
|
|
GetReconvertStringLog(pReconv).get(), *oResult));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::HandleQueryCharPosition(nsWindow* aWindow,
|
|
LPARAM lParam,
|
|
LRESULT* oResult)
|
|
{
|
|
uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
|
|
*oResult = false;
|
|
IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
|
|
if (!pCharPosition) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleQueryCharPosition, FAILED, due to pCharPosition is null"));
|
|
return false;
|
|
}
|
|
if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
|
|
"sizeof(IMECHARPOSITION)=%ld",
|
|
pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
|
|
return false;
|
|
}
|
|
if (::GetFocus() != aWindow->GetWindowHandle()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x",
|
|
::GetFocus(), aWindow->GetWindowHandle()));
|
|
return false;
|
|
}
|
|
if (pCharPosition->dwCharPos > len) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, "
|
|
"len=%ld",
|
|
pCharPosition->dwCharPos, len));
|
|
return false;
|
|
}
|
|
|
|
LayoutDeviceIntRect r;
|
|
bool ret =
|
|
GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
|
|
NS_ENSURE_TRUE(ret, false);
|
|
|
|
LayoutDeviceIntRect 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);
|
|
|
|
// XXX This might need to check writing mode. However, MSDN doesn't explain
|
|
// how to set the values in vertical writing mode. Additionally, IME
|
|
// doesn't work well with top-left of the character (this is explicitly
|
|
// documented) and its horizontal width. So, it might be better to set
|
|
// top-right corner of the character and horizontal width, but we're not
|
|
// sure if it doesn't cause any problems with a lot of IMEs...
|
|
pCharPosition->pt.x = screenRect.x;
|
|
pCharPosition->pt.y = screenRect.y;
|
|
|
|
pCharPosition->cLineHeight = r.height;
|
|
|
|
WidgetQueryContentEvent editorRect(true, eQueryEditorRect, aWindow);
|
|
aWindow->InitEvent(editorRect);
|
|
DispatchEvent(aWindow, editorRect);
|
|
if (NS_WARN_IF(!editorRect.mSucceeded)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleQueryCharPosition, eQueryEditorRect failed"));
|
|
::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
|
|
} else {
|
|
LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect;
|
|
nsWindow* window = editorRect.mReply.mFocusedWidget ?
|
|
static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget) : aWindow;
|
|
LayoutDeviceIntRect editorRectInScreen;
|
|
ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
|
|
::SetRect(&pCharPosition->rcDocument,
|
|
editorRectInScreen.x, editorRectInScreen.y,
|
|
editorRectInScreen.XMost(), editorRectInScreen.YMost());
|
|
}
|
|
|
|
*oResult = TRUE;
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
|
|
"y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
|
|
"bottom=%d } }",
|
|
pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
|
|
pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
|
|
pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::HandleDocumentFeed(nsWindow* aWindow,
|
|
LPARAM lParam,
|
|
LRESULT* oResult)
|
|
{
|
|
*oResult = 0;
|
|
RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
|
|
|
|
LayoutDeviceIntPoint point(0, 0);
|
|
|
|
bool hasCompositionString =
|
|
mIsComposing && ShouldDrawCompositionStringOurselves();
|
|
|
|
int32_t targetOffset, targetLength;
|
|
if (!hasCompositionString) {
|
|
Selection& selection = GetSelection();
|
|
if (!selection.EnsureValidSelection(aWindow)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, due to "
|
|
"Selection::EnsureValidSelection() failure"));
|
|
return false;
|
|
}
|
|
targetOffset = int32_t(selection.mOffset);
|
|
targetLength = int32_t(selection.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) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, due to the selection is out of "
|
|
"range"));
|
|
return false;
|
|
}
|
|
|
|
// Get all contents of the focused editor.
|
|
WidgetQueryContentEvent textContent(true, eQueryTextContent, aWindow);
|
|
textContent.InitForQueryTextContent(0, UINT32_MAX);
|
|
aWindow->InitEvent(textContent, &point);
|
|
DispatchEvent(aWindow, textContent);
|
|
if (!textContent.mSucceeded) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
|
|
return false;
|
|
}
|
|
|
|
nsAutoString str(textContent.mReply.mString);
|
|
if (targetOffset > int32_t(str.Length())) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, due to the caret offset is invalid"));
|
|
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;
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleDocumentFeed, succeeded, result=%ld",
|
|
*oResult));
|
|
return true;
|
|
}
|
|
|
|
if (pReconv->dwSize < needSize) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld",
|
|
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)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: HandleDocumentFeed, FAILED, due to GetTargetClauseRange() "
|
|
"failure"));
|
|
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));
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
|
|
GetReconvertStringLog(pReconv).get(), *oResult));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow)
|
|
{
|
|
if (!mComposingWindow || mComposingWindow == aWindow) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CommitCompositionOnPreviousWindow, mIsComposing=%s",
|
|
GetBoolName(mIsComposing)));
|
|
|
|
// If we have composition, we should dispatch composition events internally.
|
|
if (mIsComposing) {
|
|
IMEContext context(mComposingWindow);
|
|
NS_ASSERTION(context.IsValid(), "IME context must be valid");
|
|
|
|
HandleEndComposition(mComposingWindow);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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!!";
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
|
|
"aWindow->Destroyed()=%s",
|
|
aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed())));
|
|
|
|
if (aWindow->Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
aWindow->DispatchWindowEvent(&aEvent);
|
|
}
|
|
|
|
void
|
|
IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow,
|
|
const IMEContext& aContext)
|
|
{
|
|
NS_ASSERTION(mIsComposing, "conflict state");
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchCompositionChangeEvent"));
|
|
|
|
// If we don't need to draw composition string ourselves, we don't need to
|
|
// fire compositionchange event during composing.
|
|
if (!ShouldDrawCompositionStringOurselves()) {
|
|
// But we need to adjust composition window pos and native caret pos, here.
|
|
SetIMERelatedWindowsPos(aWindow, aContext);
|
|
return;
|
|
}
|
|
|
|
RefPtr<nsWindow> kungFuDeathGrip(aWindow);
|
|
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
|
|
nsresult rv = dispatcher->BeginNativeInputTransaction();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to "
|
|
"TextEventDispatcher::BeginNativeInputTransaction() failure"));
|
|
return;
|
|
}
|
|
|
|
// NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
|
|
// in e10s mode. compositionchange event will notify this of
|
|
// NOTIFY_IME_OF_COMPOSITION_UPDATE, then SetIMERelatedWindowsPos()
|
|
// will be called.
|
|
|
|
// XXX Sogou (Simplified Chinese IME) returns contradictory values:
|
|
// The cursor position is actual cursor position. However, other values
|
|
// (composition string and attributes) are empty.
|
|
|
|
if (mCompositionString.IsEmpty()) {
|
|
// Don't append clause information if composition string is empty.
|
|
} else if (mClauseArray.IsEmpty()) {
|
|
// Some IMEs don't return clause array information, then, we assume that
|
|
// all characters in the composition string are in one clause.
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchCompositionChangeEvent, mClauseArray.Length()=0"));
|
|
rv =dispatcher->SetPendingComposition(mCompositionString, nullptr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to"
|
|
"TextEventDispatcher::SetPendingComposition() failure"));
|
|
return;
|
|
}
|
|
} else {
|
|
// iterate over the attributes
|
|
rv = dispatcher->SetPendingCompositionString(mCompositionString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to"
|
|
"TextEventDispatcher::SetPendingCompositionString() failure"));
|
|
return;
|
|
}
|
|
uint32_t lastOffset = 0;
|
|
for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
|
|
uint32_t current = mClauseArray[i + 1];
|
|
if (current > mCompositionString.Length()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. "
|
|
"This is larger than mCompositionString.Length()=%lu",
|
|
i + 1, current, mCompositionString.Length()));
|
|
current = int32_t(mCompositionString.Length());
|
|
}
|
|
|
|
uint32_t length = current - lastOffset;
|
|
uint32_t attr = PlatformToNSAttr(mAttributeArray[lastOffset]);
|
|
rv = dispatcher->AppendClauseToPendingComposition(length, attr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to"
|
|
"TextEventDispatcher::AppendClauseToPendingComposition() failure"));
|
|
return;
|
|
}
|
|
|
|
lastOffset = current;
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchCompositionChangeEvent, index=%ld, rangeType=%s, "
|
|
"range length=%lu",
|
|
i, GetRangeTypeName(attr), length));
|
|
}
|
|
}
|
|
|
|
if (mCursorPosition == NO_IME_CARET) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: DispatchCompositionChangeEvent, no caret"));
|
|
} else {
|
|
uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
|
|
if (cursor > mCompositionString.Length()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CreateTextRangeArray, mCursorPosition=%ld. "
|
|
"This is larger than mCompositionString.Length()=%lu",
|
|
mCursorPosition, mCompositionString.Length()));
|
|
cursor = mCompositionString.Length();
|
|
}
|
|
|
|
// If caret is in the target clause, the target clause will be painted as
|
|
// normal selection range. Since caret shouldn't be in selection range on
|
|
// Windows, we shouldn't append caret range in such case.
|
|
const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses();
|
|
const TextRange* targetClause =
|
|
clauses ? clauses->GetTargetClause() : nullptr;
|
|
if (targetClause &&
|
|
cursor >= targetClause->mStartOffset &&
|
|
cursor <= targetClause->mEndOffset) {
|
|
// Forget the caret position specified by IME since Gecko's caret position
|
|
// will be at the end of composition string.
|
|
mCursorPosition = NO_IME_CARET;
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: CreateTextRangeArray, no caret due to it's in the target "
|
|
"clause, now, mCursorPosition is NO_IME_CARET"));
|
|
}
|
|
|
|
if (mCursorPosition != NO_IME_CARET) {
|
|
rv = dispatcher->SetCaretInPendingComposition(cursor, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to"
|
|
"TextEventDispatcher::SetCaretInPendingComposition() failure"));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
|
|
nsEventStatus status;
|
|
rv = dispatcher->FlushPendingComposition(status, &eventTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: DispatchCompositionChangeEvent, FAILED due to"
|
|
"TextEventDispatcher::FlushPendingComposition() failure"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMMHandler::GetCompositionString(const IMEContext& aContext,
|
|
DWORD aIndex,
|
|
nsAString& aCompositionString) const
|
|
{
|
|
aCompositionString.Truncate();
|
|
|
|
// Retrieve the size of the required output buffer.
|
|
long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0);
|
|
if (lRtn < 0 ||
|
|
!aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1,
|
|
mozilla::fallible)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: GetCompositionString, FAILED, due to OOM"));
|
|
return; // Error or out of memory.
|
|
}
|
|
|
|
// Actually retrieve the composition string information.
|
|
lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex,
|
|
(LPVOID)aCompositionString.BeginWriting(),
|
|
lRtn + sizeof(WCHAR));
|
|
aCompositionString.SetLength(lRtn / sizeof(WCHAR));
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: GetCompositionString, succeeded, aCompositionString=\"%s\"",
|
|
NS_ConvertUTF16toUTF8(aCompositionString).get()));
|
|
}
|
|
|
|
bool
|
|
IMMHandler::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
|
|
IMMHandler::ConvertToANSIString(const nsAFlatString& aStr,
|
|
UINT aCodePage,
|
|
nsACString& aANSIStr)
|
|
{
|
|
int len = ::WideCharToMultiByte(aCodePage, 0,
|
|
(LPCWSTR)aStr.get(), aStr.Length(),
|
|
nullptr, 0, nullptr, nullptr);
|
|
NS_ENSURE_TRUE(len >= 0, false);
|
|
|
|
if (!aANSIStr.SetLength(len, mozilla::fallible)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: ConvertToANSIString, FAILED, due to OOM"));
|
|
return false;
|
|
}
|
|
::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
|
|
(LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow,
|
|
uint32_t aOffset,
|
|
LayoutDeviceIntRect& aCharRect,
|
|
WritingMode* aWritingMode)
|
|
{
|
|
LayoutDeviceIntPoint point(0, 0);
|
|
|
|
Selection& selection = GetSelection();
|
|
if (!selection.EnsureValidSelection(aWindow)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: GetCharacterRectOfSelectedTextAt, FAILED, due to "
|
|
"Selection::EnsureValidSelection() failure"));
|
|
return false;
|
|
}
|
|
|
|
// The base offset of aOffset is the start of composition string during
|
|
// composing or the start of selected string not during composing.
|
|
uint32_t baseOffset =
|
|
mIsComposing ? mCompositionStart : selection.mOffset;
|
|
|
|
CheckedInt<uint32_t> checkingOffset =
|
|
CheckedInt<uint32_t>(baseOffset) + aOffset;
|
|
if (NS_WARN_IF(!checkingOffset.isValid()) ||
|
|
checkingOffset.value() == UINT32_MAX) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: GetCharacterRectOfSelectedTextAt, FAILED, due to "
|
|
"aOffset is too large (aOffset=%u, baseOffset=%u, mIsComposing=%s)",
|
|
aOffset, baseOffset, GetBoolName(mIsComposing)));
|
|
return false;
|
|
}
|
|
|
|
// If the offset is larger than the end of composition string or selected
|
|
// string, we should return false since such case must be a bug of the caller
|
|
// or the active IME. If it's an IME's bug, we need to set targetLength to
|
|
// aOffset.
|
|
uint32_t targetLength =
|
|
mIsComposing ? mCompositionString.Length() : selection.Length();
|
|
if (NS_WARN_IF(aOffset > targetLength)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: GetCharacterRectOfSelectedTextAt, FAILED, due to "
|
|
"aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
|
|
aOffset, targetLength, GetBoolName(mIsComposing)));
|
|
return false;
|
|
}
|
|
|
|
uint32_t offset = checkingOffset.value();
|
|
|
|
// If there is caret, we might be able to use caret rect.
|
|
uint32_t caretOffset = UINT32_MAX;
|
|
// There is a caret only when the normal selection is collapsed.
|
|
if (selection.Collapsed()) {
|
|
if (mIsComposing) {
|
|
// If it's composing, mCursorPosition is the offset to caret in
|
|
// the composition string.
|
|
if (mCursorPosition != NO_IME_CARET) {
|
|
MOZ_ASSERT(mCursorPosition >= 0);
|
|
caretOffset = mCompositionStart + mCursorPosition;
|
|
} else if (!ShouldDrawCompositionStringOurselves() ||
|
|
mCompositionString.IsEmpty()) {
|
|
// Otherwise, if there is no composition string, we should assume that
|
|
// there is a caret at the start of composition string.
|
|
caretOffset = mCompositionStart;
|
|
}
|
|
} else {
|
|
// If there is no composition, the selection offset is the caret offset.
|
|
caretOffset = selection.mOffset;
|
|
}
|
|
}
|
|
|
|
// If there is a caret and retrieving offset is same as the caret offset,
|
|
// we should use the caret rect.
|
|
if (offset != caretOffset) {
|
|
WidgetQueryContentEvent charRect(true, eQueryTextRect, aWindow);
|
|
charRect.InitForQueryTextRect(offset, 1);
|
|
aWindow->InitEvent(charRect, &point);
|
|
DispatchEvent(aWindow, charRect);
|
|
if (charRect.mSucceeded) {
|
|
aCharRect = charRect.mReply.mRect;
|
|
if (aWritingMode) {
|
|
*aWritingMode = charRect.GetWritingMode();
|
|
}
|
|
MOZ_LOG(gIMMLog, LogLevel::Debug,
|
|
("IMM: GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
|
|
"aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
|
|
"charRect.GetWritingMode()=%s",
|
|
aOffset, aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height,
|
|
GetWritingModeName(charRect.GetWritingMode()).get()));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return GetCaretRect(aWindow, aCharRect, aWritingMode);
|
|
}
|
|
|
|
bool
|
|
IMMHandler::GetCaretRect(nsWindow* aWindow,
|
|
LayoutDeviceIntRect& aCaretRect,
|
|
WritingMode* aWritingMode)
|
|
{
|
|
LayoutDeviceIntPoint point(0, 0);
|
|
|
|
Selection& selection = GetSelection();
|
|
if (!selection.EnsureValidSelection(aWindow)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: GetCaretRect, FAILED, due to "
|
|
"Selection::EnsureValidSelection() failure"));
|
|
return false;
|
|
}
|
|
|
|
WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow);
|
|
caretRect.InitForQueryCaretRect(selection.mOffset);
|
|
aWindow->InitEvent(caretRect, &point);
|
|
DispatchEvent(aWindow, caretRect);
|
|
if (!caretRect.mSucceeded) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: GetCaretRect, FAILED, due to eQueryCaretRect failure"));
|
|
return false;
|
|
}
|
|
aCaretRect = caretRect.mReply.mRect;
|
|
if (aWritingMode) {
|
|
*aWritingMode = caretRect.GetWritingMode();
|
|
}
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: GetCaretRect, SUCCEEDED, "
|
|
"aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
|
|
"caretRect.GetWritingMode()=%s",
|
|
aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height,
|
|
GetWritingModeName(caretRect.GetWritingMode()).get()));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
|
|
const IMEContext& aContext)
|
|
{
|
|
LayoutDeviceIntRect r;
|
|
// Get first character rect of current a normal selected text or a composing
|
|
// string.
|
|
WritingMode writingMode;
|
|
bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode);
|
|
NS_ENSURE_TRUE(ret, false);
|
|
nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
|
|
LayoutDeviceIntRect 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.
|
|
LayoutDeviceIntRect 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);
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, "
|
|
"width=%ld, height=%ld",
|
|
GetBoolName(mNativeCaretIsCreated), caretRect.width, caretRect.height));
|
|
}
|
|
::SetCaretPos(caretRect.x, caretRect.y);
|
|
|
|
if (ShouldDrawCompositionStringOurselves()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPos, Set candidate window"));
|
|
|
|
// Get a rect of first character in current target in composition string.
|
|
LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect;
|
|
if (mIsComposing && !mCompositionString.IsEmpty()) {
|
|
// If there are no targetted selection, we should use it's first character
|
|
// rect instead.
|
|
uint32_t offset, length;
|
|
if (!GetTargetClauseRange(&offset, &length)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: SetIMERelatedWindowsPos, FAILED, due to "
|
|
"GetTargetClauseRange() failure"));
|
|
return false;
|
|
}
|
|
ret = GetCharacterRectOfSelectedTextAt(aWindow,
|
|
offset - mCompositionStart,
|
|
firstTargetCharRect, &writingMode);
|
|
NS_ENSURE_TRUE(ret, false);
|
|
if (length) {
|
|
ret = GetCharacterRectOfSelectedTextAt(aWindow,
|
|
offset + length - 1 - mCompositionStart, lastTargetCharRect);
|
|
NS_ENSURE_TRUE(ret, false);
|
|
} else {
|
|
lastTargetCharRect = firstTargetCharRect;
|
|
}
|
|
} else {
|
|
// If there are no composition string, we should use a first character
|
|
// rect.
|
|
ret = GetCharacterRectOfSelectedTextAt(aWindow, 0,
|
|
firstTargetCharRect, &writingMode);
|
|
NS_ENSURE_TRUE(ret, false);
|
|
lastTargetCharRect = firstTargetCharRect;
|
|
}
|
|
ResolveIMECaretPos(toplevelWindow, firstTargetCharRect,
|
|
aWindow, firstTargetCharRect);
|
|
ResolveIMECaretPos(toplevelWindow, lastTargetCharRect,
|
|
aWindow, lastTargetCharRect);
|
|
LayoutDeviceIntRect targetClauseRect;
|
|
targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
|
|
|
|
// Move the candidate window to proper position from the target clause as
|
|
// far as possible.
|
|
CANDIDATEFORM candForm;
|
|
candForm.dwIndex = 0;
|
|
if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
|
|
candForm.dwStyle = CFS_EXCLUDE;
|
|
// Candidate window shouldn't overlap the target clause in any writing
|
|
// mode.
|
|
candForm.rcArea.left = targetClauseRect.x;
|
|
candForm.rcArea.right = targetClauseRect.XMost();
|
|
candForm.rcArea.top = targetClauseRect.y;
|
|
candForm.rcArea.bottom = targetClauseRect.YMost();
|
|
if (!writingMode.IsVertical()) {
|
|
// In horizontal layout, current point of interest should be top-left
|
|
// of the first character.
|
|
candForm.ptCurrentPos.x = firstTargetCharRect.x;
|
|
candForm.ptCurrentPos.y = firstTargetCharRect.y;
|
|
} else if (writingMode.IsVerticalRL()) {
|
|
// In vertical layout (RL), candidate window should be positioned right
|
|
// side of target clause. However, we don't set vertical writing font
|
|
// to the IME. Therefore, the candidate window may be positioned
|
|
// bottom-left of target clause rect with these information.
|
|
candForm.ptCurrentPos.x = targetClauseRect.x;
|
|
candForm.ptCurrentPos.y = targetClauseRect.y;
|
|
} else {
|
|
MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
|
|
// In vertical layout (LR), candidate window should be poisitioned left
|
|
// side of target clause. Although, we don't set vertical writing font
|
|
// to the IME, the candidate window may be positioned bottom-right of
|
|
// the target clause rect with these information.
|
|
candForm.ptCurrentPos.x = targetClauseRect.XMost();
|
|
candForm.ptCurrentPos.y = targetClauseRect.y;
|
|
}
|
|
} else {
|
|
// If vertical writing is not supported by IME, let's set candidate
|
|
// window position to the bottom-left of the target clause because
|
|
// the position must be the safest position to prevent the candidate
|
|
// window to overlap with the target clause.
|
|
candForm.dwStyle = CFS_CANDIDATEPOS;
|
|
candForm.ptCurrentPos.x = targetClauseRect.x;
|
|
candForm.ptCurrentPos.y = targetClauseRect.YMost();
|
|
}
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
|
|
"ptCurrentPos={ x=%d, y=%d }, "
|
|
"rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
|
|
"writingMode=%s",
|
|
candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
|
|
candForm.rcArea.left, candForm.rcArea.top,
|
|
candForm.rcArea.right, candForm.rcArea.bottom,
|
|
GetWritingModeName(writingMode).get()));
|
|
::ImmSetCandidateWindow(aContext.get(), &candForm);
|
|
} else {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPos, Set composition window"));
|
|
|
|
// 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 =
|
|
!writingMode.IsVerticalLR() ? firstSelectedCharRect.x :
|
|
firstSelectedCharRect.XMost();
|
|
compForm.ptCurrentPos.y = firstSelectedCharRect.y;
|
|
::ImmSetCompositionWindow(aContext.get(), &compForm);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
IMMHandler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
|
|
const IMEContext& aContext)
|
|
{
|
|
WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow);
|
|
aWindow->InitEvent(editorRectEvent);
|
|
DispatchEvent(aWindow, editorRectEvent);
|
|
if (!editorRectEvent.mSucceeded) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPosOnPlugin, "
|
|
"FAILED, due to eQueryEditorRect failure"));
|
|
return;
|
|
}
|
|
|
|
// Clip the plugin rect by the client rect of the window because composition
|
|
// window needs to be specified the position in the client area.
|
|
nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
|
|
LayoutDeviceIntRect pluginRectInScreen =
|
|
editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
|
|
LayoutDeviceIntRect winRectInScreen;
|
|
aWindow->GetClientBounds(winRectInScreen);
|
|
// composition window cannot be positioned on the edge of client area.
|
|
winRectInScreen.width--;
|
|
winRectInScreen.height--;
|
|
LayoutDeviceIntRect clippedPluginRect;
|
|
clippedPluginRect.x =
|
|
std::min(std::max(pluginRectInScreen.x, winRectInScreen.x),
|
|
winRectInScreen.XMost());
|
|
clippedPluginRect.y =
|
|
std::min(std::max(pluginRectInScreen.y, winRectInScreen.y),
|
|
winRectInScreen.YMost());
|
|
int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
|
|
int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
|
|
clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x);
|
|
clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y);
|
|
clippedPluginRect -= aWindow->WidgetToScreenOffset();
|
|
|
|
// Cover the plugin with native caret. This prevents IME's window and plugin
|
|
// overlap.
|
|
if (mNativeCaretIsCreated) {
|
|
::DestroyCaret();
|
|
}
|
|
mNativeCaretIsCreated =
|
|
::CreateCaret(aWindow->GetWindowHandle(), nullptr,
|
|
clippedPluginRect.width, clippedPluginRect.height);
|
|
::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y);
|
|
|
|
// Set the composition window to bottom-left of the clipped plugin.
|
|
// As far as we know, there is no IME for RTL language. Therefore, this code
|
|
// must not need to take care of RTL environment.
|
|
COMPOSITIONFORM compForm;
|
|
compForm.dwStyle = CFS_POINT;
|
|
compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
|
|
compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
|
|
if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: SetIMERelatedWindowsPosOnPlugin, "
|
|
"FAILED, due to ::ImmSetCompositionWindow() failure"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
|
|
LayoutDeviceIntRect& aCursorRect,
|
|
nsIWidget* aNewOriginWidget,
|
|
LayoutDeviceIntRect& aOutRect)
|
|
{
|
|
aOutRect = aCursorRect;
|
|
|
|
if (aReferenceWidget == aNewOriginWidget)
|
|
return;
|
|
|
|
if (aReferenceWidget)
|
|
aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
|
|
|
|
if (aNewOriginWidget)
|
|
aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
|
|
}
|
|
|
|
static void
|
|
SetHorizontalFontToLogFont(const nsAString& aFontFace,
|
|
LOGFONTW& aLogFont)
|
|
{
|
|
aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
|
|
if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
|
|
memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
|
|
return;
|
|
}
|
|
memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
|
|
aFontFace.Length() * sizeof(wchar_t));
|
|
aLogFont.lfFaceName[aFontFace.Length()] = 0;
|
|
}
|
|
|
|
static void
|
|
SetVerticalFontToLogFont(const nsAString& aFontFace,
|
|
LOGFONTW& aLogFont)
|
|
{
|
|
aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
|
|
if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
|
|
memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
|
|
return;
|
|
}
|
|
aLogFont.lfFaceName[0] = '@';
|
|
memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
|
|
aFontFace.Length() * sizeof(wchar_t));
|
|
aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
|
|
}
|
|
|
|
void
|
|
IMMHandler::AdjustCompositionFont(nsWindow* aWindow,
|
|
const IMEContext& aContext,
|
|
const WritingMode& aWritingMode,
|
|
bool aForceUpdate)
|
|
{
|
|
// An instance of IMMHandler is destroyed when active IME is changed.
|
|
// Therefore, we need to store the information which are set to the IM
|
|
// context to static variables since IM context is never recreated.
|
|
static bool sCompositionFontsInitialized = false;
|
|
static nsString sCompositionFont =
|
|
Preferences::GetString("intl.imm.composition_font");
|
|
|
|
// If composition font is customized by pref, we need to modify the
|
|
// composition font of the IME context at first time even if the writing mode
|
|
// is horizontal.
|
|
bool setCompositionFontForcibly = aForceUpdate ||
|
|
(!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
|
|
|
|
static WritingMode sCurrentWritingMode;
|
|
static nsString sCurrentIMEName;
|
|
if (!setCompositionFontForcibly &&
|
|
sWritingModeOfCompositionFont == aWritingMode &&
|
|
sCurrentIMEName == sIMEName) {
|
|
// Nothing to do if writing mode isn't being changed.
|
|
return;
|
|
}
|
|
|
|
// Decide composition fonts for both horizontal writing mode and vertical
|
|
// writing mode. If the font isn't specified by the pref, use default
|
|
// font which is already set to the IM context. And also in vertical writing
|
|
// mode, insert '@' to the start of the font.
|
|
if (!sCompositionFontsInitialized) {
|
|
sCompositionFontsInitialized = true;
|
|
// sCompositionFontH must not start with '@' and its length is less than
|
|
// LF_FACESIZE since it needs to end with null terminating character.
|
|
if (sCompositionFont.IsEmpty() ||
|
|
sCompositionFont.Length() > LF_FACESIZE - 1 ||
|
|
sCompositionFont[0] == '@') {
|
|
LOGFONTW defaultLogFont;
|
|
if (NS_WARN_IF(!::ImmGetCompositionFont(aContext.get(),
|
|
&defaultLogFont))) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
|
|
sCompositionFont.AssignLiteral("System");
|
|
} else {
|
|
// The font face is typically, "System".
|
|
sCompositionFont.Assign(defaultLogFont.lfFaceName);
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: AdjustCompositionFont, sCompositionFont=\"%s\" is initialized",
|
|
NS_ConvertUTF16toUTF8(sCompositionFont).get()));
|
|
}
|
|
|
|
static nsString sCompositionFontForJapanist2003;
|
|
if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
|
|
const char* kCompositionFontForJapanist2003 =
|
|
"intl.imm.composition_font.japanist_2003";
|
|
sCompositionFontForJapanist2003 =
|
|
Preferences::GetString(kCompositionFontForJapanist2003);
|
|
// If the font name is not specified properly, let's use
|
|
// "MS PGothic" instead.
|
|
if (sCompositionFontForJapanist2003.IsEmpty() ||
|
|
sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
|
|
sCompositionFontForJapanist2003[0] == '@') {
|
|
sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
|
|
}
|
|
}
|
|
|
|
sWritingModeOfCompositionFont = aWritingMode;
|
|
sCurrentIMEName = sIMEName;
|
|
|
|
LOGFONTW logFont;
|
|
memset(&logFont, 0, sizeof(logFont));
|
|
if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
|
|
logFont.lfFaceName[0] = 0;
|
|
}
|
|
// Need to reset some information which should be recomputed with new font.
|
|
logFont.lfWidth = 0;
|
|
logFont.lfWeight = FW_DONTCARE;
|
|
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
|
|
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
|
logFont.lfPitchAndFamily = DEFAULT_PITCH;
|
|
|
|
if (!aWindow->PluginHasFocus() &&
|
|
aWritingMode.IsVertical() && IsVerticalWritingSupported()) {
|
|
SetVerticalFontToLogFont(
|
|
IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
|
|
sCompositionFont, logFont);
|
|
} else {
|
|
SetHorizontalFontToLogFont(
|
|
IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
|
|
sCompositionFont, logFont);
|
|
}
|
|
MOZ_LOG(gIMMLog, LogLevel::Warning,
|
|
("IMM: AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")",
|
|
NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
|
|
::ImmSetCompositionFontW(aContext.get(), &logFont);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
IMMHandler::OnMouseButtonEvent(nsWindow* aWindow,
|
|
const IMENotification& aIMENotification)
|
|
{
|
|
// We don't need to create the instance of the handler here.
|
|
if (!gIMMHandler) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
|
|
!ShouldDrawCompositionStringOurselves()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We need to handle only mousedown event.
|
|
if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// If the character under the cursor is not in the composition string,
|
|
// we don't need to notify IME of it.
|
|
uint32_t compositionStart = gIMMHandler->mCompositionStart;
|
|
uint32_t compositionEnd =
|
|
compositionStart + gIMMHandler->mCompositionString.Length();
|
|
if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart ||
|
|
aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) {
|
|
return NS_OK;
|
|
}
|
|
|
|
BYTE button;
|
|
switch (aIMENotification.mMouseButtonEventData.mButton) {
|
|
case WidgetMouseEventBase::eLeftButton:
|
|
button = IMEMOUSE_LDOWN;
|
|
break;
|
|
case WidgetMouseEventBase::eMiddleButton:
|
|
button = IMEMOUSE_MDOWN;
|
|
break;
|
|
case WidgetMouseEventBase::eRightButton:
|
|
button = IMEMOUSE_RDOWN;
|
|
break;
|
|
default:
|
|
return NS_OK;
|
|
}
|
|
|
|
// calcurate positioning and offset
|
|
// char : JCH1|JCH2|JCH3
|
|
// offset: 0011 1122 2233
|
|
// positioning: 2301 2301 2301
|
|
nsIntPoint cursorPos =
|
|
aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
|
|
nsIntRect charRect =
|
|
aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
|
|
int32_t cursorXInChar = cursorPos.x - charRect.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 (charRect.width > 0) {
|
|
positioning = cursorXInChar * 4 / charRect.width;
|
|
positioning = (positioning + 2) % 4;
|
|
}
|
|
|
|
int offset =
|
|
aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
|
|
if (positioning < 2) {
|
|
offset++;
|
|
}
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld",
|
|
cursorPos.x, cursorPos.y, offset, positioning));
|
|
|
|
// send MS_MSIME_MOUSE message to default IME window.
|
|
HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
|
|
IMEContext context(aWindow);
|
|
if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
|
|
MAKELONG(MAKEWORD(button, positioning), offset),
|
|
(LPARAM) context.get()) == 1) {
|
|
return NS_SUCCESS_EVENT_CONSUMED;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
IMMHandler::OnKeyDownEvent(nsWindow* aWindow,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
MSGResult& aResult)
|
|
{
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
|
|
aWindow->GetWindowHandle(), wParam, lParam));
|
|
aResult.mConsumed = false;
|
|
switch (wParam) {
|
|
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:
|
|
case VK_RETURN:
|
|
// 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
|
|
// moving caret or editing text with keeping composing state, we should
|
|
// cancel the composition here because we cannot support moving
|
|
// composition string with DOM events (IE also cancels the composition
|
|
// in same cases). Then, this event will be dispatched.
|
|
if (IsComposingOnOurEditor()) {
|
|
// NOTE: We don't need to cancel the composition on another window.
|
|
CancelComposition(aWindow, false);
|
|
}
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
|
|
{
|
|
// Hack for ATOK. ATOK (Japanese IME) refers native caret position at
|
|
// deciding candidate window position.
|
|
if (aWindow->PluginHasFocus()) {
|
|
// We cannot retrieve proper character height from plugin. Therefore,
|
|
// we should assume that the caret height is always 20px since if less than
|
|
// this height, candidate window may overlap with composition string when
|
|
// there is no enough space under composition string to show candidate
|
|
// window.
|
|
static const int32_t kCaretHeight = 20;
|
|
if (sNativeCaretIsCreatedForPlugin) {
|
|
::DestroyCaret();
|
|
}
|
|
sNativeCaretIsCreatedForPlugin =
|
|
::CreateCaret(aWindow->GetWindowHandle(), nullptr, 0, kCaretHeight);
|
|
if (sNativeCaretIsCreatedForPlugin) {
|
|
LayoutDeviceIntPoint caretPosition(aForm->ptCurrentPos.x,
|
|
aForm->ptCurrentPos.y - kCaretHeight);
|
|
nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
|
|
if (toplevelWindow && toplevelWindow != aWindow) {
|
|
caretPosition += toplevelWindow->WidgetToScreenOffset();
|
|
caretPosition -= aWindow->WidgetToScreenOffset();
|
|
}
|
|
::SetCaretPos(caretPosition.x, caretPosition.y);
|
|
}
|
|
}
|
|
IMEContext context(aWindow);
|
|
::ImmSetCandidateWindow(context.get(), aForm);
|
|
}
|
|
|
|
// staitc
|
|
void
|
|
IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow, const NPEvent* aEvent)
|
|
{
|
|
switch (aEvent->event) {
|
|
case WM_IME_STARTCOMPOSITION:
|
|
EnsureHandlerInstance();
|
|
gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam,
|
|
aEvent->lParam);
|
|
break;
|
|
|
|
case WM_IME_COMPOSITION:
|
|
if (gIMMHandler) {
|
|
gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam,
|
|
aEvent->lParam);
|
|
}
|
|
break;
|
|
|
|
case WM_IME_ENDCOMPOSITION:
|
|
if (gIMMHandler) {
|
|
gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam,
|
|
aEvent->lParam);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* IMMHandler::Selection
|
|
******************************************************************************/
|
|
|
|
bool
|
|
IMMHandler::Selection::IsValid() const
|
|
{
|
|
if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
|
|
return false;
|
|
}
|
|
CheckedInt<uint32_t> endOffset =
|
|
CheckedInt<uint32_t>(mOffset) + Length();
|
|
return endOffset.isValid();
|
|
}
|
|
|
|
bool
|
|
IMMHandler::Selection::Update(const IMENotification& aIMENotification)
|
|
{
|
|
mOffset = aIMENotification.mSelectionChangeData.mOffset;
|
|
mString = aIMENotification.mSelectionChangeData.String();
|
|
mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
|
|
mIsValid = true;
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: Selection::Update, aIMENotification={ mSelectionChangeData={ "
|
|
"mOffset=%u, mLength=%u, GetWritingMode()=%s } }",
|
|
mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
|
|
|
|
if (!IsValid()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: Selection::Update, FAILED, due to invalid range"));
|
|
Clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::Selection::Init(nsWindow* aWindow)
|
|
{
|
|
Clear();
|
|
|
|
WidgetQueryContentEvent selection(true, eQuerySelectedText, aWindow);
|
|
LayoutDeviceIntPoint point(0, 0);
|
|
aWindow->InitEvent(selection, &point);
|
|
DispatchEvent(aWindow, selection);
|
|
if (NS_WARN_IF(!selection.mSucceeded)) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: Selection::Init, FAILED, due to eQuerySelectedText failure"));
|
|
return false;
|
|
}
|
|
// If the window is destroyed during querying selected text, we shouldn't
|
|
// do anymore.
|
|
if (aWindow->Destroyed()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: Selection::Init, FAILED, due to the widget destroyed"));
|
|
return false;
|
|
}
|
|
|
|
mOffset = selection.mReply.mOffset;
|
|
mString = selection.mReply.mString;
|
|
mWritingMode = selection.GetWritingMode();
|
|
mIsValid = true;
|
|
|
|
MOZ_LOG(gIMMLog, LogLevel::Info,
|
|
("IMM: Selection::Init, selection={ mReply={ mOffset=%u, "
|
|
"mString.Length()=%u, mWritingMode=%s } }",
|
|
mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
|
|
|
|
if (!IsValid()) {
|
|
MOZ_LOG(gIMMLog, LogLevel::Error,
|
|
("IMM: Selection::Init, FAILED, due to invalid range"));
|
|
Clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow)
|
|
{
|
|
if (IsValid()) {
|
|
return true;
|
|
}
|
|
return Init(aWindow);
|
|
}
|
|
|
|
} // namespace widget
|
|
} // namespace mozilla
|