2025-11-28 00:35:46 +09:00

636 lines
19 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
#include "Private.h"
#include "Globals.h"
#include "EditSession.h"
#include "SampleIME.h"
#include "CandidateListUIPresenter.h"
#include "CompositionProcessorEngine.h"
//////////////////////////////////////////////////////////////////////
//
// CSampleIME class
//
//////////////////////////////////////////////////////////////////////
//+---------------------------------------------------------------------------
//
// _IsRangeCovered
//
// Returns TRUE if pRangeTest is entirely contained within pRangeCover.
//
//----------------------------------------------------------------------------
BOOL CSampleIME::_IsRangeCovered(TfEditCookie ec, _In_ ITfRange *pRangeTest, _In_ ITfRange *pRangeCover)
{
LONG lResult = 0;;
if (FAILED(pRangeCover->CompareStart(ec, pRangeTest, TF_ANCHOR_START, &lResult))
|| (lResult > 0))
{
return FALSE;
}
if (FAILED(pRangeCover->CompareEnd(ec, pRangeTest, TF_ANCHOR_END, &lResult))
|| (lResult < 0))
{
return FALSE;
}
return TRUE;
}
//+---------------------------------------------------------------------------
//
// _DeleteCandidateList
//
//----------------------------------------------------------------------------
VOID CSampleIME::_DeleteCandidateList(BOOL isForce, _In_opt_ ITfContext *pContext)
{
isForce;pContext;
CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
pCompositionProcessorEngine = _pCompositionProcessorEngine;
pCompositionProcessorEngine->PurgeVirtualKey();
if (_pCandidateListUIPresenter)
{
_pCandidateListUIPresenter->_EndCandidateList();
_candidateMode = CANDIDATE_NONE;
_isCandidateWithWildcard = FALSE;
}
}
//+---------------------------------------------------------------------------
//
// _HandleComplete
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleComplete(TfEditCookie ec, _In_ ITfContext *pContext)
{
_DeleteCandidateList(FALSE, pContext);
// just terminate the composition
_TerminateComposition(ec, pContext);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCancel
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCancel(TfEditCookie ec, _In_ ITfContext *pContext)
{
_RemoveDummyCompositionForComposing(ec, _pComposition);
_DeleteCandidateList(FALSE, pContext);
_TerminateComposition(ec, pContext);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionInput
//
// If the keystroke happens within a composition, eat the key and return S_OK.
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionInput(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch)
{
ITfRange* pRangeComposition = nullptr;
TF_SELECTION tfSelection;
ULONG fetched = 0;
BOOL isCovered = TRUE;
CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
pCompositionProcessorEngine = _pCompositionProcessorEngine;
if ((_pCandidateListUIPresenter != nullptr) && (_candidateMode != CANDIDATE_INCREMENTAL))
{
_HandleCompositionFinalize(ec, pContext, FALSE);
}
// Start the new (std::nothrow) compositon if there is no composition.
if (!_IsComposing())
{
_StartComposition(pContext);
}
// first, test where a keystroke would go in the document if we did an insert
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched) != S_OK || fetched != 1)
{
return S_FALSE;
}
// is the insertion point covered by a composition?
if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition)))
{
isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition);
pRangeComposition->Release();
if (!isCovered)
{
goto Exit;
}
}
// Add virtual key to composition processor engine
pCompositionProcessorEngine->AddVirtualKey(wch);
_HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext);
Exit:
tfSelection.range->Release();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionInputWorker
//
// If the keystroke happens within a composition, eat the key and return S_OK.
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionInputWorker(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext)
{
HRESULT hr = S_OK;
CSampleImeArray<CStringRange> readingStrings;
BOOL isWildcardIncluded = TRUE;
//
// Get reading string from composition processor engine
//
pCompositionProcessorEngine->GetReadingStrings(&readingStrings, &isWildcardIncluded);
for (UINT index = 0; index < readingStrings.Count(); index++)
{
hr = _AddComposingAndChar(ec, pContext, readingStrings.GetAt(index));
if (FAILED(hr))
{
return hr;
}
}
//
// Get candidate string from composition processor engine
//
CSampleImeArray<CCandidateListItem> candidateList;
pCompositionProcessorEngine->GetCandidateList(&candidateList, TRUE, FALSE);
if ((candidateList.Count()))
{
hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext);
if (SUCCEEDED(hr))
{
_pCandidateListUIPresenter->_ClearList();
_pCandidateListUIPresenter->_SetText(&candidateList, TRUE);
}
}
else if (_pCandidateListUIPresenter)
{
_pCandidateListUIPresenter->_ClearList();
}
else if (readingStrings.Count() && isWildcardIncluded)
{
hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext);
if (SUCCEEDED(hr))
{
_pCandidateListUIPresenter->_ClearList();
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _CreateAndStartCandidate
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_CreateAndStartCandidate(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext)
{
HRESULT hr = S_OK;
if (((_candidateMode == CANDIDATE_PHRASE) && (_pCandidateListUIPresenter))
|| ((_candidateMode == CANDIDATE_NONE) && (_pCandidateListUIPresenter)))
{
// Recreate candidate list
_pCandidateListUIPresenter->_EndCandidateList();
delete _pCandidateListUIPresenter;
_pCandidateListUIPresenter = nullptr;
_candidateMode = CANDIDATE_NONE;
_isCandidateWithWildcard = FALSE;
}
if (_pCandidateListUIPresenter == nullptr)
{
_pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow,
CATEGORY_CANDIDATE,
pCompositionProcessorEngine->GetCandidateListIndexRange(),
FALSE);
if (!_pCandidateListUIPresenter)
{
return E_OUTOFMEMORY;
}
_candidateMode = CANDIDATE_INCREMENTAL;
_isCandidateWithWildcard = FALSE;
// we don't cache the document manager object. So get it from pContext.
ITfDocumentMgr* pDocumentMgr = nullptr;
if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr)))
{
// get the composition range.
ITfRange* pRange = nullptr;
if (SUCCEEDED(_pComposition->GetRange(&pRange)))
{
hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth());
pRange->Release();
}
pDocumentMgr->Release();
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionFinalize
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionFinalize(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isCandidateList)
{
HRESULT hr = S_OK;
if (isCandidateList && _pCandidateListUIPresenter)
{
// Finalize selected candidate string from CCandidateListUIPresenter
DWORD_PTR candidateLen = 0;
const WCHAR *pCandidateString = nullptr;
candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString);
CStringRange candidateString;
candidateString.Set(pCandidateString, candidateLen);
if (candidateLen)
{
// Finalize character
hr = _AddCharAndFinalize(ec, pContext, &candidateString);
if (FAILED(hr))
{
return hr;
}
}
}
else
{
// Finalize current text store strings
if (_IsComposing())
{
ULONG fetched = 0;
TF_SELECTION tfSelection;
if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1)
{
return S_FALSE;
}
ITfRange* pRangeComposition = nullptr;
if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition)))
{
if (_IsRangeCovered(ec, tfSelection.range, pRangeComposition))
{
_EndComposition(pContext);
}
pRangeComposition->Release();
}
tfSelection.range->Release();
}
}
_HandleCancel(ec, pContext);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionConvert
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionConvert(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isWildcardSearch)
{
HRESULT hr = S_OK;
CSampleImeArray<CCandidateListItem> candidateList;
//
// Get candidate string from composition processor engine
//
CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
pCompositionProcessorEngine = _pCompositionProcessorEngine;
pCompositionProcessorEngine->GetCandidateList(&candidateList, FALSE, isWildcardSearch);
// If there is no candlidate listin the current reading string, we don't do anything. Just wait for
// next char to be ready for the conversion with it.
int nCount = candidateList.Count();
if (nCount)
{
if (_pCandidateListUIPresenter)
{
_pCandidateListUIPresenter->_EndCandidateList();
delete _pCandidateListUIPresenter;
_pCandidateListUIPresenter = nullptr;
_candidateMode = CANDIDATE_NONE;
_isCandidateWithWildcard = FALSE;
}
//
// create an instance of the candidate list class.
//
if (_pCandidateListUIPresenter == nullptr)
{
_pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow,
CATEGORY_CANDIDATE,
pCompositionProcessorEngine->GetCandidateListIndexRange(),
FALSE);
if (!_pCandidateListUIPresenter)
{
return E_OUTOFMEMORY;
}
_candidateMode = CANDIDATE_ORIGINAL;
}
_isCandidateWithWildcard = isWildcardSearch;
// we don't cache the document manager object. So get it from pContext.
ITfDocumentMgr* pDocumentMgr = nullptr;
if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr)))
{
// get the composition range.
ITfRange* pRange = nullptr;
if (SUCCEEDED(_pComposition->GetRange(&pRange)))
{
hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth());
pRange->Release();
}
pDocumentMgr->Release();
}
if (SUCCEEDED(hr))
{
_pCandidateListUIPresenter->_SetText(&candidateList, FALSE);
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionBackspace
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionBackspace(TfEditCookie ec, _In_ ITfContext *pContext)
{
ITfRange* pRangeComposition = nullptr;
TF_SELECTION tfSelection;
ULONG fetched = 0;
BOOL isCovered = TRUE;
// Start the new (std::nothrow) compositon if there is no composition.
if (!_IsComposing())
{
return S_OK;
}
// first, test where a keystroke would go in the document if we did an insert
if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1)
{
return S_FALSE;
}
// is the insertion point covered by a composition?
if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition)))
{
isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition);
pRangeComposition->Release();
if (!isCovered)
{
goto Exit;
}
}
//
// Add virtual key to composition processor engine
//
CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
pCompositionProcessorEngine = _pCompositionProcessorEngine;
DWORD_PTR vKeyLen = pCompositionProcessorEngine->GetVirtualKeyLength();
if (vKeyLen)
{
pCompositionProcessorEngine->RemoveVirtualKey(vKeyLen - 1);
if (pCompositionProcessorEngine->GetVirtualKeyLength())
{
_HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext);
}
else
{
_HandleCancel(ec, pContext);
}
}
Exit:
tfSelection.range->Release();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionArrowKey
//
// Update the selection within a composition.
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, KEYSTROKE_FUNCTION keyFunction)
{
ITfRange* pRangeComposition = nullptr;
TF_SELECTION tfSelection;
ULONG fetched = 0;
// get the selection
if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched))
|| fetched != 1)
{
// no selection, eat the keystroke
return S_OK;
}
// get the composition range
if (FAILED(_pComposition->GetRange(&pRangeComposition)))
{
goto Exit;
}
// For incremental candidate list
if (_pCandidateListUIPresenter)
{
_pCandidateListUIPresenter->AdviseUIChangedByArrowKey(keyFunction);
}
pContext->SetSelection(ec, 1, &tfSelection);
pRangeComposition->Release();
Exit:
tfSelection.range->Release();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionPunctuation
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionPunctuation(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch)
{
HRESULT hr = S_OK;
if (_candidateMode != CANDIDATE_NONE && _pCandidateListUIPresenter)
{
DWORD_PTR candidateLen = 0;
const WCHAR* pCandidateString = nullptr;
candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString);
CStringRange candidateString;
candidateString.Set(pCandidateString, candidateLen);
if (candidateLen)
{
_AddComposingAndChar(ec, pContext, &candidateString);
}
}
//
// Get punctuation char from composition processor engine
//
CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
pCompositionProcessorEngine = _pCompositionProcessorEngine;
WCHAR punctuation = pCompositionProcessorEngine->GetPunctuation(wch);
CStringRange punctuationString;
punctuationString.Set(&punctuation, 1);
// Finalize character
hr = _AddCharAndFinalize(ec, pContext, &punctuationString);
if (FAILED(hr))
{
return hr;
}
_HandleCancel(ec, pContext);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _HandleCompositionDoubleSingleByte
//
//----------------------------------------------------------------------------
HRESULT CSampleIME::_HandleCompositionDoubleSingleByte(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch)
{
HRESULT hr = S_OK;
WCHAR fullWidth = Global::FullWidthCharTable[wch - 0x20];
CStringRange fullWidthString;
fullWidthString.Set(&fullWidth, 1);
// Finalize character
hr = _AddCharAndFinalize(ec, pContext, &fullWidthString);
if (FAILED(hr))
{
return hr;
}
_HandleCancel(ec, pContext);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _InvokeKeyHandler
//
// This text service is interested in handling keystrokes to demonstrate the
// use the compositions. Some apps will cancel compositions if they receive
// keystrokes while a compositions is ongoing.
//
// param
// [in] uCode - virtual key code of WM_KEYDOWN wParam
// [in] dwFlags - WM_KEYDOWN lParam
// [in] dwKeyFunction - Function regarding virtual key
//----------------------------------------------------------------------------
HRESULT CSampleIME::_InvokeKeyHandler(_In_ ITfContext *pContext, UINT code, WCHAR wch, DWORD flags, _KEYSTROKE_STATE keyState)
{
flags;
CKeyHandlerEditSession* pEditSession = nullptr;
HRESULT hr = E_FAIL;
// we'll insert a char ourselves in place of this keystroke
pEditSession = new (std::nothrow) CKeyHandlerEditSession(this, pContext, code, wch, keyState);
if (pEditSession == nullptr)
{
goto Exit;
}
//
// Call CKeyHandlerEditSession::DoEditSession().
//
// Do not specify TF_ES_SYNC so edit session is not invoked on WinWord
//
hr = pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr);
pEditSession->Release();
Exit:
return hr;
}