gecko/widget/windows/nsTextStore.cpp

2900 lines
98 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <olectl.h>
#ifdef MOZ_LOGGING
#define FORCE_PR_LOG /* Allow logging in the release build */
#endif // MOZ_LOGGING
#include "prlog.h"
#include "nscore.h"
#include "nsTextStore.h"
#include "nsWindow.h"
#include "nsPrintfCString.h"
#include "WinUtils.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
using namespace mozilla::widget;
/******************************************************************/
/* nsTextStore */
/******************************************************************/
ITfThreadMgr* nsTextStore::sTsfThreadMgr = NULL;
ITfDisplayAttributeMgr* nsTextStore::sDisplayAttrMgr = NULL;
ITfCategoryMgr* nsTextStore::sCategoryMgr = NULL;
DWORD nsTextStore::sTsfClientId = 0;
nsTextStore* nsTextStore::sTsfTextStore = NULL;
UINT nsTextStore::sFlushTIPInputMessage = 0;
#ifdef PR_LOGGING
/**
* TSF related code should log its behavior even on release build especially
* in the interface methods.
*
* In interface methods, use PR_LOG_ALWAYS.
* In internal methods, use PR_LOG_DEBUG for logging normal behavior.
* For logging error, use PR_LOG_ERROR.
*
* When an instance method is called, start with following text:
* "TSF: 0x%p nsFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
* after that, start with:
* "TSF: 0x%p nsFoo::Bar("
* In an internal method, start with following text:
* "TSF: 0x%p nsFoo::Bar("
* When a static method is called, start with following text:
* "TSF: nsFoo::Bar("
*/
PRLogModuleInfo* sTextStoreLog = nullptr;
static const char*
GetBoolName(bool aBool)
{
return aBool ? "true" : "false";
}
static const char*
GetIMEEnabledName(IMEState::Enabled aIMEEnabled)
{
switch (aIMEEnabled) {
case IMEState::DISABLED:
return "DISABLED";
case IMEState::ENABLED:
return "ENABLED";
case IMEState::PASSWORD:
return "PASSWORD";
case IMEState::PLUGIN:
return "PLUGIN";
default:
return "Invalid";
}
}
static nsCString
GetRIIDNameStr(REFIID aRIID)
{
LPOLESTR str = nullptr;
HRESULT hr = ::StringFromIID(aRIID, &str);
if (FAILED(hr) || !str || !str[0]) {
return EmptyCString();
}
nsAutoString key(L"Interface\\");
key += str;
nsAutoCString result;
PRUnichar buf[256];
if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr,
buf, sizeof(buf))) {
result = NS_ConvertUTF16toUTF8(buf);
} else {
result = NS_ConvertUTF16toUTF8(str);
}
::CoTaskMemFree(str);
return result;
}
static const char*
GetCommonReturnValueName(HRESULT aResult)
{
switch (aResult) {
case S_OK:
return "S_OK";
case E_ABORT:
return "E_ABORT";
case E_ACCESSDENIED:
return "E_ACCESSDENIED";
case E_FAIL:
return "E_FAIL";
case E_HANDLE:
return "E_HANDLE";
case E_INVALIDARG:
return "E_INVALIDARG";
case E_NOINTERFACE:
return "E_NOINTERFACE";
case E_NOTIMPL:
return "E_NOTIMPL";
case E_OUTOFMEMORY:
return "E_OUTOFMEMORY";
case E_POINTER:
return "E_POINTER";
case E_UNEXPECTED:
return "E_UNEXPECTED";
default:
return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
}
}
static const char*
GetTextStoreReturnValueName(HRESULT aResult)
{
switch (aResult) {
case TS_E_FORMAT:
return "TS_E_FORMAT";
case TS_E_INVALIDPOINT:
return "TS_E_INVALIDPOINT";
case TS_E_INVALIDPOS:
return "TS_E_INVALIDPOS";
case TS_E_NOINTERFACE:
return "TS_E_NOINTERFACE";
case TS_E_NOLAYOUT:
return "TS_E_NOLAYOUT";
case TS_E_NOLOCK:
return "TS_E_NOLOCK";
case TS_E_NOOBJECT:
return "TS_E_NOOBJECT";
case TS_E_NOSELECTION:
return "TS_E_NOSELECTION";
case TS_E_NOSERVICE:
return "TS_E_NOSERVICE";
case TS_E_READONLY:
return "TS_E_READONLY";
case TS_E_SYNCHRONOUS:
return "TS_E_SYNCHRONOUS";
case TS_S_ASYNC:
return "TS_S_ASYNC";
default:
return GetCommonReturnValueName(aResult);
}
}
static const nsCString
GetSinkMaskNameStr(DWORD aSinkMask)
{
nsAutoCString description;
if (aSinkMask & TS_AS_TEXT_CHANGE) {
description.AppendLiteral("TS_AS_TEXT_CHANGE");
}
if (aSinkMask & TS_AS_SEL_CHANGE) {
if (!description.IsEmpty()) {
description.AppendLiteral(" | ");
}
description.AppendLiteral("TS_AS_SEL_CHANGE");
}
if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
if (!description.IsEmpty()) {
description.AppendLiteral(" | ");
}
description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
}
if (aSinkMask & TS_AS_ATTR_CHANGE) {
if (!description.IsEmpty()) {
description.AppendLiteral(" | ");
}
description.AppendLiteral("TS_AS_ATTR_CHANGE");
}
if (aSinkMask & TS_AS_STATUS_CHANGE) {
if (!description.IsEmpty()) {
description.AppendLiteral(" | ");
}
description.AppendLiteral("TS_AS_STATUS_CHANGE");
}
if (description.IsEmpty()) {
description.AppendLiteral("not-specified");
}
return description;
}
static const char*
GetActiveSelEndName(TsActiveSelEnd aSelEnd)
{
return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" :
aSelEnd == TS_AE_START ? "TS_AE_START" :
aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown";
}
static const nsCString
GetLockFlagNameStr(DWORD aLockFlags)
{
nsAutoCString description;
if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
description.AppendLiteral("TS_LF_READWRITE");
} else if (aLockFlags & TS_LF_READ) {
description.AppendLiteral("TS_LF_READ");
}
if (aLockFlags & TS_LF_SYNC) {
if (!description.IsEmpty()) {
description.AppendLiteral(" | ");
}
description.AppendLiteral("TS_LF_SYNC");
}
if (description.IsEmpty()) {
description.AppendLiteral("not-specified");
}
return description;
}
static const char*
GetTextRunTypeName(TsRunType aRunType)
{
switch (aRunType) {
case TS_RT_PLAIN:
return "TS_RT_PLAIN";
case TS_RT_HIDDEN:
return "TS_RT_HIDDEN";
case TS_RT_OPAQUE:
return "TS_RT_OPAQUE";
default:
return "Unknown";
}
}
static nsCString
GetColorName(const TF_DA_COLOR &aColor)
{
switch (aColor.type) {
case TF_CT_NONE:
return NS_LITERAL_CSTRING("TF_CT_NONE");
case TF_CT_SYSCOLOR:
return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
static_cast<int32_t>(aColor.nIndex));
case TF_CT_COLORREF:
return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
static_cast<int32_t>(aColor.cr));
break;
default:
return nsPrintfCString("Unknown(%08X)",
static_cast<int32_t>(aColor.type));
}
}
static nsCString
GetLineStyleName(TF_DA_LINESTYLE aLineStyle)
{
switch (aLineStyle) {
case TF_LS_NONE:
return NS_LITERAL_CSTRING("TF_LS_NONE");
case TF_LS_SOLID:
return NS_LITERAL_CSTRING("TF_LS_SOLID");
case TF_LS_DOT:
return NS_LITERAL_CSTRING("TF_LS_DOT");
case TF_LS_DASH:
return NS_LITERAL_CSTRING("TF_LS_DASH");
case TF_LS_SQUIGGLE:
return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE");
default: {
return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
}
}
}
static nsCString
GetClauseAttrName(TF_DA_ATTR_INFO aAttr)
{
switch (aAttr) {
case TF_ATTR_INPUT:
return NS_LITERAL_CSTRING("TF_ATTR_INPUT");
case TF_ATTR_TARGET_CONVERTED:
return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED");
case TF_ATTR_CONVERTED:
return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED");
case TF_ATTR_TARGET_NOTCONVERTED:
return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED");
case TF_ATTR_INPUT_ERROR:
return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR");
case TF_ATTR_FIXEDCONVERTED:
return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED");
case TF_ATTR_OTHER:
return NS_LITERAL_CSTRING("TF_ATTR_OTHER");
default: {
return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
}
}
}
static nsCString
GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE &aDispAttr)
{
nsAutoCString str;
str = "crText:{ ";
str += GetColorName(aDispAttr.crText);
str += " }, crBk:{ ";
str += GetColorName(aDispAttr.crBk);
str += " }, lsStyle: ";
str += GetLineStyleName(aDispAttr.lsStyle);
str += ", fBoldLine: ";
str += GetBoolName(aDispAttr.fBoldLine);
str += ", crLine:{ ";
str += GetColorName(aDispAttr.crLine);
str += " }, bAttr: ";
str += GetClauseAttrName(aDispAttr.bAttr);
return str;
}
#endif // #ifdef PR_LOGGING
nsTextStore::nsTextStore()
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::nsTestStore(): instance is created", this));
mRefCnt = 1;
mEditCookie = 0;
mSinkMask = 0;
mWindow = nullptr;
mLock = 0;
mLockQueued = 0;
mTextChange.acpStart = INT32_MAX;
mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0;
mLastDispatchedTextEvent = nullptr;
}
nsTextStore::~nsTextStore()
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore instance is destroyed, "
"mWindow=0x%p, mDocumentMgr=0x%p, mContext=0x%p",
this, mWindow, mDocumentMgr.get(), mContext.get()));
if (mCompositionTimer) {
mCompositionTimer->Cancel();
mCompositionTimer = nullptr;
}
SaveTextEvent(nullptr);
}
bool
nsTextStore::Create(nsWindow* aWindow,
IMEState::Enabled aIMEEnabled)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::Create(aWindow=0x%p, aIMEEnabled=%s)",
this, aWindow, GetIMEEnabledName(aIMEEnabled)));
if (mDocumentMgr) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::Create() FAILED due to already initialized",
this));
return false;
}
// Create document manager
HRESULT hr = sTsfThreadMgr->CreateDocumentMgr(
getter_AddRefs(mDocumentMgr));
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::Create() FAILED to create DocumentMgr "
"(0x%08X)", this, hr));
return false;
}
mWindow = aWindow;
// Create context and add it to document manager
hr = mDocumentMgr->CreateContext(sTsfClientId, 0,
static_cast<ITextStoreACP*>(this),
getter_AddRefs(mContext), &mEditCookie);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::Create() FAILED to create the context "
"(0x%08X)", this, hr));
mDocumentMgr = NULL;
return false;
}
SetInputContextInternal(aIMEEnabled);
hr = mDocumentMgr->Push(mContext);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::Create() FAILED to push the context (0x%08X)",
this, hr));
// XXX Why don't we use NS_IF_RELEASE() here??
mContext = NULL;
mDocumentMgr = NULL;
return false;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::Create() succeeded: "
"mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
this, mDocumentMgr.get(), mContext.get(), mEditCookie));
return true;
}
bool
nsTextStore::Destroy(void)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::Destroy()", this));
if (mWindow) {
// When blurred, Tablet Input Panel posts "blur" messages
// and try to insert text when the message is retrieved later.
// But by that time the text store is already destroyed,
// so try to get the message early
MSG msg;
if (::PeekMessageW(&msg, mWindow->GetWindowHandle(),
sFlushTIPInputMessage, sFlushTIPInputMessage,
PM_REMOVE)) {
::DispatchMessageW(&msg);
}
}
mContext = NULL;
if (mDocumentMgr) {
mDocumentMgr->Pop(TF_POPF_ALL);
mDocumentMgr = NULL;
}
mSink = NULL;
mWindow = NULL;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::Destroy() succeeded", this));
return true;
}
STDMETHODIMP
nsTextStore::QueryInterface(REFIID riid,
void** ppv)
{
*ppv=NULL;
if ( (IID_IUnknown == riid) || (IID_ITextStoreACP == riid) ) {
*ppv = static_cast<ITextStoreACP*>(this);
} else if (IID_ITfContextOwnerCompositionSink == riid) {
*ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
}
if (*ppv) {
AddRef();
return S_OK;
}
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::QueryInterface() FAILED, riid=%s",
this, GetRIIDNameStr(riid).get()));
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) nsTextStore::AddRef()
{
return ++mRefCnt;
}
STDMETHODIMP_(ULONG) nsTextStore::Release()
{
--mRefCnt;
if (0 != mRefCnt)
return mRefCnt;
delete this;
return 0;
}
STDMETHODIMP
nsTextStore::AdviseSink(REFIID riid,
IUnknown *punk,
DWORD dwMask)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
"mSink=0x%p, mSinkMask=%s",
this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
if (!punk) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to the null punk",
this));
return E_UNEXPECTED;
}
if (IID_ITextStoreACPSink != riid) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to "
"unsupported interface", this));
return E_INVALIDARG; // means unsupported interface.
}
if (!mSink) {
// Install sink
punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
if (!mSink) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to "
"punk not having the interface", this));
return E_UNEXPECTED;
}
} else {
// If sink is already installed we check to see if they are the same
// Get IUnknown from both sides for comparison
nsRefPtr<IUnknown> comparison1, comparison2;
punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
if (comparison1 != comparison2) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::AdviseSink() FAILED due to "
"the sink being different from the stored sink", this));
return CONNECT_E_ADVISELIMIT;
}
}
// Update mask either for a new sink or an existing sink
mSinkMask = dwMask;
return S_OK;
}
STDMETHODIMP
nsTextStore::UnadviseSink(IUnknown *punk)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::UnadviseSink(punk=0x%p), mSink=0x%p",
this, punk, mSink.get()));
if (!punk) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to the null punk",
this));
return E_INVALIDARG;
}
if (!mSink) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to "
"any sink not stored", this));
return CONNECT_E_NOCONNECTION;
}
// Get IUnknown from both sides for comparison
nsRefPtr<IUnknown> comparison1, comparison2;
punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
// Unadvise only if sinks are the same
if (comparison1 != comparison2) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UnadviseSink() FAILED due to "
"the sink being different from the stored sink", this));
return CONNECT_E_NOCONNECTION;
}
mSink = NULL;
mSinkMask = 0;
return S_OK;
}
STDMETHODIMP
nsTextStore::RequestLock(DWORD dwLockFlags,
HRESULT *phrSession)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
"mLock=%s", this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
GetLockFlagNameStr(mLock).get()));
if (!mSink) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::RequestLock() FAILED due to "
"any sink not stored", this));
return E_FAIL;
}
if (!phrSession) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::RequestLock() FAILED due to "
"null phrSession", this));
return E_INVALIDARG;
}
if (!mLock) {
// put on lock
mLock = dwLockFlags & (~TS_LF_SYNC);
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::RequestLock() notifying OnLockGranted()...",
this));
*phrSession = mSink->OnLockGranted(mLock);
while (mLockQueued) {
mLock = mLockQueued;
mLockQueued = 0;
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::RequestLock() notifying OnLockGranted() "
"with mLockQueued (%s)...",
this, GetLockFlagNameStr(mLock).get()));
mSink->OnLockGranted(mLock);
}
mLock = 0;
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::RequestLock() succeeded: *phrSession=%s",
this, GetTextStoreReturnValueName(*phrSession)));
return S_OK;
}
// only time when reentrant lock is allowed is when caller holds a
// read-only lock and is requesting an async write lock
if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
!(dwLockFlags & TS_LF_SYNC)) {
*phrSession = TS_S_ASYNC;
mLockQueued = dwLockFlags & (~TS_LF_SYNC);
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestLock() stores the request in the "
"queue, *phrSession=TS_S_ASYNC", this));
return S_OK;
}
// no more locks allowed
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestLock() didn't allow to lock, "
"*phrSession=TS_E_SYNCHRONOUS", this));
*phrSession = TS_E_SYNCHRONOUS;
return E_FAIL;
}
STDMETHODIMP
nsTextStore::GetStatus(TS_STATUS *pdcs)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
if (!pdcs) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetStatus() FAILED due to null pdcs", this));
return E_INVALIDARG;
}
pdcs->dwDynamicFlags = 0;
// we use a "flat" text model for TSF support so no hidden text
pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
return S_OK;
}
STDMETHODIMP
nsTextStore::QueryInsert(LONG acpTestStart,
LONG acpTestEnd,
ULONG cch,
LONG *pacpResultStart,
LONG *pacpResultEnd)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::QueryInsert(acpTestStart=%ld, "
"acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd));
if (!pacpResultStart || !pacpResultEnd) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to "
"the null argument", this));
return E_INVALIDARG;
}
if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::QueryInsert() FAILED due to "
"wrong argument", this));
return E_INVALIDARG;
}
// XXX need to adjust to cluster boundary
// Assume we are given good offsets for now
*pacpResultStart = acpTestStart;
*pacpResultEnd = acpTestStart + cch;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::QueryInsert() succeeded: "
"*pacpResultStart=%ld, *pacpResultEnd=%ld)",
this, *pacpResultStart, *pacpResultEnd));
return S_OK;
}
STDMETHODIMP
nsTextStore::GetSelection(ULONG ulIndex,
ULONG ulCount,
TS_SELECTION_ACP *pSelection,
ULONG *pcFetched)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
"pSelection=0x%p, pcFetched=0x%p)",
this, ulIndex, ulCount, pSelection, pcFetched));
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetSelection() FAILED due to not locked",
this));
return TS_E_NOLOCK;
}
if (!ulCount || !pSelection || !pcFetched) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetSelection() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
*pcFetched = 0;
if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) &&
ulIndex != 0) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetSelection() FAILED due to "
"unsupported selection", this));
return TS_E_NOSELECTION;
}
if (!GetSelectionInternal(*pSelection)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetSelection() FAILED to get selection",
this));
return E_FAIL;
}
*pcFetched = 1;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetSelection() succeeded", this));
return S_OK;
}
bool
nsTextStore::GetSelectionInternal(TS_SELECTION_ACP &aSelectionACP)
{
if (mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetSelectionInternal(), "
"there is no composition view", this));
// Emulate selection during compositions
aSelectionACP = mCompositionSelection;
} else {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetSelectionInternal(), "
"try to get normal selection...", this));
// Construct and initialize an event to get selection info
nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, mWindow);
mWindow->InitEvent(event);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetSelectionInternal() FAILED to "
"query selected text", this));
return false;
}
// Usually the selection anchor (beginning) position corresponds to the
// TSF start and the selection focus (ending) position corresponds to
// the TSF end, but if selection is reversed the focus now corresponds
// to the TSF start and the anchor now corresponds to the TSF end
aSelectionACP.acpStart = event.mReply.mOffset;
aSelectionACP.acpEnd =
aSelectionACP.acpStart + event.mReply.mString.Length();
aSelectionACP.style.ase =
event.mReply.mString.Length() && event.mReply.mReversed ? TS_AE_START :
TS_AE_END;
// No support for interim character
aSelectionACP.style.fInterimChar = FALSE;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetSelectionInternal() succeeded: "
"acpStart=%lu, acpEnd=%lu, style.ase=%s, style.fInterimChar=%s",
this, aSelectionACP.acpStart, aSelectionACP.acpEnd,
GetActiveSelEndName(aSelectionACP.style.ase),
GetBoolName(aSelectionACP.style.fInterimChar)));
return true;
}
static HRESULT
GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength)
{
nsRefPtr<ITfRangeACP> rangeACP;
aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
NS_ENSURE_TRUE(rangeACP, E_FAIL);
return rangeACP->GetExtent(aStart, aLength);
}
static uint32_t
GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE &aDisplayAttr)
{
uint32_t result;
switch (aDisplayAttr.bAttr) {
case TF_ATTR_TARGET_CONVERTED:
result = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
break;
case TF_ATTR_CONVERTED:
result = NS_TEXTRANGE_CONVERTEDTEXT;
break;
case TF_ATTR_TARGET_NOTCONVERTED:
result = NS_TEXTRANGE_SELECTEDRAWTEXT;
break;
default:
result = NS_TEXTRANGE_RAWINPUT;
break;
}
return result;
}
HRESULT
nsTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty,
ITfRange* aRange,
TF_DISPLAYATTRIBUTE* aResult)
{
NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
NS_ENSURE_TRUE(aRange, E_FAIL);
NS_ENSURE_TRUE(aResult, E_FAIL);
HRESULT hr;
#ifdef PR_LOGGING
if (PR_LOG_TEST(sTextStoreLog, PR_LOG_DEBUG)) {
LONG start = 0, length = 0;
hr = GetRangeExtent(aRange, &start, &length);
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetDisplayAttribute(): "
"GetDisplayAttribute range=%ld-%ld (hr=%s)",
this, start - mCompositionStart, start - mCompositionStart + length,
GetCommonReturnValueName(hr)));
}
#endif
VARIANT propValue;
::VariantInit(&propValue);
hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to "
"ITfProperty::GetValue() failed", this));
return hr;
}
if (VT_I4 != propValue.vt) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to "
"ITfProperty::GetValue() returns non-VT_I4 value", this));
::VariantClear(&propValue);
return E_FAIL;
}
NS_ENSURE_TRUE(sCategoryMgr, E_FAIL);
GUID guid;
hr = sCategoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
::VariantClear(&propValue);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to "
"ITfCategoryMgr::GetGUID() failed", this));
return hr;
}
NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL);
nsRefPtr<ITfDisplayAttributeInfo> info;
hr = sDisplayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
NULL);
if (FAILED(hr) || !info) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to "
"ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed", this));
return hr;
}
hr = info->GetAttributeInfo(aResult);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() FAILED due to "
"ITfDisplayAttributeInfo::GetAttributeInfo() failed", this));
return hr;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetDisplayAttribute() succeeded: "
"Result={ %s }", this, GetDisplayAttrStr(*aResult).get()));
return S_OK;
}
HRESULT
nsTextStore::SaveTextEvent(const nsTextEvent* aEvent)
{
if (mLastDispatchedTextEvent) {
if (mLastDispatchedTextEvent->rangeArray)
delete [] mLastDispatchedTextEvent->rangeArray;
delete mLastDispatchedTextEvent;
mLastDispatchedTextEvent = nullptr;
}
if (!aEvent)
return S_OK;
mLastDispatchedTextEvent = new nsTextEvent(true, NS_TEXT_TEXT, nullptr);
if (!mLastDispatchedTextEvent)
return E_OUTOFMEMORY;
mLastDispatchedTextEvent->rangeCount = aEvent->rangeCount;
mLastDispatchedTextEvent->theText = aEvent->theText;
mLastDispatchedTextEvent->rangeArray = nullptr;
if (aEvent->rangeCount == 0)
return S_OK;
NS_ENSURE_TRUE(aEvent->rangeArray, E_FAIL);
mLastDispatchedTextEvent->rangeArray = new nsTextRange[aEvent->rangeCount];
if (!mLastDispatchedTextEvent->rangeArray) {
delete mLastDispatchedTextEvent;
mLastDispatchedTextEvent = nullptr;
return E_OUTOFMEMORY;
}
memcpy(mLastDispatchedTextEvent->rangeArray, aEvent->rangeArray,
sizeof(nsTextRange) * aEvent->rangeCount);
return S_OK;
}
static bool
IsSameTextEvent(const nsTextEvent* aEvent1, const nsTextEvent* aEvent2)
{
NS_PRECONDITION(aEvent1 || aEvent2, "both events are null");
NS_PRECONDITION(aEvent2 && (aEvent1 != aEvent2),
"both events are same instance");
return (aEvent1 && aEvent2 &&
aEvent1->rangeCount == aEvent2->rangeCount &&
aEvent1->theText == aEvent2->theText &&
(aEvent1->rangeCount == 0 ||
((aEvent1->rangeArray && aEvent2->rangeArray) &&
!memcmp(aEvent1->rangeArray, aEvent2->rangeArray,
sizeof(nsTextRange) * aEvent1->rangeCount))));
}
HRESULT
nsTextStore::UpdateCompositionExtent(ITfRange* aRangeNew)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent(aRangeNew=0x%p), "
"mCompositionView=0x%p", this, aRangeNew, mCompositionView.get()));
if (!mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to "
"no composition view", this));
return E_FAIL;
}
HRESULT hr;
nsRefPtr<ITfCompositionView> pComposition(mCompositionView);
nsRefPtr<ITfRange> composingRange(aRangeNew);
if (!composingRange) {
hr = pComposition->GetRange(getter_AddRefs(composingRange));
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to "
"pComposition->GetRange() failure", this));
return hr;
}
}
// Get starting offset of the composition
LONG compStart = 0, compLength = 0;
hr = GetRangeExtent(composingRange, &compStart, &compLength);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent() FAILED due to "
"GetRangeExtent() failure", this));
return hr;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent(), range=%ld-%ld, "
"mCompositionStart=%ld, mCompositionString.Length()=%lu",
this, compStart, compStart + compLength, mCompositionStart,
mCompositionString.Length()));
if (mCompositionStart != compStart ||
mCompositionString.Length() != (ULONG)compLength) {
// If the queried composition length is different from the length
// of our composition string, OnUpdateComposition is being called
// because a part of the original composition was committed.
// Reflect that by committing existing composition and starting
// a new one. OnEndComposition followed by OnStartComposition
// will accomplish this automagically.
OnEndComposition(pComposition);
OnStartCompositionInternal(pComposition, composingRange, true);
} else {
mCompositionLength = compLength;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::UpdateCompositionExtent() succeeded", this));
return S_OK;
}
static bool
GetColor(const TF_DA_COLOR &aTSFColor, nscolor &aResult)
{
switch (aTSFColor.type) {
case TF_CT_SYSCOLOR: {
DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
aResult = NS_RGB(GetRValue(sysColor), GetGValue(sysColor),
GetBValue(sysColor));
return true;
}
case TF_CT_COLORREF:
aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
GetBValue(aTSFColor.cr));
return true;
case TF_CT_NONE:
default:
return false;
}
}
static bool
GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle, uint8_t &aTextRangeLineStyle)
{
switch (aTSFLineStyle) {
case TF_LS_NONE:
aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_NONE;
return true;
case TF_LS_SOLID:
aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
return true;
case TF_LS_DOT:
aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_DOTTED;
return true;
case TF_LS_DASH:
aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_DASHED;
return true;
case TF_LS_SQUIGGLE:
aTextRangeLineStyle = nsTextRangeStyle::LINESTYLE_WAVY;
return true;
default:
return false;
}
}
HRESULT
nsTextStore::SendTextEventForCompositionString()
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString(), "
"mCompositionView=0x%p, mCompositionString=\"%s\"",
this, mCompositionView.get(),
NS_ConvertUTF16toUTF8(mCompositionString).get()));
if (!mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED "
"due to no composition view", this));
return E_FAIL;
}
// Getting display attributes is *really* complicated!
// We first get the context and the property objects to query for
// attributes, but since a big range can have a variety of values for
// the attribute, we have to find out all the ranges that have distinct
// attribute values. Then we query for what the value represents through
// the display attribute manager and translate that to nsTextRange to be
// sent in NS_TEXT_TEXT
nsRefPtr<ITfProperty> attrPropetry;
HRESULT hr = mContext->GetProperty(GUID_PROP_ATTRIBUTE,
getter_AddRefs(attrPropetry));
if (FAILED(hr) || !attrPropetry) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED "
"due to mContext->GetProperty() failure", this));
return FAILED(hr) ? hr : E_FAIL;
}
// Use NS_TEXT_TEXT to set composition string
nsTextEvent event(true, NS_TEXT_TEXT, mWindow);
mWindow->InitEvent(event);
nsRefPtr<ITfRange> composingRange;
hr = mCompositionView->GetRange(getter_AddRefs(composingRange));
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED "
"due to mCompositionView->GetRange() failure", this));
return hr;
}
nsRefPtr<IEnumTfRanges> enumRanges;
hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
getter_AddRefs(enumRanges), composingRange);
if (FAILED(hr) || !enumRanges) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() FAILED "
"due to attrPropetry->EnumRanges() failure", this));
return FAILED(hr) ? hr : E_FAIL;
}
nsAutoTArray<nsTextRange, 4> textRanges;
nsTextRange newRange;
// No matter if we have display attribute info or not,
// we always pass in at least one range to NS_TEXT_TEXT
newRange.mStartOffset = 0;
newRange.mEndOffset = mCompositionString.Length();
newRange.mRangeType = NS_TEXTRANGE_RAWINPUT;
textRanges.AppendElement(newRange);
nsRefPtr<ITfRange> range;
while (S_OK == enumRanges->Next(1, getter_AddRefs(range), NULL) && range) {
LONG start = 0, length = 0;
if (FAILED(GetRangeExtent(range, &start, &length)))
continue;
nsTextRange newRange;
newRange.mStartOffset = uint32_t(start - mCompositionStart);
// The end of the last range in the array is
// always kept at the end of composition
newRange.mEndOffset = mCompositionString.Length();
TF_DISPLAYATTRIBUTE attr;
hr = GetDisplayAttribute(attrPropetry, range, &attr);
if (FAILED(hr)) {
newRange.mRangeType = NS_TEXTRANGE_RAWINPUT;
} else {
newRange.mRangeType = GetGeckoSelectionValue(attr);
if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
newRange.mRangeStyle.mDefinedStyles |=
nsTextRangeStyle::DEFINED_FOREGROUND_COLOR;
}
if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
newRange.mRangeStyle.mDefinedStyles |=
nsTextRangeStyle::DEFINED_BACKGROUND_COLOR;
}
if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
newRange.mRangeStyle.mDefinedStyles |=
nsTextRangeStyle::DEFINED_UNDERLINE_COLOR;
}
if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
newRange.mRangeStyle.mDefinedStyles |=
nsTextRangeStyle::DEFINED_LINESTYLE;
newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
}
}
nsTextRange& lastRange = textRanges[textRanges.Length() - 1];
if (lastRange.mStartOffset == newRange.mStartOffset) {
// Replace range if last range is the same as this one
// So that ranges don't overlap and confuse the editor
lastRange = newRange;
} else {
lastRange.mEndOffset = newRange.mStartOffset;
textRanges.AppendElement(newRange);
}
}
// We need to hack for Korean Input System which is Korean standard TIP.
// It sets no change style to IME selection (the selection is always only
// one). So, the composition string looks like normal (or committed) string.
// At this time, mCompositionSelection range is same as the composition
// string range. Other applications set a wide caret which covers the
// composition string, however, Gecko doesn't support the wide caret drawing
// now (Gecko doesn't support XOR drawing), unfortunately. For now, we should
// change the range style to undefined.
if (mCompositionSelection.acpStart != mCompositionSelection.acpEnd &&
textRanges.Length() == 1) {
nsTextRange& range = textRanges[0];
LONG start = NS_MIN(mCompositionSelection.acpStart,
mCompositionSelection.acpEnd);
LONG end = NS_MAX(mCompositionSelection.acpStart,
mCompositionSelection.acpEnd);
if ((LONG)range.mStartOffset == start - mCompositionStart &&
(LONG)range.mEndOffset == end - mCompositionStart &&
range.mRangeStyle.IsNoChangeStyle()) {
range.mRangeStyle.Clear();
// The looks of selected type is better than others.
range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
}
}
// The caret position has to be collapsed.
LONG caretPosition = NS_MAX(mCompositionSelection.acpStart,
mCompositionSelection.acpEnd);
caretPosition -= mCompositionStart;
nsTextRange caretRange;
caretRange.mStartOffset = caretRange.mEndOffset = uint32_t(caretPosition);
caretRange.mRangeType = NS_TEXTRANGE_CARETPOSITION;
textRanges.AppendElement(caretRange);
event.theText = mCompositionString;
event.rangeArray = textRanges.Elements();
event.rangeCount = textRanges.Length();
// If we are already send same text event, we should not resend it. Because
// it can be a cause of flickering.
if (IsSameTextEvent(mLastDispatchedTextEvent, &event)) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() "
"succeeded but any DOM events are not dispatched", this));
return S_OK;
}
if (mCompositionString != mLastDispatchedCompositionString) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() "
"dispatching compositionupdate event...", this));
nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
mWindow);
mWindow->InitEvent(compositionUpdate);
compositionUpdate.data = mCompositionString;
mLastDispatchedCompositionString = mCompositionString;
mWindow->DispatchWindowEvent(&compositionUpdate);
}
if (mWindow && !mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() "
"dispatching text event...", this));
mWindow->DispatchWindowEvent(&event);
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SendTextEventForCompositionString() "
"succeeded", this));
return SaveTextEvent(&event);
}
HRESULT
nsTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
bool aDispatchTextEvent)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::SetSelectionInternal(pSelection=%ld-%ld, "
"aDispatchTextEvent=%s), %s",
this, pSelection->acpStart, pSelection->acpEnd,
GetBoolName(aDispatchTextEvent),
mCompositionView ? "there is composition view" :
"there is no composition view"));
if (mCompositionView) {
if (aDispatchTextEvent) {
HRESULT hr = UpdateCompositionExtent(nullptr);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to "
"UpdateCompositionExtent() failure", this));
return hr;
}
}
if (pSelection->acpStart < mCompositionStart ||
pSelection->acpEnd >
mCompositionStart + static_cast<LONG>(mCompositionString.Length())) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to "
"the selection being out of the composition string", this));
return TS_E_INVALIDPOS;
}
// Emulate selection during compositions
mCompositionSelection = *pSelection;
if (aDispatchTextEvent) {
HRESULT hr = SendTextEventForCompositionString();
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to "
"SendTextEventForCompositionString() failure", this));
return hr;
}
}
return S_OK;
} else {
nsSelectionEvent event(true, NS_SELECTION_SET, mWindow);
event.mOffset = pSelection->acpStart;
event.mLength = uint32_t(pSelection->acpEnd - pSelection->acpStart);
event.mReversed = pSelection->style.ase == TS_AE_START;
mWindow->InitEvent(event);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelectionInternal() FAILED due to "
"NS_SELECTION_SET failure", this));
return E_FAIL;
}
}
return S_OK;
}
STDMETHODIMP
nsTextStore::SetSelection(ULONG ulCount,
const TS_SELECTION_ACP *pSelection)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SetSelection(ulCount=%lu)",
this, ulCount));
if (!IsReadWriteLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelection() FAILED due to "
"not locked (read-write)", this));
return TS_E_NOLOCK;
}
if (ulCount != 1) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelection() FAILED due to "
"trying setting multiple selection", this));
return E_INVALIDARG;
}
if (!pSelection) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelection() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
HRESULT hr = SetSelectionInternal(pSelection, true);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetSelection() FAILED due to "
"SetSelectionInternal() failure", this));
} else {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SetSelection() succeeded", this));
}
return hr;
}
STDMETHODIMP
nsTextStore::GetText(LONG acpStart,
LONG acpEnd,
WCHAR *pchPlain,
ULONG cchPlainReq,
ULONG *pcchPlainOut,
TS_RUNINFO *prgRunInfo,
ULONG ulRunInfoReq,
ULONG *pulRunInfoOut,
LONG *pacpNext)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
"cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
"pulRunInfoOut=0x%p, pacpNext=0x%p), %s, mCompositionStart=%ld, "
"mCompositionLength=%ld, mCompositionString.Length()=%lu",
this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut,
prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext,
mCompositionView ? "there is composition view" :
"there is no composition view",
mCompositionStart, mCompositionLength,
mCompositionString.Length()));
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetText() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
!cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetText() FAILED due to "
"invalid argument", this));
return E_INVALIDARG;
}
if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetText() FAILED due to "
"invalid position", this));
return TS_E_INVALIDPOS;
}
// Making sure to NULL-terminate string just to be on the safe side
*pcchPlainOut = 0;
if (pchPlain && cchPlainReq) *pchPlain = 0;
if (pulRunInfoOut) *pulRunInfoOut = 0;
if (pacpNext) *pacpNext = acpStart;
if (prgRunInfo && ulRunInfoReq) {
prgRunInfo->uCount = 0;
prgRunInfo->type = TS_RT_PLAIN;
}
uint32_t length = -1 == acpEnd ? UINT32_MAX : uint32_t(acpEnd - acpStart);
if (cchPlainReq && cchPlainReq - 1 < length) {
length = cchPlainReq - 1;
}
if (length) {
LONG compNewStart = 0, compOldEnd = 0, compNewEnd = 0;
if (mCompositionView) {
// Sometimes GetText gets called between InsertTextAtSelection and
// OnUpdateComposition. In this case the returned text would
// be out of sync because we haven't sent NS_TEXT_TEXT in
// OnUpdateComposition yet. Manually resync here.
compOldEnd = NS_MIN(LONG(length) + acpStart,
mCompositionLength + mCompositionStart);
compNewEnd = NS_MIN(LONG(length) + acpStart,
LONG(mCompositionString.Length()) + mCompositionStart);
compNewStart = NS_MAX(acpStart, mCompositionStart);
// Check if the range is affected
if (compOldEnd > compNewStart || compNewEnd > compNewStart) {
NS_ASSERTION(compOldEnd >= mCompositionStart &&
compNewEnd >= mCompositionStart, "Range end is less than start\n");
length = uint32_t(LONG(length) + compOldEnd - compNewEnd);
}
}
// Send NS_QUERY_TEXT_CONTENT to get text content
nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, mWindow);
mWindow->InitEvent(event);
event.InitForQueryTextContent(uint32_t(acpStart), length);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetText() FAILED due to "
"NS_QUERY_TEXT_CONTENT failure: length=%lu", this, length));
return E_FAIL;
}
if (compOldEnd > compNewStart || compNewEnd > compNewStart) {
// Resync composition string
const PRUnichar* compStrStart = mCompositionString.BeginReading() +
NS_MAX<LONG>(compNewStart - mCompositionStart, 0);
event.mReply.mString.Replace(compNewStart - acpStart,
compOldEnd - mCompositionStart, compStrStart,
compNewEnd - mCompositionStart);
length = uint32_t(LONG(length) - compOldEnd + compNewEnd);
}
if (-1 != acpEnd && event.mReply.mString.Length() != length) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetText() FAILED due to "
"unexpected length=%lu", this, length));
return TS_E_INVALIDPOS;
}
length = NS_MIN(length, event.mReply.mString.Length());
if (pchPlain && cchPlainReq) {
memcpy(pchPlain, event.mReply.mString.BeginReading(),
length * sizeof(*pchPlain));
pchPlain[length] = 0;
*pcchPlainOut = length;
}
if (prgRunInfo && ulRunInfoReq) {
prgRunInfo->uCount = length;
prgRunInfo->type = TS_RT_PLAIN;
if (pulRunInfoOut) *pulRunInfoOut = 1;
}
if (pacpNext) *pacpNext = acpStart + length;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
"*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
"*pacpNext=%ld)",
this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
pulRunInfoOut ? pulRunInfoOut : 0, pacpNext ? pacpNext : 0));
return S_OK;
}
STDMETHODIMP
nsTextStore::SetText(DWORD dwFlags,
LONG acpStart,
LONG acpEnd,
const WCHAR *pchText,
ULONG cch,
TS_TEXTCHANGE *pChange)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
"pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), %s",
this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" :
"not-specified",
acpStart, acpEnd, pchText,
pchText && cch ?
NS_ConvertUTF16toUTF8(pchText, cch).get() : "",
cch, pChange,
mCompositionView ? "there is composition view" :
"there is no composition view"));
// Per SDK documentation, and since we don't have better
// ways to do this, this method acts as a helper to
// call SetSelection followed by InsertTextAtSelection
if (!IsReadWriteLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetText() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
TS_SELECTION_ACP selection;
selection.acpStart = acpStart;
selection.acpEnd = acpEnd;
selection.style.ase = TS_AE_END;
selection.style.fInterimChar = 0;
// Set selection to desired range
HRESULT hr = SetSelectionInternal(&selection);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetText() FAILED due to "
"SetSelectionInternal() failure", this));
return hr;
}
// Replace just selected text
if (!InsertTextAtSelectionInternal(nsDependentString(pchText, cch),
pChange)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::SetText() FAILED due to "
"InsertTextAtSelectionInternal() failure", this));
return E_FAIL;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::SetText() succeeded: pChange={ "
"acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
this, pChange ? pChange->acpStart : 0,
pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
return S_OK;
}
STDMETHODIMP
nsTextStore::GetFormattedText(LONG acpStart,
LONG acpEnd,
IDataObject **ppDataObject)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetFormattedText() called "
"but not supported (E_NOTIMPL)", this));
// no support for formatted text
return E_NOTIMPL;
}
STDMETHODIMP
nsTextStore::GetEmbedded(LONG acpPos,
REFGUID rguidService,
REFIID riid,
IUnknown **ppunk)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetEmbedded() called "
"but not supported (E_NOTIMPL)", this));
// embedded objects are not supported
return E_NOTIMPL;
}
STDMETHODIMP
nsTextStore::QueryInsertEmbedded(const GUID *pguidService,
const FORMATETC *pFormatEtc,
BOOL *pfInsertable)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::QueryInsertEmbedded() called "
"but not supported, *pfInsertable=FALSE (S_OK)", this));
// embedded objects are not supported
*pfInsertable = FALSE;
return S_OK;
}
STDMETHODIMP
nsTextStore::InsertEmbedded(DWORD dwFlags,
LONG acpStart,
LONG acpEnd,
IDataObject *pDataObject,
TS_TEXTCHANGE *pChange)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::InsertEmbedded() called "
"but not supported (E_NOTIMPL)", this));
// embedded objects are not supported
return E_NOTIMPL;
}
STDMETHODIMP
nsTextStore::RequestSupportedAttrs(DWORD dwFlags,
ULONG cFilterAttrs,
const TS_ATTRID *paFilterAttrs)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestSupportedAttrs() called "
"but not supported (S_OK)", this));
// no attributes defined
return S_OK;
}
STDMETHODIMP
nsTextStore::RequestAttrsAtPosition(LONG acpPos,
ULONG cFilterAttrs,
const TS_ATTRID *paFilterAttrs,
DWORD dwFlags)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestAttrsAtPosition() called "
"but not supported (S_OK)", this));
// no per character attributes defined
return S_OK;
}
STDMETHODIMP
nsTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
ULONG cFilterAttrs,
const TS_ATTRID *paFilterAttr,
DWORD dwFlags)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RequestAttrsTransitioningAtPosition() called "
"but not supported (S_OK)", this));
// no per character attributes defined
return S_OK;
}
STDMETHODIMP
nsTextStore::FindNextAttrTransition(LONG acpStart,
LONG acpHalt,
ULONG cFilterAttrs,
const TS_ATTRID *paFilterAttrs,
DWORD dwFlags,
LONG *pacpNext,
BOOL *pfFound,
LONG *plFoundOffset)
{
if (!pacpNext || !pfFound || !plFoundOffset) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::FindNextAttrTransition() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::FindNextAttrTransition() called "
"but not supported (S_OK)", this));
// no per character attributes defined
*pacpNext = *plFoundOffset = acpHalt;
*pfFound = FALSE;
return S_OK;
}
STDMETHODIMP
nsTextStore::RetrieveRequestedAttrs(ULONG ulCount,
TS_ATTRVAL *paAttrVals,
ULONG *pcFetched)
{
if (!pcFetched || !ulCount || !paAttrVals) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::RetrieveRequestedAttrs() called "
"but not supported, *pcFetched=0 (S_OK)", this));
// no attributes defined
*pcFetched = 0;
return S_OK;
}
STDMETHODIMP
nsTextStore::GetEndACP(LONG *pacp)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetEndACP(pacp=0x%p)", this, pacp));
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
if (!pacp) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
// Flattened text is retrieved and its length returned
nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, mWindow);
mWindow->InitEvent(event);
// Return entire text
event.InitForQueryTextContent(0, INT32_MAX);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetEndACP() FAILED due to "
"NS_QUERY_TEXT_CONTENT failure", this));
return E_FAIL;
}
*pacp = LONG(event.mReply.mString.Length());
return S_OK;
}
#define TEXTSTORE_DEFAULT_VIEW (1)
STDMETHODIMP
nsTextStore::GetActiveView(TsViewCookie *pvcView)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
if (!pvcView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetActiveView() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
*pvcView = TEXTSTORE_DEFAULT_VIEW;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetActiveView() succeeded: *pvcView=%ld",
this, *pvcView));
return S_OK;
}
STDMETHODIMP
nsTextStore::GetACPFromPoint(TsViewCookie vcView,
const POINT *pt,
DWORD dwFlags,
LONG *pacp)
{
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
if (vcView != TEXTSTORE_DEFAULT_VIEW) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetACPFromPoint() FAILED due to "
"called with invalid view", this));
return E_INVALIDARG;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetACPFromPoint(vcView=%ld, "
"pt(0x%p)={ x=%ld, y=%ld }, dwFlags=%s, pacp=0x%p) called "
"but not supported (E_NOTIMPL)", this));
// not supported for now
return E_NOTIMPL;
}
STDMETHODIMP
nsTextStore::GetTextExt(TsViewCookie vcView,
LONG acpStart,
LONG acpEnd,
RECT *prc,
BOOL *pfClipped)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetTextExt(vcView=%ld, "
"acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p)",
this, vcView, acpStart, acpEnd, prc, pfClipped));
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
if (vcView != TEXTSTORE_DEFAULT_VIEW) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"called with invalid view", this));
return E_INVALIDARG;
}
if (!prc || !pfClipped) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
if (acpStart < 0 || acpEnd < acpStart) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"invalid position", this));
return TS_E_INVALIDPOS;
}
// use NS_QUERY_TEXT_RECT to get rect in system, screen coordinates
nsQueryContentEvent event(true, NS_QUERY_TEXT_RECT, mWindow);
mWindow->InitEvent(event);
event.InitForQueryTextRect(acpStart, acpEnd - acpStart);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"NS_QUERY_TEXT_RECT failure", this));
return TS_E_INVALIDPOS; // but unexpected failure, maybe.
}
// IMEs don't like empty rects, fix here
if (event.mReply.mRect.width <= 0)
event.mReply.mRect.width = 1;
if (event.mReply.mRect.height <= 0)
event.mReply.mRect.height = 1;
// convert to unclipped screen rect
nsWindow* refWindow = static_cast<nsWindow*>(
event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow);
// Result rect is in top level widget coordinates
refWindow = refWindow->GetTopLevelWindow(false);
if (!refWindow) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"no top level window", this));
return E_FAIL;
}
event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset());
// get bounding screen rect to test for clipping
if (!GetScreenExtInternal(*prc)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetTextExt() FAILED due to "
"GetScreenExtInternal() failure", this));
return E_FAIL;
}
// clip text rect to bounding rect
RECT textRect;
::SetRect(&textRect, event.mReply.mRect.x, event.mReply.mRect.y,
event.mReply.mRect.XMost(), event.mReply.mRect.YMost());
if (!::IntersectRect(prc, prc, &textRect))
// Text is not visible
::SetRectEmpty(prc);
// not equal if text rect was clipped
*pfClipped = !::EqualRect(prc, &textRect);
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetTextExt() succeeded: "
"*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
this, prc->left, prc->top, prc->right, prc->bottom,
GetBoolName(*pfClipped)));
return S_OK;
}
STDMETHODIMP
nsTextStore::GetScreenExt(TsViewCookie vcView,
RECT *prc)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetScreenExt(vcView=%ld, prc=0x%p)",
this, vcView, prc));
if (vcView != TEXTSTORE_DEFAULT_VIEW) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to "
"called with invalid view", this));
return E_INVALIDARG;
}
if (!prc) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
if (!GetScreenExtInternal(*prc)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to "
"GetScreenExtInternal() failure", this));
return E_FAIL;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetScreenExt() succeeded: "
"*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
this, prc->left, prc->top, prc->right, prc->bottom));
return S_OK;
}
bool
nsTextStore::GetScreenExtInternal(RECT &aScreenExt)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetScreenExtInternal()", this));
// use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
nsQueryContentEvent event(true, NS_QUERY_EDITOR_RECT, mWindow);
mWindow->InitEvent(event);
mWindow->DispatchWindowEvent(&event);
if (!event.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to "
"NS_QUERY_EDITOR_RECT failure", this));
return false;
}
nsWindow* refWindow = static_cast<nsWindow*>(
event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWindow);
// Result rect is in top level widget coordinates
refWindow = refWindow->GetTopLevelWindow(false);
if (!refWindow) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to "
"no top level window", this));
return false;
}
nsIntRect boundRect;
if (NS_FAILED(refWindow->GetClientBounds(boundRect))) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExtInternal() FAILED due to "
"failed to get the client bounds", this));
return false;
}
boundRect.MoveTo(0, 0);
// Clip frame rect to window rect
boundRect.IntersectRect(event.mReply.mRect, boundRect);
if (!boundRect.IsEmpty()) {
boundRect.MoveBy(refWindow->WidgetToScreenOffset());
::SetRect(&aScreenExt, boundRect.x, boundRect.y,
boundRect.XMost(), boundRect.YMost());
} else {
::SetRectEmpty(&aScreenExt);
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::GetScreenExtInternal() succeeded: "
"aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
this, aScreenExt.left, aScreenExt.top,
aScreenExt.right, aScreenExt.bottom));
return true;
}
STDMETHODIMP
nsTextStore::GetWnd(TsViewCookie vcView,
HWND *phwnd)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetWnd(vcView=%ld, phwnd=0x%p), mWindow=0x%p",
this, vcView, phwnd, mWindow));
if (vcView != TEXTSTORE_DEFAULT_VIEW) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetWnd() FAILED due to "
"called with invalid view", this));
return E_INVALIDARG;
}
if (!phwnd) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::GetScreenExt() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
*phwnd = mWindow->GetWindowHandle();
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::GetWnd() succeeded: *phwnd=0x%p",
this, static_cast<void*>(*phwnd)));
return S_OK;
}
STDMETHODIMP
nsTextStore::InsertTextAtSelection(DWORD dwFlags,
const WCHAR *pchText,
ULONG cch,
LONG *pacpStart,
LONG *pacpEnd,
TS_TEXTCHANGE *pChange)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::InsertTextAtSelection(dwFlags=%s, "
"pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
"pChange=0x%p), %s",
this, dwFlags == 0 ? "0" :
dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY" :
dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown",
pchText,
pchText && cch ? NS_ConvertUTF16toUTF8(pchText, cch).get() : "",
cch, pacpStart, pacpEnd, pChange,
mCompositionView ? "there is composition view" :
"there is no composition view"));
if (cch && !pchText) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"null pchText", this));
return E_INVALIDARG;
}
if (TS_IAS_QUERYONLY == dwFlags) {
if (!IsReadLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"not locked (read)", this));
return TS_E_NOLOCK;
}
if (!pacpStart || !pacpEnd) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
// Get selection first
TS_SELECTION_ACP sel;
if (!GetSelectionInternal(sel)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"GetSelectionInternal() failure", this));
return E_FAIL;
}
// Simulate text insertion
*pacpStart = sel.acpStart;
*pacpEnd = sel.acpEnd;
if (pChange) {
pChange->acpStart = sel.acpStart;
pChange->acpOldEnd = sel.acpEnd;
pChange->acpNewEnd = sel.acpStart + cch;
}
} else {
if (!IsReadWriteLocked()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"not locked (read-write)", this));
return TS_E_NOLOCK;
}
if (!pChange) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"null pChange", this));
return E_INVALIDARG;
}
if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"null argument", this));
return E_INVALIDARG;
}
if (!InsertTextAtSelectionInternal(nsDependentString(pchText, cch),
pChange)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() FAILED due to "
"InsertTextAtSelectionInternal() failure", this));
return E_FAIL;
}
if (TS_IAS_NOQUERY != dwFlags) {
*pacpStart = pChange->acpStart;
*pacpEnd = pChange->acpNewEnd;
}
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::InsertTextAtSelection() succeeded: "
"*pacpStart=%ld, *pacpEnd=%ld, "
"*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
pChange ? pChange->acpStart: 0, pChange ? pChange->acpOldEnd : 0,
pChange ? pChange->acpNewEnd : 0));
return S_OK;
}
bool
nsTextStore::InsertTextAtSelectionInternal(const nsAString &aInsertStr,
TS_TEXTCHANGE* aTextChange)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal("
"aInsertStr=\"%s\", aTextChange=0x%p), %s",
this, NS_ConvertUTF16toUTF8(aInsertStr).get(), aTextChange,
mCompositionView ? "there is composition view" :
"there is no composition view"));
TS_SELECTION_ACP oldSelection;
oldSelection.acpStart = 0;
oldSelection.acpEnd = 0;
if (mCompositionView) {
oldSelection = mCompositionSelection;
// Emulate text insertion during compositions, because during a
// composition, editor expects the whole composition string to
// be sent in NS_TEXT_TEXT, not just the inserted part.
// The actual NS_TEXT_TEXT will be sent in SetSelection or
// OnUpdateComposition.
mCompositionString.Replace(
static_cast<uint32_t>(oldSelection.acpStart) - mCompositionStart,
static_cast<uint32_t>(oldSelection.acpEnd - oldSelection.acpStart),
aInsertStr);
mCompositionSelection.acpStart += aInsertStr.Length();
mCompositionSelection.acpEnd = mCompositionSelection.acpStart;
mCompositionSelection.style.ase = TS_AE_END;
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() replaced "
"a part of (%lu-%lu) the composition string, waiting "
"SetSelection() or OnUpdateComposition()...", this,
oldSelection.acpStart - mCompositionStart,
oldSelection.acpEnd - mCompositionStart));
} else {
// Use actual selection if it's not composing.
if (aTextChange && !GetSelectionInternal(oldSelection)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED "
"due to GetSelectionInternal() failure", this));
return false;
}
// Use a temporary composition to contain the text
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() "
"dispatching a compositionstart event...", this));
nsCompositionEvent compStartEvent(true, NS_COMPOSITION_START, mWindow);
mWindow->InitEvent(compStartEvent);
mWindow->DispatchWindowEvent(&compStartEvent);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED "
"due to the widget destroyed by compositionstart event", this));
return false;
}
if (!aInsertStr.IsEmpty()) {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() "
"dispatching a compositionupdate event...", this));
nsCompositionEvent compUpdateEvent(true, NS_COMPOSITION_UPDATE, mWindow);
compUpdateEvent.data = aInsertStr;
mWindow->DispatchWindowEvent(&compUpdateEvent);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() "
"FAILED due to the widget destroyed by compositionupdate event",
this));
return false;
}
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() "
"dispatching a text event...", this));
nsTextEvent textEvent(true, NS_TEXT_TEXT, mWindow);
mWindow->InitEvent(textEvent);
textEvent.theText = aInsertStr;
textEvent.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
NS_LITERAL_STRING("\n"));
mWindow->DispatchWindowEvent(&textEvent);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED "
"due to the widget destroyed by text event", this));
return false;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() "
"dispatching a compositionend event...", this));
nsCompositionEvent compEndEvent(true, NS_COMPOSITION_END, mWindow);
compEndEvent.data = aInsertStr;
mWindow->DispatchWindowEvent(&compEndEvent);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED "
"due to the widget destroyed by compositionend event", this));
return false;
}
}
if (aTextChange) {
aTextChange->acpStart = oldSelection.acpStart;
aTextChange->acpOldEnd = oldSelection.acpEnd;
// Get new selection
TS_SELECTION_ACP newSelection;
if (!GetSelectionInternal(newSelection)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() FAILED "
"due to GetSelectionInternal() failure after inserted the text",
this));
return false;
}
aTextChange->acpNewEnd = newSelection.acpEnd;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::InsertTextAtSelectionInternal() succeeded: "
"mWindow=0x%p, mWindow->Destroyed()=%s, aTextChange={ acpStart=%ld, "
"acpOldEnd=%ld, acpNewEnd=%ld }",
this, mWindow, GetBoolName(mWindow ? mWindow->Destroyed() : true),
aTextChange ? aTextChange->acpStart : 0,
aTextChange ? aTextChange->acpOldEnd : 0,
aTextChange ? aTextChange->acpNewEnd : 0));
return true;
}
STDMETHODIMP
nsTextStore::InsertEmbeddedAtSelection(DWORD dwFlags,
IDataObject *pDataObject,
LONG *pacpStart,
LONG *pacpEnd,
TS_TEXTCHANGE *pChange)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::InsertEmbeddedAtSelection() called "
"but not supported (E_NOTIMPL)", this));
// embedded objects are not supported
return E_NOTIMPL;
}
HRESULT
nsTextStore::OnStartCompositionInternal(ITfCompositionView* pComposition,
ITfRange* aRange,
bool aPreserveSelection)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal("
"pComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), "
"mCompositionView=0x%p",
this, pComposition, aRange, GetBoolName(aPreserveSelection),
mCompositionView.get()));
mCompositionView = pComposition;
HRESULT hr = GetRangeExtent(aRange, &mCompositionStart, &mCompositionLength);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due "
"to GetRangeExtent() failure", this));
return hr;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), "
"mCompositionStart=%ld, mCompositionLength=%ld",
this, mCompositionStart, mCompositionLength));
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), "
"dispatching selectionset event..."));
// Select composition range so the new composition replaces the range
nsSelectionEvent selEvent(true, NS_SELECTION_SET, mWindow);
mWindow->InitEvent(selEvent);
selEvent.mOffset = uint32_t(mCompositionStart);
selEvent.mLength = uint32_t(mCompositionLength);
selEvent.mReversed = false;
mWindow->DispatchWindowEvent(&selEvent);
if (!selEvent.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due "
"to NS_SELECTION_SET failure", this));
return E_FAIL;
}
// Set up composition
nsQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, mWindow);
mWindow->InitEvent(queryEvent);
mWindow->DispatchWindowEvent(&queryEvent);
if (!queryEvent.mSucceeded) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal() FAILED due "
"to NS_QUERY_SELECTED_TEXT failure", this));
return E_FAIL;
}
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal(), "
"dispatching compositionstart event..."));
mCompositionString = queryEvent.mReply.mString;
if (!aPreserveSelection) {
mCompositionSelection.acpStart = mCompositionStart;
mCompositionSelection.acpEnd = mCompositionStart + mCompositionLength;
mCompositionSelection.style.ase = TS_AE_END;
mCompositionSelection.style.fInterimChar = FALSE;
}
nsCompositionEvent event(true, NS_COMPOSITION_START, mWindow);
mWindow->InitEvent(event);
mWindow->DispatchWindowEvent(&event);
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnStartCompositionInternal() succeeded: "
"mCompositionStart=%ld, mCompositionLength=%ld, "
"mCompositionSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
"style.iInterimChar=%s }",
this, mCompositionStart, mCompositionLength,
mCompositionSelection.acpStart, mCompositionSelection.acpEnd,
GetActiveSelEndName(mCompositionSelection.style.ase),
GetBoolName(mCompositionSelection.style.fInterimChar)));
return S_OK;
}
static uint32_t
GetLayoutChangeIntervalTime()
{
static int32_t sTime = -1;
if (sTime > 0)
return uint32_t(sTime);
sTime = NS_MAX(10,
Preferences::GetInt("intl.tsf.on_layout_change_interval", 100));
return uint32_t(sTime);
}
STDMETHODIMP
nsTextStore::OnStartComposition(ITfCompositionView* pComposition,
BOOL* pfOk)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnStartComposition(pComposition=0x%p, "
"pfOk=0x%p), mCompositionView=0x%p",
this, pComposition, pfOk, mCompositionView.get()));
*pfOk = FALSE;
// Only one composition at a time
if (mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to "
"there is another composition already (but returns S_OK)", this));
return S_OK;
}
nsRefPtr<ITfRange> range;
HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to "
"pComposition->GetRange() failure", this));
return hr;
}
hr = OnStartCompositionInternal(pComposition, range, false);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnStartComposition() FAILED due to "
"OnStartCompositionInternal() failure", this));
return hr;
}
NS_ASSERTION(!mCompositionTimer, "The timer is alive!");
mCompositionTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (mCompositionTimer) {
mCompositionTimer->InitWithFuncCallback(CompositionTimerCallbackFunc, this,
GetLayoutChangeIntervalTime(),
nsITimer::TYPE_REPEATING_SLACK);
}
*pfOk = TRUE;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnStartComposition() succeeded", this));
return S_OK;
}
STDMETHODIMP
nsTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
ITfRange* pRangeNew)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnUpdateComposition(pComposition=0x%p, "
"pRangeNew=0x%p), mCompositionView=0x%p",
this, pComposition, pRangeNew, mCompositionView.get()));
if (!mDocumentMgr || !mContext) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to "
"not ready for the composition", this));
return E_UNEXPECTED;
}
if (!mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to "
"no active composition", this));
return E_UNEXPECTED;
}
if (mCompositionView != pComposition) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to "
"different composition view specified", this));
return E_UNEXPECTED;
}
// pRangeNew is null when the update is not complete
if (!pRangeNew) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded but "
"not complete", this));
return S_OK;
}
HRESULT hr = UpdateCompositionExtent(pRangeNew);
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to "
"UpdateCompositionExtent() failure", this));
return hr;
}
hr = SendTextEventForCompositionString();
if (FAILED(hr)) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnUpdateComposition() FAILED due to "
"SendTextEventForCompositionString() failure", this));
return hr;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnUpdateComposition() succeeded: "
"mCompositionStart=%ld, mCompositionLength=%ld, "
"mCompositionSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
"style.iInterimChar=%s }, mCompositionString=\"%s\"",
this, mCompositionStart, mCompositionLength,
mCompositionSelection.acpStart, mCompositionSelection.acpEnd,
GetActiveSelEndName(mCompositionSelection.style.ase),
GetBoolName(mCompositionSelection.style.fInterimChar),
NS_ConvertUTF16toUTF8(mCompositionString).get()));
return S_OK;
}
STDMETHODIMP
nsTextStore::OnEndComposition(ITfCompositionView* pComposition)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(pComposition=0x%p), "
"mCompositionView=0x%p, mCompositionString=\"%s\"",
this, pComposition, mCompositionView.get(),
NS_ConvertUTF16toUTF8(mCompositionString).get()));
if (!mCompositionView) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to "
"no active composition", this));
return E_UNEXPECTED;
}
if (mCompositionView != pComposition) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: 0x%p nsTextStore::OnEndComposition() FAILED due to "
"different composition view specified", this));
return E_UNEXPECTED;
}
// Clear the saved text event
SaveTextEvent(nullptr);
if (mCompositionTimer) {
mCompositionTimer->Cancel();
mCompositionTimer = nullptr;
}
if (mCompositionString != mLastDispatchedCompositionString) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"dispatching compositionupdate event...", this));
nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
mWindow);
mWindow->InitEvent(compositionUpdate);
compositionUpdate.data = mCompositionString;
mLastDispatchedCompositionString = mCompositionString;
mWindow->DispatchWindowEvent(&compositionUpdate);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"succeeded, but the widget has gone", this));
return S_OK;
}
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"dispatching text event...", this));
// Use NS_TEXT_TEXT to commit composition string
nsTextEvent textEvent(true, NS_TEXT_TEXT, mWindow);
mWindow->InitEvent(textEvent);
textEvent.theText = mCompositionString;
textEvent.theText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
NS_LITERAL_STRING("\n"));
mWindow->DispatchWindowEvent(&textEvent);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"succeeded, but the widget has gone", this));
return S_OK;
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"dispatching compositionend event...", this));
nsCompositionEvent event(true, NS_COMPOSITION_END, mWindow);
event.data = mLastDispatchedCompositionString;
mWindow->InitEvent(event);
mWindow->DispatchWindowEvent(&event);
if (!mWindow || mWindow->Destroyed()) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), "
"succeeded, but the widget has gone", this));
return S_OK;
}
mCompositionView = NULL;
mCompositionString.Truncate(0);
mLastDispatchedCompositionString.Truncate();
// Maintain selection
SetSelectionInternal(&mCompositionSelection);
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnEndComposition(), succeeded", this));
return S_OK;
}
// static
nsresult
nsTextStore::OnFocusChange(bool aFocus,
nsWindow* aWindow,
IMEState::Enabled aIMEEnabled)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: nsTextStore::OnFocusChange(aFocus=%s, aWindow=0x%p, "
"aIMEEnabled=%s), sTsfThreadMgr=0x%p, sTsfTextStore=0x%p",
GetBoolName(aFocus), aWindow, GetIMEEnabledName(aIMEEnabled),
sTsfThreadMgr, sTsfTextStore));
// no change notifications if TSF is disabled
if (!sTsfThreadMgr || !sTsfTextStore)
return NS_ERROR_NOT_AVAILABLE;
if (aFocus) {
bool bRet = sTsfTextStore->Create(aWindow, aIMEEnabled);
NS_ENSURE_TRUE(bRet, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(sTsfTextStore->mDocumentMgr, NS_ERROR_FAILURE);
HRESULT hr = sTsfThreadMgr->SetFocus(sTsfTextStore->mDocumentMgr);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
} else {
sTsfTextStore->Destroy();
}
return NS_OK;
}
nsresult
nsTextStore::OnTextChangeInternal(uint32_t aStart,
uint32_t aOldEnd,
uint32_t aNewEnd)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnTextChangeInternal(aStart=%lu, "
"aOldEnd=%lu, aNewEnd=%lu), mLock=%s, mSink=0x%p, mSinkMask=%s, "
"mTextChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
this, aStart, aOldEnd, aNewEnd, GetLockFlagNameStr(mLock).get(),
mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
mTextChange.acpStart, mTextChange.acpOldEnd, mTextChange.acpNewEnd));
if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE)) {
mTextChange.acpStart = NS_MIN(mTextChange.acpStart, LONG(aStart));
mTextChange.acpOldEnd = NS_MAX(mTextChange.acpOldEnd, LONG(aOldEnd));
mTextChange.acpNewEnd = NS_MAX(mTextChange.acpNewEnd, LONG(aNewEnd));
::PostMessageW(mWindow->GetWindowHandle(),
WM_USER_TSF_TEXTCHANGE, 0, 0);
}
return NS_OK;
}
void
nsTextStore::OnTextChangeMsgInternal(void)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnTextChangeMsgInternal(), mLock=%s, "
"mSink=0x%p, mSinkMask=%s, mTextChange={ acpStart=%ld, "
"acpOldEnd=%ld, acpNewEnd=%ld }",
this, GetLockFlagNameStr(mLock).get(), mSink.get(),
GetSinkMaskNameStr(mSinkMask).get(), mTextChange.acpStart,
mTextChange.acpOldEnd, mTextChange.acpNewEnd));
if (!mLock && mSink && 0 != (mSinkMask & TS_AS_TEXT_CHANGE) &&
INT32_MAX > mTextChange.acpStart) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnTextChangeMsgInternal(), calling"
"mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
"acpNewEnd=%ld })...", this, mTextChange.acpStart,
mTextChange.acpOldEnd, mTextChange.acpNewEnd));
mSink->OnTextChange(0, &mTextChange);
mTextChange.acpStart = INT32_MAX;
mTextChange.acpOldEnd = mTextChange.acpNewEnd = 0;
}
}
nsresult
nsTextStore::OnSelectionChangeInternal(void)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), mLock=%s, "
"mSink=0x%p, mSinkMask=%s",
this, GetLockFlagNameStr(mLock).get(), mSink.get(),
GetSinkMaskNameStr(mSinkMask).get()));
if (!mLock && mSink && 0 != (mSinkMask & TS_AS_SEL_CHANGE)) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnSelectionChangeInternal(), calling "
"mSink->OnSelectionChange()...", this));
mSink->OnSelectionChange();
}
return NS_OK;
}
nsresult
nsTextStore::OnCompositionTimer()
{
NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
// XXXmnakano We always call OnLayoutChange for now, but this might use CPU
// power when the focused editor has very long text. Ideally, we should call
// this only when the composition string screen position is changed by window
// moving, resizing. And also reflowing and scrolling the contents.
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::OnCompositionTimer(), calling "
"mSink->OnLayoutChange()...", this));
HRESULT hr = mSink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
return NS_OK;
}
void
nsTextStore::CommitCompositionInternal(bool aDiscard)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::CommitCompositionInternal(aDiscard=%s), "
"mLock=%s, mSink=0x%p, mContext=0x%p, mCompositionView=0x%p, "
"mCompositionString=\"%s\"",
this, GetBoolName(aDiscard), GetLockFlagNameStr(mLock).get(),
mSink.get(), mContext.get(), mCompositionView.get(),
NS_ConvertUTF16toUTF8(mCompositionString).get()));
if (mCompositionView && aDiscard) {
mCompositionString.Truncate(0);
if (mSink && !mLock) {
TS_TEXTCHANGE textChange;
textChange.acpStart = mCompositionStart;
textChange.acpOldEnd = mCompositionStart + mCompositionLength;
textChange.acpNewEnd = mCompositionStart;
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: 0x%p nsTextStore::CommitCompositionInternal(), calling"
"mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
"acpNewEnd=%ld })...", this, textChange.acpStart,
textChange.acpOldEnd, textChange.acpNewEnd));
mSink->OnTextChange(0, &textChange);
}
}
// Terminate two contexts, the base context (mContext) and the top
// if the top context is not the same as the base context
nsRefPtr<ITfContext> context = mContext;
do {
if (context) {
nsRefPtr<ITfContextOwnerCompositionServices> services;
context->QueryInterface(IID_ITfContextOwnerCompositionServices,
getter_AddRefs(services));
if (services) {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::CommitCompositionInternal(), "
"requesting TerminateComposition() for the context 0x%p...",
this, context.get()));
services->TerminateComposition(NULL);
}
}
if (context != mContext)
break;
if (mDocumentMgr)
mDocumentMgr->GetTop(getter_AddRefs(context));
} while (context != mContext);
}
static
bool
GetCompartment(IUnknown* pUnk,
const GUID& aID,
ITfCompartment** aCompartment)
{
if (!pUnk) return false;
nsRefPtr<ITfCompartmentMgr> compMgr;
pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
if (!compMgr) return false;
return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
(*aCompartment) != NULL;
}
// static
void
nsTextStore::SetIMEOpenState(bool aState)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: nsTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
nsRefPtr<ITfCompartment> comp;
if (!GetCompartment(sTsfThreadMgr,
GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
getter_AddRefs(comp))) {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: nsTextStore::SetIMEOpenState() FAILED due to"
"no compartment available"));
return;
}
VARIANT variant;
variant.vt = VT_I4;
variant.lVal = aState;
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: nsTextStore::SetIMEOpenState(), setting "
"0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
variant.lVal));
comp->SetValue(sTsfClientId, &variant);
}
// static
bool
nsTextStore::GetIMEOpenState(void)
{
nsRefPtr<ITfCompartment> comp;
if (!GetCompartment(sTsfThreadMgr,
GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
getter_AddRefs(comp)))
return false;
VARIANT variant;
::VariantInit(&variant);
if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4)
return variant.lVal != 0;
::VariantClear(&variant); // clear up in case variant.vt != VT_I4
return false;
}
void
nsTextStore::SetInputContextInternal(IMEState::Enabled aState)
{
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::SetInputContextInternal(aState=%s), "
"mContext=0x%p",
this, GetIMEEnabledName(aState), mContext.get()));
VARIANT variant;
variant.vt = VT_I4;
variant.lVal = (aState != IMEState::ENABLED);
// Set two contexts, the base context (mContext) and the top
// if the top context is not the same as the base context
nsRefPtr<ITfContext> context = mContext;
nsRefPtr<ITfCompartment> comp;
do {
if (GetCompartment(context, GUID_COMPARTMENT_KEYBOARD_DISABLED,
getter_AddRefs(comp))) {
PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
("TSF: 0x%p nsTextStore::SetInputContextInternal(), setting "
"0x%04X to GUID_COMPARTMENT_KEYBOARD_DISABLED of context 0x%p...",
this, variant.lVal, context.get()));
comp->SetValue(sTsfClientId, &variant);
}
if (context != mContext)
break;
if (mDocumentMgr)
mDocumentMgr->GetTop(getter_AddRefs(context));
} while (context != mContext);
}
// static
void
nsTextStore::Initialize(void)
{
#ifdef PR_LOGGING
if (!sTextStoreLog) {
sTextStoreLog = PR_NewLogModule("nsTextStoreWidgets");
}
#endif
bool enableTsf = Preferences::GetBool("intl.enable_tsf_support", false);
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize(), TSF is %s",
enableTsf ? "enabled" : "disabled"));
if (!enableTsf) {
return;
}
if (!sTsfThreadMgr) {
if (SUCCEEDED(CoCreateInstance(CLSID_TF_ThreadMgr, NULL,
CLSCTX_INPROC_SERVER, IID_ITfThreadMgr,
reinterpret_cast<void**>(&sTsfThreadMgr)))) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize() succeeded to "
"create the thread manager, activating..."));
if (FAILED(sTsfThreadMgr->Activate(&sTsfClientId))) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: nsTextStore::Initialize() FAILED to activate, "
"releasing the thread manager..."));
NS_RELEASE(sTsfThreadMgr);
}
}
#ifdef PR_LOGGING
else {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: nsTextStore::Initialize() FAILED to "
"create the thread manager"));
}
#endif // #ifdef PR_LOGGING
}
if (sTsfThreadMgr && !sTsfTextStore) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize() is creating "
"an nsTextStore instance..."));
sTsfTextStore = new nsTextStore();
}
if (sTsfThreadMgr && !sDisplayAttrMgr) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize() is creating "
"a display attribute manager instance..."));
HRESULT hr =
::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, NULL,
CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr,
reinterpret_cast<void**>(&sDisplayAttrMgr));
if (FAILED(hr) || !sDisplayAttrMgr) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: nsTextStore::Initialize() FAILED to create "
"a display attribute manager instance"));
}
}
if (sTsfThreadMgr && sDisplayAttrMgr && !sCategoryMgr) {
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize() is creating "
"a category manager instance..."));
HRESULT hr =
::CoCreateInstance(CLSID_TF_CategoryMgr, NULL,
CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
reinterpret_cast<void**>(&sCategoryMgr));
if (FAILED(hr) || !sCategoryMgr) {
PR_LOG(sTextStoreLog, PR_LOG_ERROR,
("TSF: nsTextStore::Initialize() FAILED to create "
"a category manager instance"));
// release the display manager because it cannot work without the
// category manager
NS_RELEASE(sDisplayAttrMgr);
}
}
if (sTsfThreadMgr && !sFlushTIPInputMessage) {
sFlushTIPInputMessage = ::RegisterWindowMessageW(
NS_LITERAL_STRING("Flush TIP Input Message").get());
}
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
("TSF: nsTextStore::Initialize(), sTsfThreadMgr=0x%p, "
"sTsfClientId=0x%08X, sTsfTextStore=0x%p, sDisplayAttrMgr=0x%p, "
"sCategoryMgr=0x%p",
sTsfThreadMgr, sTsfClientId, sTsfTextStore,
sDisplayAttrMgr, sCategoryMgr));
}
// static
void
nsTextStore::Terminate(void)
{
PR_LOG(sTextStoreLog, PR_LOG_ALWAYS, ("TSF: nsTextStore::Terminate()"));
NS_IF_RELEASE(sDisplayAttrMgr);
NS_IF_RELEASE(sCategoryMgr);
NS_IF_RELEASE(sTsfTextStore);
sTsfClientId = 0;
if (sTsfThreadMgr) {
sTsfThreadMgr->Deactivate();
NS_RELEASE(sTsfThreadMgr);
}
}