You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3043 lines
99 KiB
3043 lines
99 KiB
//
|
|
// reconv.cpp
|
|
//
|
|
|
|
#include "private.h"
|
|
#include "globals.h"
|
|
#include "sapilayr.h"
|
|
#include "fnrecon.h"
|
|
#include "immxutil.h"
|
|
#include "candlist.h"
|
|
#include "propstor.h"
|
|
#include "catutil.h"
|
|
#include "osver.h"
|
|
#include "mui.h"
|
|
#include "tsattrs.h"
|
|
#include "cregkey.h"
|
|
#include <htmlhelp.h>
|
|
#include "TabletTip_i.c"
|
|
#include "spgrmr.h"
|
|
|
|
HRESULT HandlePhraseElement( CSpDynamicString *pDstr, const WCHAR *pwszTextThis, BYTE bAttrThis, BYTE bAttrPrev, ULONG *pulOffsetThis);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CSapiAlternativeList
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ctor/dtor
|
|
CSapiAlternativeList::CSapiAlternativeList(LANGID langid, ITfRange *pRange, ULONG ulMaxCandChars)
|
|
{
|
|
m_nItem = 0;
|
|
m_ulStart = 0;
|
|
m_ulcElem = 0;
|
|
m_cAlt = 0;
|
|
m_ppAlt = NULL;
|
|
m_langid = langid;
|
|
m_cpRange = pRange;
|
|
m_fFirstAltInCandidate = FALSE;
|
|
m_fNoAlternate = FALSE;
|
|
m_iFakeAlternate = NO_FAKEALT;
|
|
m_MaxCandChars = ulMaxCandChars;
|
|
m_ulIndexSelect = 0;
|
|
}
|
|
|
|
CSapiAlternativeList::~CSapiAlternativeList()
|
|
{
|
|
UINT i;
|
|
for (i = 0; i < m_cAlt; i++)
|
|
{
|
|
if (NULL != m_ppAlt[i])
|
|
{
|
|
m_ppAlt[i]->Release();
|
|
}
|
|
}
|
|
if (m_prgLMAlternates)
|
|
{
|
|
int nItem = m_prgLMAlternates->Count();
|
|
for (i = 0; i < (UINT)nItem; i++)
|
|
{
|
|
CLMAlternates *plmalt = m_prgLMAlternates->Get(i);
|
|
|
|
if (plmalt)
|
|
delete plmalt;
|
|
}
|
|
delete m_prgLMAlternates;
|
|
}
|
|
if (m_ppAlt)
|
|
cicMemFree(m_ppAlt);
|
|
|
|
if (m_rgElemUsed.Count())
|
|
{
|
|
for ( i=0; i<(UINT)m_rgElemUsed.Count(); i++)
|
|
{
|
|
SPELEMENTUSED *pElemUsed;
|
|
|
|
pElemUsed = m_rgElemUsed.GetPtr(i);
|
|
|
|
if ( pElemUsed && pElemUsed->pwszAltText)
|
|
{
|
|
cicMemFree(pElemUsed->pwszAltText);
|
|
pElemUsed->pwszAltText = NULL;
|
|
}
|
|
}
|
|
m_rgElemUsed.Clear();
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// AddLMAlternates
|
|
//
|
|
HRESULT CSapiAlternativeList::AddLMAlternates(CLMAlternates *pLMAlt)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
BOOL fFoundADup = FALSE;
|
|
if (!pLMAlt)
|
|
return E_INVALIDARG;
|
|
|
|
if (!m_prgLMAlternates)
|
|
{
|
|
m_prgLMAlternates = new CPtrArray<CLMAlternates>;
|
|
}
|
|
|
|
if (m_prgLMAlternates)
|
|
{
|
|
int iIdx = m_prgLMAlternates->Count();
|
|
|
|
// need to find a dup
|
|
for (int i = 0; i < iIdx && !fFoundADup ; i++)
|
|
{
|
|
CLMAlternates *plma = m_prgLMAlternates->Get(i);
|
|
if (plma)
|
|
{
|
|
WCHAR *pszStored = new WCHAR[plma->GetLen()+1];
|
|
WCHAR *pszAdding = new WCHAR[pLMAlt->GetLen()+1];
|
|
|
|
if (pszStored && pszAdding)
|
|
{
|
|
|
|
plma->GetString(pszStored, plma->GetLen()+1);
|
|
pLMAlt->GetString(pszAdding, pLMAlt->GetLen()+1);
|
|
|
|
if (!wcscmp(pszAdding, pszStored))
|
|
{
|
|
fFoundADup = TRUE;
|
|
}
|
|
}
|
|
|
|
if (pszStored)
|
|
delete[] pszStored;
|
|
|
|
if (pszAdding)
|
|
delete[] pszAdding;
|
|
}
|
|
}
|
|
|
|
if (!fFoundADup && pLMAlt)
|
|
{
|
|
if (!m_prgLMAlternates->Insert(iIdx, 1))
|
|
return E_OUTOFMEMORY;
|
|
|
|
m_prgLMAlternates->Set(iIdx, pLMAlt);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// SetPhraseAlt
|
|
//
|
|
// synopsis: receive an alternates list as a param and copy the alternates
|
|
// to the array which this class internally maintains.
|
|
// Additionally, a pointer to the reco result wrapper
|
|
// is maintained per CSapiAlternativeList class instance.
|
|
//
|
|
// params pResWrap - a pointer to the wrapper object
|
|
// ppAlt - a pointer to the array of phrases that the callar has alloced
|
|
// cAlt - is passed in with the # of real SAPI alternates
|
|
// ulStart - index to the start element in the parent phrase
|
|
// culElem - # of minimum elements used (will get replaced) in the parent phrase.
|
|
//
|
|
HRESULT CSapiAlternativeList::SetPhraseAlt(CRecoResultWrap *pResWrap, ISpPhraseAlt **ppAlt, ULONG cAlt, ULONG ulStart, ULONG ulcElem, WCHAR *pwszParent)
|
|
{
|
|
// setup the info for used elements in parent phrase
|
|
// these are useful for the 0 index (ITN) alternate
|
|
|
|
HRESULT hr = S_OK;
|
|
SPPHRASE *pParentPhrase = NULL;
|
|
CSpDynamicString dstr;
|
|
|
|
if ( !pResWrap || !ppAlt || !pwszParent )
|
|
return E_INVALIDARG;
|
|
|
|
m_ulStart = ulStart;
|
|
m_ulcElem = ulcElem;
|
|
|
|
|
|
|
|
m_fFirstAltInCandidate = FALSE;
|
|
m_fNoAlternate = FALSE;
|
|
m_iFakeAlternate = NO_FAKEALT;
|
|
|
|
// alloc the struct for the used element info
|
|
m_rgElemUsed.Append(cAlt);
|
|
|
|
for ( int i=0; i<m_rgElemUsed.Count( ); i++)
|
|
{
|
|
SPELEMENTUSED *pElemUsed;
|
|
if ( pElemUsed = m_rgElemUsed.GetPtr(i))
|
|
{
|
|
pElemUsed->pwszAltText = NULL;
|
|
}
|
|
}
|
|
|
|
// comptr releases on the previous object
|
|
// at this indirection
|
|
//
|
|
Assert(pResWrap);
|
|
|
|
m_cpwrp = pResWrap;
|
|
|
|
if (m_ppAlt)
|
|
{
|
|
for (UINT i = 0; i < m_cAlt; i++)
|
|
{
|
|
if (NULL != m_ppAlt[i])
|
|
{
|
|
m_ppAlt[i]->Release();
|
|
}
|
|
}
|
|
cicMemFree(m_ppAlt);
|
|
m_ppAlt = NULL;
|
|
}
|
|
|
|
m_ppAlt = (ISpPhraseAlt **)cicMemAlloc(sizeof(*ppAlt)*cAlt);
|
|
if (!m_ppAlt)
|
|
return E_OUTOFMEMORY;
|
|
|
|
Assert(ppAlt);
|
|
|
|
#ifdef DONTUSE
|
|
|
|
// Get the current select text in parent phrase.
|
|
CComPtr<IServiceProvider> cpServicePrv;
|
|
CComPtr<ISpRecoResult> cpResult;
|
|
|
|
hr = m_cpwrp->QueryInterface(IID_IServiceProvider, (void **)&cpServicePrv);
|
|
|
|
if ( S_OK == hr )
|
|
hr = cpServicePrv->QueryService(GUID_NULL, IID_ISpRecoResult, (void **)&cpResult);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
CSpDynamicString dstrReplace;
|
|
|
|
cpResult->GetPhrase(&pParentPhrase);
|
|
|
|
for (ULONG i = m_ulStart; i < m_ulStart + m_ulcElem; i++ )
|
|
{
|
|
BOOL fInsideITN;
|
|
ULONG ulITNStart, ulITNNumElem;
|
|
|
|
fInsideITN = m_cpwrp->_CheckITNForElement(pParentPhrase, i, &ulITNStart, &ulITNNumElem, (CSpDynamicString *)&dstrReplace);
|
|
|
|
if ( fInsideITN )
|
|
{
|
|
// This element is inside an ITN range.
|
|
if ( i == (ulITNStart + ulITNNumElem - 1) )
|
|
{
|
|
// This is the last element of the new ITN.
|
|
// we need to add the replace text to the dstr string
|
|
// so that next non-ITN element will get correct offset.
|
|
|
|
dstr.Append( (WCHAR *)dstrReplace );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pParentPhrase->pElements[i].pszDisplayText)
|
|
{
|
|
const WCHAR *pwszTextThis;
|
|
BYTE bAttrThis = 0;
|
|
BYTE bAttrPrev = 0;
|
|
|
|
pwszTextThis = pParentPhrase->pElements[i].pszDisplayText;
|
|
bAttrThis = pParentPhrase->pElements[i].bDisplayAttributes;
|
|
|
|
if ( i > m_ulStart )
|
|
bAttrPrev = pParentPhrase->pElements[i-1].bDisplayAttributes;
|
|
|
|
HandlePhraseElement( (CSpDynamicString *)&dstr, pwszTextThis, bAttrThis, bAttrPrev,NULL);
|
|
}
|
|
}
|
|
} // for
|
|
|
|
pwszParent = (WCHAR *)dstr;
|
|
|
|
if (pParentPhrase)
|
|
CoTaskMemFree(pParentPhrase);
|
|
}
|
|
#endif
|
|
|
|
UINT j=0;
|
|
|
|
if ( pwszParent )
|
|
{
|
|
ULONG ulRecoWrpStart, ulRecoWrpNumElements;
|
|
WCHAR *pwszFakeAlt = NULL; // This is for Capitalized string for parent phrase.
|
|
|
|
if ( iswalpha(pwszParent[0]) )
|
|
{
|
|
int iStrLen = wcslen(pwszParent);
|
|
|
|
pwszFakeAlt = (WCHAR *)cicMemAlloc((iStrLen+1) * sizeof(WCHAR));
|
|
|
|
if ( pwszFakeAlt )
|
|
{
|
|
WCHAR wch;
|
|
|
|
wch = pwszParent[0];
|
|
StringCchCopyW(pwszFakeAlt, iStrLen+1, pwszParent);
|
|
|
|
if ( iswlower(wch) )
|
|
pwszFakeAlt[0] = towupper(wch);
|
|
else
|
|
pwszFakeAlt[0] = towlower(wch);
|
|
|
|
int iLen = wcslen(pwszFakeAlt);
|
|
|
|
if ( (iLen > 0) && (pwszFakeAlt[iLen-1] < 0x20) )
|
|
pwszFakeAlt[iLen-1] = L'\0';
|
|
}
|
|
}
|
|
|
|
ulRecoWrpStart = pResWrap->GetStart( );
|
|
ulRecoWrpNumElements = pResWrap->GetNumElements( );
|
|
|
|
ULONG ValidParentStart, ValidParentEnd; // Point to the valid parent element range which could be matched by
|
|
// the alternative phrase.
|
|
|
|
int ShiftDelta = 2; // We just want to shift the valid parent element range by ShiftDelta elements from current
|
|
// start and end element in parent phrase.
|
|
|
|
// ie, ulStart - 3, ulEnd + 3, if they are in the valid range of the reco wrapper.
|
|
|
|
ValidParentStart = ulRecoWrpStart;
|
|
if ( ((int)ulStart - ShiftDelta) > (int)ulRecoWrpStart )
|
|
ValidParentStart = ulStart - ShiftDelta;
|
|
|
|
ValidParentEnd = ulRecoWrpStart + ulRecoWrpNumElements - 1;
|
|
if ( ((int)ulStart + (int)ulcElem -1 + ShiftDelta) < (int)ValidParentEnd )
|
|
ValidParentEnd = ulStart + ulcElem - 1 + ShiftDelta;
|
|
|
|
CComPtr<ISpRecoResult> cpResult;
|
|
pResWrap->GetResult(&cpResult);
|
|
cpResult->GetPhrase(&pParentPhrase);
|
|
|
|
for (UINT i = 0; (i < cAlt) && (j < cAlt) && *ppAlt; i++, ppAlt++)
|
|
{
|
|
SPPHRASE *pPhrases = NULL;
|
|
ULONG ulcElements = 0;
|
|
ULONG ulParentStart = 0;
|
|
ULONG ulcParentElements = 0;
|
|
ULONG ulLeadSpaceRemoved = 0;
|
|
|
|
// Assume the first alt phrase is exactly same as the parent phrase.
|
|
// practically it is true so far.
|
|
// if it is not true in the future, we may need to change logical here!!!
|
|
|
|
(*ppAlt)->GetPhrase(&pPhrases);
|
|
(*ppAlt)->GetAltInfo(NULL, &ulParentStart, &ulcParentElements, &ulcElements);
|
|
|
|
if ( (ulParentStart >= ValidParentStart) && ( ulParentStart+ulcParentElements -1 <= ValidParentEnd) )
|
|
{
|
|
WCHAR *pwszAlt = (WCHAR *)cicMemAllocClear((m_MaxCandChars+1)*sizeof(WCHAR));
|
|
|
|
if ( !pwszAlt )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
|
|
BOOL fAddToCandidateList = FALSE;
|
|
BOOL fControlCharsInAltPhrase = FALSE;
|
|
|
|
// Add code to skip start and end elements which are the same as parent phrase's elements.
|
|
UINT ulSkipStartWords = 0;
|
|
UINT ulSkipEndWords = 0;
|
|
|
|
for (UINT k = ulParentStart; k < ulStart; k++)
|
|
{
|
|
if (_wcsicmp(pPhrases->pElements[k].pszDisplayText, pParentPhrase->pElements[k].pszDisplayText) == 0)
|
|
{
|
|
// Matching pre-word in alternate. This is redundant.
|
|
ulSkipStartWords ++;
|
|
}
|
|
else
|
|
{
|
|
// Do not match. Stop processing.
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (UINT k = ulParentStart + ulcParentElements - 1; k >= ulStart + ulcElem ; k--)
|
|
{
|
|
// Count backwards in alternate phrase.
|
|
UINT l = ulParentStart + ulcElements - ((ulParentStart + ulcParentElements) - k);
|
|
if (_wcsicmp(pPhrases->pElements[l].pszDisplayText, pParentPhrase->pElements[k].pszDisplayText) == 0)
|
|
{
|
|
ulSkipEndWords ++;
|
|
}
|
|
else
|
|
{
|
|
// Do not match. Stop processing.
|
|
break;
|
|
}
|
|
}
|
|
|
|
ulParentStart += ulSkipStartWords;
|
|
ulcElements -= ulSkipStartWords + ulSkipEndWords;
|
|
ulcParentElements -= ulSkipStartWords + ulSkipEndWords;
|
|
|
|
hr = GetAlternativeText(*ppAlt, pPhrases, (i ==0 ? TRUE : FALSE), ulParentStart, ulcElements, pwszAlt, m_MaxCandChars, &ulLeadSpaceRemoved);
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
for ( ULONG iIndex =0; iIndex < wcslen(pwszAlt); iIndex++ )
|
|
{
|
|
if ( pwszAlt[iIndex] < 0x20 )
|
|
{
|
|
fControlCharsInAltPhrase = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL fNotDupAlt = TRUE;
|
|
|
|
if ( S_OK == hr && pwszAlt )
|
|
{
|
|
fNotDupAlt = _wcsicmp(pwszAlt, pwszParent);
|
|
if ( fNotDupAlt && (j>0) )
|
|
{
|
|
SPELEMENTUSED *pElemUsed;
|
|
|
|
for (UINT x=0; x<j; x++ )
|
|
{
|
|
if ( pElemUsed = m_rgElemUsed.GetPtr(x))
|
|
fNotDupAlt = _wcsicmp(pwszAlt, pElemUsed->pwszAltText);
|
|
|
|
if ( !fNotDupAlt )
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((S_OK == hr) && !fControlCharsInAltPhrase && (pwszAlt[0] != L'\0') && fNotDupAlt)
|
|
{
|
|
// This is different item from the parent, it should be inserted to the canidate list.
|
|
// initialize the AltCached item
|
|
|
|
if ( (i > 0) || (pResWrap->_RangeHasITN(ulParentStart, ulcParentElements) > 0 ) )
|
|
{
|
|
|
|
SPELEMENTUSED *pElemUsed;
|
|
if ( pElemUsed = m_rgElemUsed.GetPtr(j))
|
|
{
|
|
pElemUsed->ulParentStart = ulParentStart;
|
|
pElemUsed->ulcParentElements = ulcParentElements;
|
|
pElemUsed->ulcElements = ulcElements;
|
|
pElemUsed->pwszAltText = pwszAlt;
|
|
pElemUsed->ulLeadSpaceRemoved = ulLeadSpaceRemoved;
|
|
|
|
m_ppAlt[j] = *ppAlt;
|
|
m_ppAlt[j]->AddRef();
|
|
j ++;
|
|
|
|
if ( i == 0 )
|
|
{
|
|
// The first Alt phrase is also in the canidate list. the selectedd range must contain ITN.
|
|
m_fFirstAltInCandidate = TRUE;
|
|
}
|
|
|
|
fAddToCandidateList = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( fAddToCandidateList == FALSE )
|
|
{
|
|
// Same string. or GetAlternativeText returns Error.
|
|
// don't insert it into the candidate list.
|
|
|
|
// Release the alloced memory
|
|
cicMemFree(pwszAlt);
|
|
}
|
|
|
|
// Handle Faked Alternate
|
|
if ((i == 0) && (pwszFakeAlt != NULL))
|
|
{
|
|
// This is the parent phrase, Only the first character is capitalized.
|
|
SPELEMENTUSED *pElemUsed;
|
|
if ( pElemUsed = m_rgElemUsed.GetPtr(j))
|
|
{
|
|
pElemUsed->ulParentStart = ulParentStart;
|
|
pElemUsed->ulcParentElements = ulcParentElements;
|
|
pElemUsed->ulcElements = ulcElements;
|
|
pElemUsed->pwszAltText = pwszFakeAlt;
|
|
pElemUsed->ulLeadSpaceRemoved = 0;
|
|
|
|
m_iFakeAlternate = j;
|
|
|
|
m_ppAlt[j] = *ppAlt;
|
|
m_ppAlt[j]->AddRef();
|
|
j ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pPhrases)
|
|
CoTaskMemFree( pPhrases );
|
|
}
|
|
|
|
if (pParentPhrase)
|
|
{
|
|
CoTaskMemFree(pParentPhrase);
|
|
}
|
|
|
|
if ( pwszFakeAlt && (m_iFakeAlternate == NO_FAKEALT) )
|
|
cicMemFree(pwszFakeAlt);
|
|
}
|
|
|
|
m_cAlt = j;
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
if ( m_cAlt == 0 )
|
|
{
|
|
// There is no available Alternate.
|
|
// Just show string "No Alternate" in the candidate window.
|
|
m_fNoAlternate = TRUE;
|
|
|
|
SPELEMENTUSED *pElemUsed;
|
|
WCHAR *pwszNoAlt=(WCHAR *)cicMemAllocClear(m_MaxCandChars*sizeof(WCHAR));
|
|
|
|
if ( (pElemUsed = m_rgElemUsed.GetPtr(0)) && pwszNoAlt )
|
|
{
|
|
pElemUsed->ulParentStart = m_ulStart;
|
|
pElemUsed->ulcParentElements = m_ulcElem;
|
|
pElemUsed->ulcElements = m_ulcElem;
|
|
pElemUsed->ulLeadSpaceRemoved = 0;
|
|
|
|
CicLoadStringWrapW(g_hInst, IDS_NO_ALTERNATE, pwszNoAlt, m_MaxCandChars);
|
|
|
|
pElemUsed->pwszAltText = pwszNoAlt;
|
|
|
|
CComPtr<IServiceProvider> cpServicePrv;
|
|
CComPtr<ISpRecoResult> cpResult;
|
|
|
|
hr = m_cpwrp->QueryInterface(IID_IServiceProvider, (void **)&cpServicePrv);
|
|
|
|
if ( S_OK == hr )
|
|
hr = cpServicePrv->QueryService(GUID_NULL, IID_ISpRecoResult, (void **)&cpResult);
|
|
|
|
if ( (hr == S_OK) && cpResult )
|
|
{
|
|
m_ppAlt[0] = (ISpPhraseAlt *)(ISpRecoResult *)cpResult;
|
|
m_ppAlt[0]->AddRef();
|
|
}
|
|
|
|
m_cAlt = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Release all the allocated memeory and AltCached Items in this function.
|
|
UINT i;
|
|
if (m_ppAlt)
|
|
{
|
|
for (i = 0; i < m_cAlt; i++)
|
|
{
|
|
m_ppAlt[i]->Release();
|
|
m_ppAlt[i] = NULL;
|
|
}
|
|
cicMemFree(m_ppAlt);
|
|
m_ppAlt = NULL;
|
|
}
|
|
|
|
if (m_rgElemUsed.Count())
|
|
{
|
|
for ( i=0; i<(UINT)m_rgElemUsed.Count(); i++)
|
|
{
|
|
SPELEMENTUSED *pElemUsed;
|
|
|
|
pElemUsed = m_rgElemUsed.GetPtr(i);
|
|
|
|
if ( pElemUsed && pElemUsed->pwszAltText)
|
|
{
|
|
cicMemFree(pElemUsed->pwszAltText);
|
|
pElemUsed->pwszAltText = NULL;
|
|
}
|
|
}
|
|
|
|
m_rgElemUsed.Clear();
|
|
}
|
|
|
|
m_cAlt = 0;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// GetNumItem
|
|
//
|
|
//
|
|
int CSapiAlternativeList::GetNumItem(void)
|
|
{
|
|
if (!m_nItem)
|
|
m_nItem = m_cAlt;
|
|
|
|
if ( m_prgLMAlternates )
|
|
{
|
|
return m_nItem + m_prgLMAlternates->Count();
|
|
}
|
|
else
|
|
return m_nItem;
|
|
}
|
|
|
|
|
|
HRESULT CSapiAlternativeList::_ProcessTrailingSpaces(SPPHRASE *pPhrases, ULONG ulNextElem, WCHAR *pwszAlt)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ULONG ulSize;
|
|
BOOL fRemoveTrail;
|
|
|
|
if ( !pwszAlt || !pPhrases)
|
|
return E_INVALIDARG;
|
|
|
|
if ( ulNextElem >= pPhrases->Rule.ulCountOfElements)
|
|
{
|
|
// NextElement is not a valid element
|
|
return hr;
|
|
}
|
|
|
|
if ( pPhrases->pElements[ulNextElem].bDisplayAttributes & SPAF_CONSUME_LEADING_SPACES )
|
|
fRemoveTrail = TRUE;
|
|
else
|
|
fRemoveTrail = FALSE;
|
|
|
|
if ( !fRemoveTrail )
|
|
return hr;
|
|
|
|
ulSize = wcslen(pwszAlt);
|
|
|
|
for ( ULONG i=ulSize; i>0; i-- )
|
|
{
|
|
if ( (pwszAlt[i-1] != L' ') && (pwszAlt[i-1] != L'\t') )
|
|
break;
|
|
|
|
pwszAlt[i-1] = L'\0';
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSapiAlternativeList::GetAlternativeText(ISpPhraseAlt *pAlt,SPPHRASE *pPhrases, BOOL fFirstAlt, ULONG ulStartElem, ULONG ulNumElems, WCHAR *pwszAlt, int cchAlt, ULONG *pulLeadSpaceRemoved)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CSpDynamicString sds;
|
|
ULONG ulLeadSpaceRemoved = 0;
|
|
|
|
if ( !pPhrases || !pwszAlt || !cchAlt || !pulLeadSpaceRemoved)
|
|
return E_INVALIDARG;
|
|
|
|
if ( !pAlt )
|
|
return E_INVALIDARG;
|
|
|
|
// We assume the first phrase in the AltPhrase list is exactly same as the parent phrase.
|
|
// Specially handle it when it contains ITN.
|
|
|
|
if ((m_cpwrp->m_ulNumOfITN > 0) && fFirstAlt)
|
|
{
|
|
// the ITN is always index 0
|
|
//
|
|
CSpDynamicString dstr;
|
|
//
|
|
// this seems confusing but fITNShown indicates whether the ITN
|
|
// is displayed on the doc.
|
|
|
|
// We no longer use fITNShown to inidicates the ITN show state.
|
|
// Because there are may be more ITNs in one phrase, and every ITN may have
|
|
// different show state on the doc.
|
|
// Now we use an ITNSHOWSTATE list to keep the display state for individual ITN.
|
|
|
|
// So we have to include non-ITN in the alternates if the ITN is on the doc,
|
|
// and include the ITN in the alternates if the non-ITN is on the doc.
|
|
//
|
|
|
|
ULONG ulRepCount = 0;
|
|
|
|
if (pPhrases->Rule.ulCountOfElements > 0)
|
|
{
|
|
for (UINT i = 0; i < pPhrases->Rule.ulCountOfElements; i++ )
|
|
{
|
|
if (i >= ulStartElem && i < ulStartElem + ulNumElems)
|
|
{
|
|
ULONG ulITNStart, ulITNNumElem;
|
|
BOOL fITNShown = FALSE;
|
|
BOOL fInsideITN = FALSE;
|
|
|
|
// Check to see if this element is inside an ITN,
|
|
// and if this ITN is shown up in the current Doc.
|
|
|
|
for ( ulRepCount=0; ulRepCount<pPhrases->cReplacements; ulRepCount++)
|
|
{
|
|
|
|
ulITNStart = pPhrases->pReplacements[ulRepCount].ulFirstElement;
|
|
ulITNNumElem = pPhrases->pReplacements[ulRepCount].ulCountOfElements;
|
|
|
|
if ( (i == ulITNStart) && ((i + ulITNNumElem) <= pPhrases->Rule.ulCountOfElements))
|
|
{
|
|
// This element is in an ITN.
|
|
|
|
fInsideITN = TRUE;
|
|
|
|
// check if this ITN is shown as ITN in current DOC
|
|
|
|
for ( ULONG iIndex=0; iIndex < m_cpwrp->m_ulNumOfITN; iIndex ++ )
|
|
{
|
|
SPITNSHOWSTATE *pITNShowState;
|
|
|
|
pITNShowState = m_cpwrp->m_rgITNShowState.GetPtr(iIndex);
|
|
if ( pITNShowState )
|
|
{
|
|
if ( (pITNShowState->ulITNStart == ulITNStart)
|
|
&& (pITNShowState->ulITNNumElem == ulITNNumElem) )
|
|
{
|
|
fITNShown = pITNShowState->fITNShown;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// use ITN version for an alternate when it is not shown in the parent
|
|
BOOL fUseITN = fInsideITN && !fITNShown;
|
|
|
|
if ( fUseITN && (ulRepCount < pPhrases->cReplacements) )
|
|
{
|
|
sds.Append(pPhrases->pReplacements[ulRepCount].pszReplacementText);
|
|
i += pPhrases->pReplacements[ulRepCount].ulCountOfElements - 1;
|
|
|
|
if (pPhrases->pReplacements[ulRepCount].bDisplayAttributes & SPAF_ONE_TRAILING_SPACE)
|
|
{
|
|
sds.Append(L" ");
|
|
}
|
|
else if (pPhrases->pReplacements[ulRepCount].bDisplayAttributes & SPAF_TWO_TRAILING_SPACES)
|
|
{
|
|
sds.Append(L" ");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
const WCHAR *pwszTextThis;
|
|
BYTE bAttrThis = 0;
|
|
BYTE bAttrPrev = 0;
|
|
|
|
pwszTextThis = pPhrases->pElements[i].pszDisplayText;
|
|
bAttrThis = pPhrases->pElements[i].bDisplayAttributes;
|
|
|
|
if ( i > m_ulStart )
|
|
bAttrPrev = pPhrases->pElements[i-1].bDisplayAttributes;
|
|
|
|
HandlePhraseElement( (CSpDynamicString *)&sds, pwszTextThis, bAttrThis, bAttrPrev,NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is not the first Altphrase.
|
|
// Or even if it is the first Altphrase, but there is no ITN in this phrase
|
|
ULONG ulcElements = 0;
|
|
ULONG ulParentStart = 0;
|
|
ULONG ulcParentElements = 0;
|
|
|
|
if (pPhrases->Rule.ulCountOfElements > 0)
|
|
{
|
|
//
|
|
// If the start element is not the first element in parent phrase,
|
|
// and it has SPAF_CONSUME_LEADING_SPACES attr in parent phrase,
|
|
// but this start element doesn't have SPAF_CONSUME_LEADING_SPACES in
|
|
// the alternate phrase,
|
|
// in this case, we need to add leading space in this alternate text.
|
|
// the number of spaces can be found from previous element's attribute,
|
|
// or the replacement's attribute if the previous phrase is displayed with
|
|
// replacement text.
|
|
//
|
|
// Example here, if you dictate: "this is a test, this is good example."
|
|
//
|
|
// element test has one trail space.
|
|
// element "," has SPAF_CONSUME_LEADING_SPACES.
|
|
//
|
|
// When you select "," to get alternate, the alternate could be "of,", "to,"
|
|
// in the alternate phrase, the start element doesn't have SPAF_CONSUME_LEADING_SPACES
|
|
// attr. in this case, the alternate text needs to add one space, so the text would be " of,"
|
|
// " to,". otherwise, when user selects this alternate, the new text would be like
|
|
// "this is a testof,....". ( there is no space between test and of ).
|
|
//
|
|
|
|
if ( ulStartElem > m_cpwrp->GetStart( ) )
|
|
{
|
|
BYTE bAttrParent, bAttrPrevParent;
|
|
BYTE bAttrAlternate;
|
|
ULONG ulPrevSpace = 0;
|
|
|
|
bAttrParent = m_cpwrp->_GetElementDispAttribute(ulStartElem);
|
|
bAttrPrevParent = m_cpwrp->_GetElementDispAttribute(ulStartElem - 1);
|
|
|
|
bAttrAlternate = pPhrases->pElements[ulStartElem].bDisplayAttributes;
|
|
|
|
if ( bAttrPrevParent & SPAF_ONE_TRAILING_SPACE )
|
|
ulPrevSpace = 1;
|
|
else if ( bAttrPrevParent & SPAF_TWO_TRAILING_SPACES )
|
|
ulPrevSpace = 2;
|
|
|
|
if ( (bAttrParent & SPAF_CONSUME_LEADING_SPACES) &&
|
|
!(bAttrAlternate & SPAF_CONSUME_LEADING_SPACES) &&
|
|
ulPrevSpace > 0)
|
|
{
|
|
// Add the required spaces for the previous element
|
|
// which was removed before when parent phrase showed up.
|
|
sds.Append( (ulPrevSpace == 1 ? L" " : L" ") );
|
|
}
|
|
|
|
if ( !(bAttrParent & SPAF_CONSUME_LEADING_SPACES) &&
|
|
(bAttrAlternate & SPAF_CONSUME_LEADING_SPACES) &&
|
|
ulPrevSpace > 0 )
|
|
{
|
|
// the previous element's trailing space needs to be
|
|
// removed if it is selected.
|
|
ulLeadSpaceRemoved = ulPrevSpace;
|
|
}
|
|
}
|
|
/*
|
|
// This code block tries to get Non-ITN form text for the alternate.
|
|
// Yakima engine changed design to require ITN form text must be shown up in
|
|
// candidate window.
|
|
//
|
|
// So this part of code is replaced by the below code block.
|
|
//
|
|
for (UINT i = 0; i < pPhrases->Rule.ulCountOfElements; i++ )
|
|
{
|
|
if (i >= ulStartElem && i < ulStartElem + ulNumElems)
|
|
{
|
|
const WCHAR *pwszTextThis;
|
|
BYTE bAttrThis = 0;
|
|
BYTE bAttrPrev = 0;
|
|
|
|
pwszTextThis = pPhrases->pElements[i].pszDisplayText;
|
|
bAttrThis = pPhrases->pElements[i].bDisplayAttributes;
|
|
|
|
if ( i > ulParentStart )
|
|
bAttrPrev = pPhrases->pElements[i-1].bDisplayAttributes;
|
|
|
|
HandlePhraseElement( (CSpDynamicString *)&sds, pwszTextThis, bAttrThis, bAttrPrev,NULL);
|
|
}
|
|
}
|
|
*/
|
|
BYTE bAttr = 0;
|
|
CSpDynamicString sdsAltText;
|
|
|
|
if ( pAlt->GetText(ulStartElem, ulNumElems, TRUE, &sdsAltText, &bAttr) == S_OK )
|
|
{
|
|
if (bAttr & SPAF_ONE_TRAILING_SPACE)
|
|
{
|
|
sdsAltText.Append(L" ");
|
|
}
|
|
else if (bAttr & SPAF_TWO_TRAILING_SPACES)
|
|
{
|
|
sdsAltText.Append(L" ");
|
|
}
|
|
}
|
|
|
|
if ( sdsAltText )
|
|
sds.Append(sdsAltText);
|
|
}
|
|
}
|
|
|
|
if (sds)
|
|
{
|
|
_ProcessTrailingSpaces(pPhrases, ulStartElem + ulNumElems, (WCHAR *)sds);
|
|
|
|
int TextLen;
|
|
|
|
TextLen = wcslen( (WCHAR *)sds);
|
|
if (TextLen > cchAlt )
|
|
{
|
|
// There is not enough buffer to hold this alternate text.
|
|
// Set the first element as NULL to indicate this situation.
|
|
pwszAlt[0] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
// The passed buffer can hold all the alternate text.
|
|
wcsncpy(pwszAlt, sds, TextLen);
|
|
pwszAlt[TextLen] = L'\0';
|
|
}
|
|
}
|
|
|
|
if ( pulLeadSpaceRemoved )
|
|
*pulLeadSpaceRemoved = ulLeadSpaceRemoved;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSapiAlternativeList::GetProbability(int nId, int * pnPrb)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
//
|
|
// bogus for now
|
|
//
|
|
if(pnPrb && nId >= 0)
|
|
{
|
|
if ( nId < m_nItem)
|
|
{
|
|
*pnPrb = 10 - nId;
|
|
}
|
|
else if ( m_prgLMAlternates
|
|
&& (nId - m_nItem) < m_prgLMAlternates->Count())
|
|
{
|
|
// we'll be able to get something from LM
|
|
// but not normalized for now anyway
|
|
*pnPrb = 1;
|
|
}
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSapiAlternativeList::GetCachedAltInfo
|
|
(
|
|
ULONG nId,
|
|
ULONG *pulParentStart,
|
|
ULONG *pulcParentElements,
|
|
ULONG *pulcElements,
|
|
WCHAR **ppwszText,
|
|
ULONG *pulLeadSpaceRemoved
|
|
)
|
|
{
|
|
if (nId < m_cAlt)
|
|
{
|
|
SPELEMENTUSED *pElemUsed;
|
|
if ( pElemUsed = m_rgElemUsed.GetPtr(nId))
|
|
{
|
|
if (pulParentStart)
|
|
*pulParentStart = pElemUsed->ulParentStart;
|
|
if (pulcParentElements)
|
|
*pulcParentElements = pElemUsed->ulcParentElements;
|
|
if (pulcElements)
|
|
*pulcElements = pElemUsed->ulcElements;
|
|
if ( ppwszText)
|
|
*ppwszText = pElemUsed->pwszAltText;
|
|
if (pulLeadSpaceRemoved )
|
|
*pulLeadSpaceRemoved = pElemUsed->ulLeadSpaceRemoved;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CSapiAlternativeList::_Commit(ULONG nIdx, ISpRecoResult *pRecoResult)
|
|
{
|
|
|
|
if ((m_iFakeAlternate != NO_FAKEALT) && (m_iFakeAlternate == (int)nIdx))
|
|
{
|
|
// This is for Faked Alternate.
|
|
// Don't change any thing.
|
|
// just return here.
|
|
|
|
return;
|
|
}
|
|
|
|
if (m_cpwrp->m_ulNumOfITN > 0)
|
|
{
|
|
// if we now have the ITN shown as the recognized text, it's swapped with non-ITN
|
|
// if we have non-ITN shown as the recognized text, it's swapped with the ITN
|
|
|
|
// We should change the show state only for the replaced range,
|
|
// ( not for all the phrase if uses doesn't select the whole phrase.
|
|
|
|
if ((nIdx == 0) && _IsFirstAltInCandidate() )
|
|
{
|
|
// the ITN alternate has been chosen.
|
|
//
|
|
|
|
// we need to invert the show state for all the ITN inside the selection range.
|
|
|
|
m_cpwrp->_InvertITNShowStateForRange(m_ulStart, m_ulcElem);
|
|
|
|
// we don't have to commit this to SR engine but
|
|
// we need to recalculate the character offsets for
|
|
// SR elements using the current pharse (set NULL)
|
|
//
|
|
m_cpwrp->_SetElementOffsetCch(NULL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if a non-SR candidate (such as LM's) is chosen,
|
|
// nIdx would be >= m_cAlt and we don't have to
|
|
// tell SR about that.
|
|
if(nIdx < m_cAlt)
|
|
{
|
|
|
|
// Need to update the real ITN show state list
|
|
// and then save the real text and get the offset for
|
|
// all the elements.
|
|
|
|
HRESULT hr = m_cpwrp->_UpdateStateWithAltPhrase(m_ppAlt[nIdx]);
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
|
|
// Offset value should be based on ITN display status.
|
|
// revise character offsets using the alternate phrase
|
|
m_cpwrp->_SetElementOffsetCch(m_ppAlt[nIdx]);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = m_ppAlt[nIdx]->Commit();
|
|
}
|
|
|
|
// we need to invalidate the result object too
|
|
if (S_OK == hr)
|
|
{
|
|
hr = m_cpwrp->Init(pRecoResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _GetUIFont()
|
|
//
|
|
// synopsis: get appropriate logfont based on
|
|
// the current langid assigned to the alternativelist
|
|
//
|
|
// return : TRUE if there's a specific logfont to the langid
|
|
// FALSE if no logfont data is available for the langid
|
|
//
|
|
//+---------------------------------------------------------------------------
|
|
BOOL CSapiAlternativeList::_GetUIFont(BOOL fVerticalWriting, LOGFONTW *plf)
|
|
{
|
|
// other languages will follow later
|
|
//
|
|
const WCHAR c_szFontJPW2K[] = L"Microsoft Sans Serif";
|
|
const WCHAR c_szFontJPOTHER[] = L"MS P Gothic";
|
|
const WCHAR c_szFontJPNVert[] = L"@MS Gothic";
|
|
const WCHAR c_szFontJPNVertWin9x[] = L"@\xFF2D\xFF33 \xFF30\x30B4\x30B7\x30C3\x30AF"; // @MS P Gothic
|
|
const WCHAR c_szFontCHS[] = L"SimSum";
|
|
const WCHAR c_szFontCHSVert[] = L"@SimSun";
|
|
const WCHAR c_szFontCHSVertLoc[] = L"@\x5b8b\x4f53";
|
|
|
|
Assert(plf);
|
|
|
|
int iDpi = 96;
|
|
int iPoint = 0;
|
|
|
|
HDC hdc = CreateIC("DISPLAY", NULL, NULL, NULL);
|
|
if (hdc)
|
|
{
|
|
iDpi = GetDeviceCaps(hdc, LOGPIXELSY);
|
|
DeleteDC(hdc);
|
|
}
|
|
else
|
|
goto err_exit;
|
|
|
|
|
|
|
|
switch(PRIMARYLANGID(m_langid))
|
|
{
|
|
case LANG_JAPANESE:
|
|
iPoint = 12; // Satori uses 12 point font
|
|
|
|
if ( !fVerticalWriting )
|
|
{
|
|
wcsncpy(plf->lfFaceName,
|
|
IsOnNT5() ? c_szFontJPW2K : c_szFontJPOTHER,
|
|
ARRAYSIZE(plf->lfFaceName));
|
|
}
|
|
else
|
|
{
|
|
wcsncpy(plf->lfFaceName,
|
|
IsOn98() ? c_szFontJPNVertWin9x : c_szFontJPNVert,
|
|
ARRAYSIZE(plf->lfFaceName));
|
|
}
|
|
|
|
// don't bother to call GetLocaleInfo() for now
|
|
plf->lfCharSet = SHIFTJIS_CHARSET;
|
|
break;
|
|
|
|
case LANG_CHINESE:
|
|
|
|
iPoint = 9;
|
|
|
|
if ( !fVerticalWriting )
|
|
{
|
|
wcsncpy(plf->lfFaceName, c_szFontCHS, ARRAYSIZE(plf->lfFaceName));
|
|
}
|
|
else
|
|
{
|
|
wcsncpy(plf->lfFaceName,
|
|
IsOnNT5() ? c_szFontCHSVert : c_szFontCHSVertLoc,
|
|
ARRAYSIZE(plf->lfFaceName));
|
|
}
|
|
|
|
// don't bother to call GetLocaleInfo() for now
|
|
plf->lfCharSet = GB2312_CHARSET;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
if (iPoint > 0)
|
|
plf->lfHeight = -iPoint * iDpi / 72;
|
|
|
|
err_exit:
|
|
|
|
return iPoint > 0;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CFunction
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ctor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CFunction::CFunction(CSapiIMX *pImx)
|
|
{
|
|
m_pImx = pImx;
|
|
|
|
if (m_pImx)
|
|
m_pImx->AddRef();
|
|
|
|
m_cRef = 1;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CFunction::~CFunction()
|
|
{
|
|
SafeRelease(m_pImx);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFunction::GetFocusedTarget
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CFunction::GetFocusedTarget(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, BOOL bAdjust, ITfRange **ppRangeTmp)
|
|
{
|
|
ITfRange *pRangeTmp = NULL;
|
|
ITfRange *pRangeTmp2 = NULL;
|
|
IEnumTfRanges *pEnumTrack = NULL;
|
|
BOOL bRet = FALSE;
|
|
|
|
BOOL fWholeDoc = FALSE;
|
|
|
|
if (!pRange)
|
|
{
|
|
fWholeDoc = TRUE;
|
|
|
|
if (FAILED(GetRangeForWholeDoc(ec, pic, &pRange)))
|
|
return FALSE;
|
|
}
|
|
|
|
if (bAdjust)
|
|
{
|
|
//
|
|
// multi owner and PF_FOCUS range support.
|
|
//
|
|
|
|
if (FAILED(AdjustRangeByTextOwner(ec, pic,
|
|
pRange,
|
|
&pRangeTmp2,
|
|
CLSID_SapiLayr)))
|
|
goto Exit;
|
|
|
|
GUID rgGuid[2];
|
|
rgGuid[0] = GUID_ATTR_SAPI_INPUT;
|
|
rgGuid[1] = GUID_ATTR_SAPI_GREENBAR;
|
|
|
|
if (FAILED(AdjustRangeByAttribute(m_pImx->_GetLibTLS(),
|
|
ec, pic,
|
|
pRangeTmp2,
|
|
&pRangeTmp,
|
|
rgGuid, ARRAYSIZE(rgGuid))))
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
pRange->Clone(&pRangeTmp);
|
|
}
|
|
|
|
ITfRange *pPropRange;
|
|
ITfReadOnlyProperty *pProp;
|
|
//
|
|
// check if there is an intersection of PF_FOCUS range and owned range.
|
|
// if there is no such range, we return FALSE.
|
|
//
|
|
if (FAILED(EnumTrackTextAndFocus(ec, pic, pRangeTmp, &pProp, &pEnumTrack)))
|
|
goto Exit;
|
|
|
|
while(pEnumTrack->Next(1, &pPropRange, 0) == S_OK)
|
|
{
|
|
if (IsOwnerAndFocus(m_pImx->_GetLibTLS(), ec, CLSID_SapiLayr, pProp, pPropRange))
|
|
bRet = TRUE;
|
|
|
|
pPropRange->Release();
|
|
}
|
|
|
|
pProp->Release();
|
|
|
|
if (bRet)
|
|
{
|
|
*ppRangeTmp = pRangeTmp;
|
|
(*ppRangeTmp)->AddRef();
|
|
}
|
|
|
|
Exit:
|
|
SafeRelease(pEnumTrack);
|
|
SafeRelease(pRangeTmp);
|
|
SafeRelease(pRangeTmp2);
|
|
if (fWholeDoc)
|
|
pRange->Release();
|
|
return bRet;
|
|
}
|
|
|
|
HRESULT CFunction::_GetLangIdFromRange(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, LANGID *plangid)
|
|
{
|
|
HRESULT hr;
|
|
ITfProperty *pProp;
|
|
LANGID langid;
|
|
|
|
// get langid from the given range
|
|
if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LANGID, &pProp)))
|
|
{
|
|
GetLangIdPropertyData(ec, pProp, pRange, &langid);
|
|
pProp->Release();
|
|
}
|
|
|
|
if (SUCCEEDED(hr) && plangid)
|
|
*plangid = langid;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CFnReconversion
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IUnknown
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CFnReconversion::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_ITfFnReconversion))
|
|
{
|
|
*ppvObj = SAFECAST(this, CFnReconversion *);
|
|
}
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDAPI_(ULONG) CFnReconversion::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
STDAPI_(ULONG) CFnReconversion::Release()
|
|
{
|
|
long cr;
|
|
|
|
cr = InterlockedDecrement(&m_cRef);
|
|
Assert(cr >= 0);
|
|
|
|
if (cr == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ctor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CFnReconversion::CFnReconversion(CSapiIMX *psi) : CFunction(psi) , CMasterLMWrap(psi), CBestPropRange( )
|
|
{
|
|
m_psal = NULL;
|
|
|
|
// initialize with the current profile langid
|
|
m_langid = m_pImx->GetLangID();
|
|
|
|
// m_MaxCandChars = 0;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CFnReconversion::~CFnReconversion()
|
|
{
|
|
if (m_psal)
|
|
{
|
|
delete m_psal;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CFnReconversion::GetDisplayName(BSTR *pbstrName)
|
|
{
|
|
*pbstrName = SysAllocString(L"Reconversion");
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::QueryRange
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::QueryRange(ITfRange *pRange, ITfRange **ppRange, BOOL *pfConvertable)
|
|
{
|
|
CFnRecvEditSession *pes;
|
|
CComPtr<ITfContext> cpic;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (ppRange == NULL || pfConvertable == NULL || pRange == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppRange = NULL;
|
|
*pfConvertable = FALSE;
|
|
|
|
// call MasterLM when it's available
|
|
//
|
|
_EnsureMasterLM(m_langid);
|
|
if (m_cpMasterLM)
|
|
{
|
|
hr = m_cpMasterLM->QueryRange( pRange, ppRange, pfConvertable );
|
|
|
|
return hr;
|
|
}
|
|
|
|
if (SUCCEEDED(pRange->GetContext(&cpic)))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (pes = new CFnRecvEditSession(this, pRange, cpic))
|
|
{
|
|
pes->_SetEditSessionData(ESCB_RECONV_QUERYRECONV,NULL, 0);
|
|
|
|
cpic->RequestEditSession(m_pImx->_GetId(), pes, TF_ES_READ | TF_ES_SYNC, &hr);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
*ppRange = (ITfRange *)pes->_GetRetUnknown( );
|
|
|
|
pes->Release();
|
|
}
|
|
|
|
*pfConvertable = (hr == S_OK);
|
|
if (hr == S_FALSE)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::GetReconversion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CFnReconversion::GetReconversion(ITfRange *pRange, ITfCandidateList **ppCandList)
|
|
{
|
|
CFnRecvEditSession *pes;
|
|
ITfContext *pic;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// Call master LM when it's available!
|
|
//
|
|
//
|
|
Assert(pRange);
|
|
|
|
_EnsureMasterLM(m_langid);
|
|
if (m_cpMasterLM)
|
|
{
|
|
return m_cpMasterLM->GetReconversion( pRange, ppCandList);
|
|
}
|
|
|
|
if (FAILED(pRange->GetContext(&pic)))
|
|
goto Exit;
|
|
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (pes = new CFnRecvEditSession(this, pRange,pic) )
|
|
{
|
|
pes->_SetEditSessionData(ESCB_RECONV_GETRECONV,NULL, 0);
|
|
pic->RequestEditSession(m_pImx->_GetId(), pes, TF_ES_READ | TF_ES_SYNC, &hr);
|
|
|
|
if (SUCCEEDED(hr))
|
|
*ppCandList = (ITfCandidateList *)pes->_GetRetUnknown( );
|
|
|
|
pes->Release();
|
|
}
|
|
|
|
pic->Release();
|
|
|
|
Exit:
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::_QueryReconversion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT CFnReconversion::_QueryReconversion(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, ITfRange **ppNewRange)
|
|
{
|
|
Assert(pic);
|
|
Assert(pRange);
|
|
Assert(ppNewRange);
|
|
Assert(*ppNewRange == NULL);
|
|
|
|
|
|
CComPtr<ITfProperty> cpProp ;
|
|
HRESULT hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp);
|
|
|
|
if (SUCCEEDED(hr) && cpProp)
|
|
{
|
|
CComPtr<ITfRange> cpBestPropRange;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = _ComputeBestFitPropRange(ec, cpProp, pRange, &cpBestPropRange, NULL, NULL);
|
|
}
|
|
// adjust start element and num elements
|
|
if (S_OK == hr)
|
|
{
|
|
if (ppNewRange)
|
|
{
|
|
// TODO: this adjustment has to be done per element not phrase
|
|
*ppNewRange = cpBestPropRange;
|
|
(*ppNewRange)->AddRef();
|
|
}
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::_GetSapilayrEngineInstance
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT CFnReconversion::_GetSapilayrEngineInstance(ISpRecognizer **ppRecoEngine)
|
|
{
|
|
#ifdef _WIN64
|
|
return E_NOTIMPL;
|
|
#else
|
|
HRESULT hr = E_FAIL;
|
|
CComPtr<ITfFnGetSAPIObject> cpGetSAPI;
|
|
|
|
// we shouldn't release this until we terminate ourselves
|
|
// so we don't use comptr here
|
|
hr = m_pImx->GetFunction(GUID_NULL, IID_ITfFnGetSAPIObject, (IUnknown **)&cpGetSAPI);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpGetSAPI->Get(GETIF_RECOGNIZERNOINIT, (IUnknown **)ppRecoEngine);
|
|
}
|
|
|
|
return hr;
|
|
#endif
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::_GetReconversion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT CFnReconversion::_GetReconversion(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, ITfCandidateList **ppCandList, BOOL fDisableEngine /*=FALSE*/)
|
|
{
|
|
WCHAR *pszText = NULL;
|
|
CCandidateList *pCandList = NULL;
|
|
CComPtr<ITfProperty> cpProp;
|
|
HRESULT hr;
|
|
BOOL fEmpty;
|
|
|
|
if(!pRange)
|
|
return E_FAIL;
|
|
|
|
Assert(m_pImx);
|
|
ULONG cAlt = m_pImx->_GetMaxAlternates();
|
|
|
|
|
|
if (pRange->IsEmpty(ec, &fEmpty) != S_OK || fEmpty)
|
|
return E_FAIL;
|
|
|
|
// GUID_PROP_SAPIRESULTOBJECT really gets us
|
|
// a wrapper object of ISpRecoResult
|
|
//
|
|
hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp);
|
|
if (S_OK != hr)
|
|
return S_FALSE;
|
|
|
|
// try to reuse the candidate object if user opens it twice
|
|
// on the same range using the same function instance
|
|
//
|
|
if (m_psal && m_psal->IsSameRange(pRange, ec))
|
|
{
|
|
CComPtr<ITfRange> cpPropRangeTemp;
|
|
hr = cpProp->FindRange(ec, pRange, &cpPropRangeTemp, TF_ANCHOR_START);
|
|
|
|
if (S_OK == hr)
|
|
hr = GetCandidateForRange(m_psal, pic, pRange, ppCandList) ;
|
|
}
|
|
else
|
|
{
|
|
// get langid from the range property
|
|
LANGID langid;
|
|
if (FAILED(_GetLangIdFromRange(ec, pic, pRange, &langid)) || (langid == 0))
|
|
{
|
|
langid = GetUserDefaultLangID();
|
|
}
|
|
|
|
_SetCurrentLangID(langid);
|
|
|
|
if (m_psal)
|
|
{
|
|
delete m_psal;
|
|
m_psal = NULL;
|
|
}
|
|
|
|
CSapiAlternativeList *psal = new CSapiAlternativeList(langid, pRange, _GetMaxCandidateChars( ));
|
|
|
|
if (psal)
|
|
{
|
|
m_psal = psal;
|
|
}
|
|
|
|
if (SUCCEEDED(hr) && cpProp && psal)
|
|
{
|
|
CComPtr<ITfRange> cpPropRange;
|
|
hr = cpProp->FindRange(ec, pRange, &cpPropRange, TF_ANCHOR_START);
|
|
|
|
CComPtr<IUnknown> cpunk;
|
|
|
|
// this punk points to the wrapper
|
|
if (S_OK == hr)
|
|
hr = GetUnknownPropertyData(ec, cpProp, cpPropRange, &cpunk);
|
|
|
|
if ((hr == S_OK) && cpunk)
|
|
{
|
|
|
|
CSpTask *psp;
|
|
|
|
hr = m_pImx->GetSpeechTask(&psp);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
CRecoResultWrap *pResWrap;
|
|
hr = cpunk->QueryInterface(IID_PRIV_RESULTWRAP, (void **)&pResWrap);
|
|
if (S_OK == hr)
|
|
{
|
|
CComPtr<ITfRange> cpBestPropRange;
|
|
ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(cAlt*sizeof(ISpPhraseAlt *));
|
|
if (!ppAlt)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
ULONG ulStart, ulcElem;
|
|
hr = _ComputeBestFitPropRange(ec, cpProp, pRange, &cpBestPropRange, &ulStart, &ulcElem);
|
|
if (S_OK == hr)
|
|
{
|
|
m_cpRecoResult.Release();
|
|
|
|
CComPtr<ISpRecognizer> cpEngine;
|
|
_GetSapilayrEngineInstance(&cpEngine);
|
|
|
|
if (fDisableEngine && cpEngine)
|
|
{
|
|
// We stop the engine deliberately here (whether active or not - cannot afford to check as
|
|
// we may get blocked by SAPI). This forces the engine into a synchronization since the audio
|
|
// stops and we are guaranteed to be able to display the candidate list object
|
|
// This particular scenario (fDisableEngine == TRUE) occurs with a Word right-click context
|
|
// request for alternates. The normal 'display alternates list' scenario is handled in the
|
|
// _Reconvert() call since it requires the engine to be re-enabled after the alternates list
|
|
// is display to avoid it blocking until the engine hears silence.
|
|
cpEngine->SetRecoState(SPRST_INACTIVE_WITH_PURGE);
|
|
}
|
|
|
|
hr = psp->GetAlternates(pResWrap, ulStart, ulcElem, ppAlt, &cAlt, &m_cpRecoResult);
|
|
|
|
if (fDisableEngine && cpEngine)
|
|
{
|
|
// If the microphone is supposed to be open, we now restart the engine.
|
|
if (m_pImx->GetOnOff())
|
|
{
|
|
// We need to restart the engine now that we are fully initialized.
|
|
cpEngine->SetRecoState(SPRST_ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// Before we call SetPhraseAlt( ), we need to get the current parent text covered
|
|
// by cpBestPropRange.
|
|
WCHAR *pwszParent = NULL;
|
|
CComPtr<ITfRange> cpParentRange;
|
|
long cchChunck = 128;
|
|
|
|
hr = cpBestPropRange->Clone(&cpParentRange);
|
|
if ( S_OK == hr )
|
|
{
|
|
long cch;
|
|
int iNumOfChunck=1;
|
|
|
|
pwszParent = (WCHAR *) cicMemAllocClear((cchChunck+1) * sizeof(WCHAR) );
|
|
|
|
if ( pwszParent )
|
|
{
|
|
hr = cpParentRange->GetText(ec, TF_TF_MOVESTART, pwszParent, (ULONG)cchChunck, (ULONG *)&cch);
|
|
|
|
if ( (S_OK == hr) && ( cch > 0 ) )
|
|
pwszParent[cch] = L'\0';
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
while ( (S_OK == hr) && (cch == cchChunck))
|
|
{
|
|
long iNewSize;
|
|
|
|
iNewSize = ((iNumOfChunck+1) * cchChunck + 1 ) * sizeof(WCHAR);
|
|
|
|
pwszParent = (WCHAR *)cicMemReAlloc(pwszParent, iNewSize);
|
|
|
|
if ( pwszParent )
|
|
{
|
|
WCHAR *pwszNewPosition;
|
|
|
|
pwszNewPosition = pwszParent + iNumOfChunck * cchChunck;
|
|
hr = cpParentRange->GetText(ec, TF_TF_MOVESTART, pwszNewPosition, (ULONG)cchChunck, (ULONG *)&cch);
|
|
|
|
if ( (S_OK == hr) && ( cch > 0 ) )
|
|
pwszNewPosition[cch] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
iNumOfChunck ++;
|
|
}
|
|
}
|
|
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
|
|
// this is to store the obtained alternate phrases
|
|
// to CSapiAlternativeList class instance
|
|
//
|
|
hr = psal->SetPhraseAlt(pResWrap, ppAlt, cAlt, ulStart, ulcElem, pwszParent);
|
|
}
|
|
|
|
if ( pwszParent )
|
|
cicMemFree(pwszParent);
|
|
|
|
}
|
|
if ((hr == S_OK) && ppAlt)
|
|
{
|
|
for (UINT i = 0; i < cAlt; i++)
|
|
{
|
|
if (NULL != ppAlt[i])
|
|
{
|
|
ppAlt[i]->Release();
|
|
ppAlt[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ppAlt )
|
|
cicMemFree(ppAlt);
|
|
}
|
|
|
|
pResWrap->Release();
|
|
|
|
// get this alternative list processed by external LM
|
|
//
|
|
if (S_OK == hr)
|
|
{
|
|
Assert(cpBestPropRange);
|
|
|
|
hr = GetCandidateForRange(psal, pic, cpBestPropRange, ppCandList) ;
|
|
}
|
|
}
|
|
psp->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CFnReconversion::GetCandidateForRange(CSapiAlternativeList *psal, ITfContext *pic, ITfRange *pRange, ITfCandidateList **ppCandList)
|
|
{
|
|
Assert(psal);
|
|
|
|
if ( !psal || !pic || !pRange || !ppCandList )
|
|
return E_INVALIDARG;
|
|
|
|
HRESULT hr = S_OK;
|
|
int nItem = psal->GetNumItem();
|
|
WCHAR *pwszAlt = NULL;
|
|
|
|
CCandidateList *pCandList = new CCandidateList(SetResult, pic, pRange, SetOption);
|
|
|
|
if (!pCandList)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
int nPrb;
|
|
|
|
for (int i = 0; SUCCEEDED(hr) && i < nItem ; i++)
|
|
{
|
|
psal->GetCachedAltInfo(i, NULL, NULL,NULL, &pwszAlt);
|
|
|
|
if ( pwszAlt )
|
|
{
|
|
hr = psal->GetProbability(i, &nPrb);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// note CSapiAlternateveList has exactly same life span as CFnReconversion
|
|
pCandList->AddString(pwszAlt, m_langid, psal, this, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add menu options here.
|
|
HICON hIcon = NULL;
|
|
WCHAR wzTmp[MAX_PATH];
|
|
|
|
wzTmp[0] = 0;
|
|
CicLoadStringWrapW(g_hInst, IDS_REPLAY, wzTmp, MAX_PATH);
|
|
if (wzTmp[0] != 0)
|
|
{
|
|
hIcon = (HICON)LoadImage(g_hInst, MAKEINTRESOURCE(ID_ICON_TTSPLAY), IMAGE_ICON, 16, 16, 0);
|
|
pCandList->AddOption(wzTmp, m_langid, NULL, this, NULL, OPTION_REPLAY, hIcon ? hIcon : NULL, NULL);
|
|
}
|
|
|
|
wzTmp[0] = 0;
|
|
CicLoadStringWrapW(g_hInst, IDS_DELETE, wzTmp, MAX_PATH);
|
|
if (wzTmp[0] != 0)
|
|
{
|
|
hIcon = (HICON)LoadImage(g_hInstSpgrmr,
|
|
MAKEINTRESOURCE(IDI_SPTIP_DELETEICON),
|
|
IMAGE_ICON, 16, 16, 0);
|
|
pCandList->AddOption(wzTmp, m_langid, NULL, this, NULL, OPTION_DELETE, hIcon ? hIcon : NULL, NULL);
|
|
}
|
|
|
|
if (GetSystemMetrics(SM_TABLETPC) > 0)
|
|
{
|
|
BOOL fDisplayRedo = TRUE;
|
|
DWORD dw = 0;
|
|
CMyRegKey regkey;
|
|
|
|
if (S_OK == regkey.Open(HKEY_LOCAL_MACHINE, c_szSapilayrKey, KEY_READ ) )
|
|
{
|
|
if (ERROR_SUCCESS == regkey.QueryValue(dw, TEXT("DisableRewrite")))
|
|
{
|
|
if (dw == 1)
|
|
{
|
|
fDisplayRedo = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fDisplayRedo)
|
|
{
|
|
wzTmp[0] = 0;
|
|
CicLoadStringWrapW(g_hInst, IDS_REDO, wzTmp, MAX_PATH);
|
|
if (wzTmp[0] != 0)
|
|
{
|
|
pCandList->AddOption(wzTmp, m_langid, NULL, this, NULL, OPTION_REDO, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
hr = pCandList->QueryInterface(IID_ITfCandidateList, (void **)ppCandList);
|
|
pCandList->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::Reconvert
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CFnReconversion::Reconvert(ITfRange *pRange)
|
|
{
|
|
CFnRecvEditSession *pes;
|
|
ITfContext *pic;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
Assert(pRange);
|
|
|
|
if (FAILED(pRange->GetContext(&pic)))
|
|
goto Exit;
|
|
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (pes = new CFnRecvEditSession(this, pRange, pic))
|
|
{
|
|
BOOL fCallLMReconvert = FALSE;
|
|
|
|
pes->_SetEditSessionData(ESCB_RECONV_RECONV, NULL, 0);
|
|
pic->RequestEditSession(m_pImx->_GetId(), pes, TF_ES_READWRITE | TF_ES_ASYNC, &hr);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
fCallLMReconvert = (BOOL)pes->_GetRetData( );
|
|
|
|
if (hr == S_OK && fCallLMReconvert)
|
|
{
|
|
// need to call LM reconvert
|
|
Assert(m_cpMasterLM != NULL);
|
|
hr = m_cpMasterLM->Reconvert(pRange);
|
|
}
|
|
|
|
pes->Release();
|
|
}
|
|
|
|
pic->Release();
|
|
|
|
Exit:
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::_Reconvert
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::_Reconvert(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, BOOL *pfCallLMReconvert)
|
|
{
|
|
ITfCandidateList *pCandList;
|
|
HRESULT hr;
|
|
CSpTask *psp = NULL;
|
|
|
|
// Call master LM when it's available!
|
|
//
|
|
// For voice playback, we need to do a little more.
|
|
// we have to call QueryRange first and determine the
|
|
// length of playback here.
|
|
//
|
|
*pfCallLMReconvert = FALSE;
|
|
|
|
_EnsureMasterLM(m_langid);
|
|
if (m_cpMasterLM)
|
|
{
|
|
// playback the whole range for now
|
|
//
|
|
CComPtr<ITfProperty> cpProp;
|
|
hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp);
|
|
if (S_OK == hr)
|
|
{
|
|
CComPtr<ITfRange> cpPropRange;
|
|
CComPtr<IUnknown> cpunk;
|
|
hr = cpProp->FindRange(ec, pRange, &cpPropRange, TF_ANCHOR_START);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = GetUnknownPropertyData(ec, cpProp, cpPropRange, &cpunk);
|
|
}
|
|
if (S_OK == hr)
|
|
{
|
|
CRecoResultWrap *pwrap = (CRecoResultWrap *)(void *)cpunk;
|
|
pwrap->_SpeakAudio(0, 0);
|
|
}
|
|
}
|
|
|
|
// after exiting this edit session, caller needs to call m_cpMasterLM->Reconvert
|
|
*pfCallLMReconvert = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
CComPtr<ISpRecognizer> cpEngine;
|
|
_GetSapilayrEngineInstance(&cpEngine);
|
|
|
|
if (cpEngine)
|
|
{
|
|
// We stop the engine deliberately here (whether active or not - cannot afford to check as
|
|
// we may get blocked by SAPI). This forces the engine into a synchronization since the audio
|
|
// stops and we are guaranteed to be able to display the candidate list object
|
|
cpEngine->SetRecoState(SPRST_INACTIVE_WITH_PURGE);
|
|
|
|
}
|
|
|
|
if (S_OK != (hr = _GetReconversion(ec, pic, pRange, &pCandList)))
|
|
return hr;
|
|
|
|
// voice playback
|
|
|
|
if ( m_pImx->_EnablePlaybackWhileCandUIOpen( ) )
|
|
{
|
|
if (m_psal)
|
|
m_psal->_Speak();
|
|
}
|
|
|
|
hr = ShowCandidateList(ec, pic, pRange, pCandList);
|
|
|
|
if (cpEngine)
|
|
{
|
|
// If the microphone is supposed to be open, we now restart the engine.
|
|
if (m_pImx->GetOnOff())
|
|
{
|
|
// We need to restart the engine now that we are fully initialized.
|
|
cpEngine->SetRecoState(SPRST_ACTIVE);
|
|
}
|
|
}
|
|
|
|
pCandList->Release();
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::ShowCandidateList
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::ShowCandidateList(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, ITfCandidateList *pCandList)
|
|
{
|
|
// Determine if current range is vertical writing
|
|
CComPtr<ITfReadOnlyProperty> cpProperty;
|
|
VARIANT var;
|
|
BOOL fVertical = FALSE;
|
|
ULONG lDirection = 0;
|
|
|
|
if ( pic->GetAppProperty(TSATTRID_Text_VerticalWriting, &cpProperty) == S_OK )
|
|
{
|
|
if (cpProperty->GetValue(ec, pRange, &var) == S_OK )
|
|
{
|
|
fVertical = var.boolVal;
|
|
}
|
|
}
|
|
|
|
// Get the current text orientation.
|
|
cpProperty.Release( );
|
|
if ( pic->GetAppProperty(TSATTRID_Text_Orientation, &cpProperty) == S_OK )
|
|
{
|
|
if (cpProperty->GetValue(ec, pRange, &var) == S_OK )
|
|
{
|
|
lDirection = var.lVal;
|
|
}
|
|
}
|
|
|
|
// During the speech tip activation time, we just want to create candidateui object once for perf improvement.
|
|
if ( m_pImx->_pCandUIEx == NULL )
|
|
{
|
|
CoCreateInstance(CLSID_TFCandidateUI, NULL, CLSCTX_INPROC_SERVER, IID_ITfCandidateUI, (void**)&m_pImx->_pCandUIEx);
|
|
}
|
|
|
|
if ( m_pImx->_pCandUIEx )
|
|
{
|
|
ITfDocumentMgr *pdim;
|
|
if (SUCCEEDED(m_pImx->GetFocusDIM(&pdim)))
|
|
{
|
|
|
|
m_pImx->_pCandUIEx->SetClientId(m_pImx->_GetId());
|
|
|
|
ITfCandUIAutoFilterEventSink *pCuiFes = new CCandUIFilterEventSink(this, pic, m_pImx->_pCandUIEx);
|
|
CComPtr<ITfCandUIFnAutoFilter> cpFnFilter;
|
|
|
|
if (S_OK == m_pImx->_pCandUIEx->GetFunction(IID_ITfCandUIFnAutoFilter, (IUnknown **)&cpFnFilter))
|
|
{
|
|
cpFnFilter->Advise(pCuiFes);
|
|
cpFnFilter->Enable(TRUE);
|
|
|
|
}
|
|
|
|
//
|
|
// set the right font size for Japanese and Chinese case
|
|
//
|
|
CComPtr<ITfCandUICandString> cpITfCandUIObj;
|
|
if (S_OK == m_pImx->_pCandUIEx->GetUIObject(IID_ITfCandUICandString, (IUnknown **)&cpITfCandUIObj))
|
|
{
|
|
Assert(m_psal); // this shouldn't fail
|
|
|
|
if (m_psal)
|
|
{
|
|
LOGFONTW lf = {0};
|
|
if (m_psal->_GetUIFont(fVertical, &lf))
|
|
{
|
|
cpITfCandUIObj->SetFont(&lf);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the candidate Ui window's style.
|
|
//
|
|
// Speech TIP always uses drop-down candidat window.
|
|
//
|
|
CComPtr<ITfCandUIFnUIConfig> cpFnUIConfig;
|
|
|
|
if (S_OK == m_pImx->_pCandUIEx->GetFunction(IID_ITfCandUIFnUIConfig, (IUnknown **)&cpFnUIConfig))
|
|
{
|
|
CANDUISTYLE style;
|
|
|
|
style = CANDUISTY_LIST;
|
|
cpFnUIConfig->SetUIStyle(pic, style);
|
|
}
|
|
|
|
//
|
|
// Set the candidate UI window's direction.
|
|
//
|
|
|
|
CComPtr<ITfCandUICandWindow> cpUICandWnd;
|
|
|
|
if ( S_OK == m_pImx->_pCandUIEx->GetUIObject(IID_ITfCandUICandWindow, (IUnknown **)&cpUICandWnd) )
|
|
{
|
|
CANDUIUIDIRECTION dwOption = CANDUIDIR_TOPTOBOTTOM;
|
|
|
|
switch ( lDirection )
|
|
{
|
|
case 900 : // Text direction Bottom to Top
|
|
dwOption = CANDUIDIR_LEFTTORIGHT;
|
|
break;
|
|
|
|
case 1800 : // Text direction Right to Left.
|
|
dwOption = CANDUIDIR_BOTTOMTOTOP;
|
|
break;
|
|
|
|
case 2700 : // Text direction Top to Bottom.
|
|
dwOption = dwOption = CANDUIDIR_RIGHTTOLEFT;
|
|
break;
|
|
|
|
default :
|
|
dwOption = CANDUIDIR_TOPTOBOTTOM;
|
|
break;
|
|
}
|
|
|
|
cpUICandWnd->SetUIDirection(dwOption);
|
|
|
|
}
|
|
|
|
m_pImx->_pCandUIEx->SetCandidateList(pCandList);
|
|
|
|
// Before Open Candidate UI window, we want to save the current IP
|
|
|
|
m_pImx->_SaveCorrectOrgIP(ec, pic);
|
|
|
|
m_pImx->_pCandUIEx->OpenCandidateUI(NULL, pdim, ec, pRange);
|
|
|
|
pCuiFes->Release();
|
|
pdim->Release();
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::SetResult
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::SetResult(ITfContext *pic, ITfRange *pRange, CCandidateString *pCand, TfCandidateResult imcr)
|
|
{
|
|
BSTR bstr;
|
|
HRESULT hr = S_OK;
|
|
CFnReconversion *pReconv = (CFnReconversion *)(pCand->_punk);
|
|
|
|
if ((imcr == CAND_FINALIZED) || (imcr == CAND_SELECTED))
|
|
{
|
|
pCand->GetString(&bstr);
|
|
|
|
// TODO: here we have to re-calc the range based on the strart element points
|
|
// which are indicated from AltInfo.
|
|
ULONG ulParentStart = 0;
|
|
ULONG ulcParentElements = 0;
|
|
ULONG ulIndex = 0;
|
|
ULONG cchParentStart = 0;
|
|
ULONG cchParentReplace = 0;
|
|
|
|
BOOL fNoAlternate = FALSE;
|
|
|
|
CSapiAlternativeList *psal = (CSapiAlternativeList *)pCand->_pv;
|
|
|
|
pCand->GetIndex(&ulIndex);
|
|
|
|
if (psal)
|
|
{
|
|
CRecoResultWrap *cpRecoWrap;
|
|
ULONG ulStartElement;
|
|
ULONG ulLeadSpaceRemoved = 0;
|
|
|
|
// save current selection index.
|
|
|
|
psal->_SaveCurrentSelectionIndex(ulIndex);
|
|
|
|
cpRecoWrap = psal->GetResultWrap();
|
|
ulStartElement = cpRecoWrap->GetStart( );
|
|
|
|
psal->GetCachedAltInfo(ulIndex, &ulParentStart, &ulcParentElements, NULL, NULL, &ulLeadSpaceRemoved);
|
|
|
|
cchParentStart = cpRecoWrap->_GetElementOffsetCch(ulParentStart);
|
|
cchParentReplace = cpRecoWrap->_GetElementOffsetCch(ulParentStart + ulcParentElements) - cchParentStart;
|
|
|
|
cchParentStart = cchParentStart - cpRecoWrap->_GetElementOffsetCch(ulStartElement) + cpRecoWrap->_GetOffsetDelta( );
|
|
|
|
if ( ulLeadSpaceRemoved > 0 && cchParentStart > ulLeadSpaceRemoved)
|
|
{
|
|
cchParentStart -= ulLeadSpaceRemoved;
|
|
cchParentReplace += ulLeadSpaceRemoved;
|
|
}
|
|
|
|
fNoAlternate = psal->_IsNoAlternate( );
|
|
|
|
if ( !fNoAlternate )
|
|
{
|
|
hr = pReconv->m_pImx->SetReplaceSelection(pRange, cchParentStart, cchParentReplace, pic);
|
|
|
|
if ( (SUCCEEDED(hr)) && (imcr == CAND_FINALIZED) )
|
|
{
|
|
if ( ulParentStart + ulcParentElements == ulStartElement + cpRecoWrap->GetNumElements( ) )
|
|
{
|
|
// The parent selection contains the last element.
|
|
// if its trailing spaces were removed before, we also want to
|
|
// remove the same number of trailing spaces in the new alternative text.
|
|
|
|
// We already considered this case during _Commit( ).
|
|
// We just need to update the result text which wil be injected.
|
|
|
|
ULONG ulTSRemoved;
|
|
|
|
ulTSRemoved = cpRecoWrap->GetTrailSpaceRemoved( );
|
|
|
|
if ( ulTSRemoved > 0 )
|
|
{
|
|
ULONG ulTextLen;
|
|
ULONG ulRemovedNew = 0;
|
|
|
|
ulTextLen = wcslen(bstr);
|
|
|
|
for ( ULONG i=ulTextLen-1; (int)i>=0 && ulRemovedNew <= ulTSRemoved; i-- )
|
|
{
|
|
if ( bstr[i] == L' ' )
|
|
{
|
|
bstr[i] = L'\0';
|
|
ulRemovedNew ++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if ( ulRemovedNew < ulTSRemoved )
|
|
cpRecoWrap->SetTrailSpaceRemoved( ulRemovedNew );
|
|
}
|
|
}
|
|
|
|
pReconv->_Commit(pCand);
|
|
|
|
// If the first element in this RecoWrap is updated by the new alternate
|
|
// speech tip needs to check if this new alternate wants to
|
|
// consume the leading space or if extra space is required to add
|
|
// between this phrase and previous phrase.
|
|
//
|
|
BOOL bHandleLeadingSpace = (ulParentStart == ulStartElement) ? TRUE : FALSE;
|
|
hr = pReconv->m_pImx->InjectAlternateText(bstr, pReconv->m_langid, pic, bHandleLeadingSpace);
|
|
|
|
//
|
|
// Update the Selection grammar's text buffer.
|
|
//
|
|
if ( SUCCEEDED(hr) && pReconv->m_pImx )
|
|
{
|
|
CSpTask *psp = NULL;
|
|
(pReconv->m_pImx)->GetSpeechTask(&psp);
|
|
|
|
if ( psp )
|
|
{
|
|
hr = psp->_UpdateSelectGramTextBufWhenStatusChanged( );
|
|
psp->Release( );
|
|
}
|
|
}
|
|
//
|
|
}
|
|
}
|
|
}
|
|
|
|
SysFreeString(bstr);
|
|
|
|
|
|
}
|
|
// close candidate UI if it's still there
|
|
if (imcr == CAND_FINALIZED || imcr == CAND_CANCELED)
|
|
{
|
|
// Just close the candidate UI, don't release the object, so that the object keeps alive while the
|
|
// speech tip is activated, this is for performance improvement.
|
|
pReconv->m_pImx->CloseCandUI( );
|
|
|
|
if ( imcr == CAND_CANCELED )
|
|
{
|
|
// Just release the stored IP to avoid memory leak.
|
|
// Don't restore it according to the new spec so that
|
|
// user can continue to dictate new text over the selection.
|
|
//
|
|
|
|
// If we find this is not a good Usuability,
|
|
// we can change it back to the original behavior.
|
|
pReconv->m_pImx->_ReleaseCorrectOrgIP( );
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::GetTabletTip
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::GetTabletTip(void)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IUnknown> cpunk;
|
|
|
|
if (m_cpTabletTip)
|
|
{
|
|
m_cpTabletTip = NULL; // Releases our reference.
|
|
}
|
|
|
|
hr = CoCreateInstance(CLSID_UIHost, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **) &cpunk);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = cpunk->QueryInterface(IID_ITipWindow, (void **) &m_cpTabletTip);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CFnReconversion::SetOption
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CFnReconversion::SetOption(ITfContext *pic, ITfRange *pRange, CCandidateString *pCand, TfCandidateResult imcr)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CFnReconversion *pReconv = (CFnReconversion *)(pCand->_punk);
|
|
|
|
if (imcr == CAND_FINALIZED)
|
|
{
|
|
ULONG ulID = 0;
|
|
|
|
pCand->GetID(&ulID);
|
|
switch (ulID)
|
|
{
|
|
case OPTION_REPLAY:
|
|
{
|
|
// Replay audio. Do not close candidate list.
|
|
CSapiAlternativeList *psal;
|
|
psal = pReconv->GetCSapiAlternativeList( );
|
|
if ( psal )
|
|
{
|
|
psal->_Speak( );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OPTION_DELETE: // Delete from dictinary...
|
|
{
|
|
// Close candidate UI.
|
|
pReconv->m_pImx->_pCandUIEx->CloseCandidateUI();
|
|
|
|
// Delete the current selection in the document.
|
|
pReconv->m_pImx->HandleKey(VK_DELETE);
|
|
|
|
break;
|
|
}
|
|
|
|
case OPTION_REDO: // Tablet PC specific option.
|
|
{
|
|
// Close candidate UI.
|
|
pReconv->m_pImx->_pCandUIEx->CloseCandidateUI();
|
|
|
|
if (pReconv->m_cpTabletTip == NULL)
|
|
{
|
|
pReconv->GetTabletTip();
|
|
}
|
|
if (pReconv->m_cpTabletTip)
|
|
{
|
|
hr = pReconv->m_cpTabletTip->ShowWnd(VARIANT_TRUE);
|
|
if (FAILED(hr))
|
|
{
|
|
// Reget TabletTip and try a second time in case the previous instance was killed.
|
|
hr = pReconv->GetTabletTip();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pReconv->m_cpTabletTip->ShowWnd(VARIANT_TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// close candidate UI if it's still there
|
|
if (imcr == CAND_CANCELED)
|
|
{
|
|
if (pReconv->m_pImx->_pCandUIEx)
|
|
{
|
|
pReconv->m_pImx->_pCandUIEx->CloseCandidateUI();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// _Commit
|
|
//
|
|
// synopisis: accept the candidate string as the final selection and
|
|
// let SR know the decision has been made
|
|
//
|
|
void CFnReconversion::_Commit(CCandidateString *pcand)
|
|
{
|
|
ULONG nIdx;
|
|
Assert(pcand);
|
|
|
|
if (S_OK == pcand->GetIndex(&nIdx))
|
|
{
|
|
// let CSapiAlternativeList class do the real work
|
|
if (m_psal)
|
|
m_psal->_Commit(nIdx, m_cpRecoResult);
|
|
|
|
// we no longer need to hold the reco result
|
|
m_cpRecoResult.Release();
|
|
}
|
|
}
|
|
|
|
ULONG CBestPropRange::_GetMaxCandidateChars( )
|
|
{
|
|
if ( m_MaxCandChars == 0 )
|
|
{
|
|
// it is not initialized
|
|
CMyRegKey regkey;
|
|
DWORD dw = MAX_CANDIDATE_CHARS;
|
|
|
|
if (S_OK == regkey.Open(HKEY_LOCAL_MACHINE, c_szSapilayrKey, KEY_READ))
|
|
{
|
|
regkey.QueryValue(dw, c_szMaxCandChars);
|
|
}
|
|
|
|
if ( (dw > MAX_CANDIDATE_CHARS) || (dw == 0) )
|
|
dw = MAX_CANDIDATE_CHARS;
|
|
|
|
m_MaxCandChars = dw;
|
|
}
|
|
|
|
return m_MaxCandChars;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FindVisiblePropertyRange
|
|
//
|
|
// Searches for a property range, skipping over any "empty" (containing only hidden text)
|
|
// property spans along the way.
|
|
//
|
|
// We can encounter hidden property spans (zero-length from a tip's point of view) if the
|
|
// user marks some dictated text as hidden in word.
|
|
HRESULT FindVisiblePropertyRange(TfEditCookie ec, ITfProperty *pProperty, ITfRange *pTestRange, ITfRange **ppPropertyRange)
|
|
{
|
|
BOOL fEmpty;
|
|
HRESULT hr;
|
|
|
|
while (TRUE)
|
|
{
|
|
hr = pProperty->FindRange(ec, pTestRange, ppPropertyRange, TF_ANCHOR_START);
|
|
|
|
if (hr != S_OK)
|
|
break;
|
|
|
|
if ((*ppPropertyRange)->IsEmpty(ec, &fEmpty) != S_OK)
|
|
{
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (!fEmpty)
|
|
break;
|
|
|
|
// found an empty property span
|
|
// this means it contains only hidden text, so skip it
|
|
if (pTestRange->ShiftStartToRange(ec, *ppPropertyRange, TF_ANCHOR_END) != S_OK)
|
|
{
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
(*ppPropertyRange)->Release();
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
*ppPropertyRange = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// _ComputeBestFitPropRange
|
|
//
|
|
// synopsis: returns the range that includes at least one SPPHRASE element
|
|
// which also includes the specified (incoming) range
|
|
// *pulStart should include the start element used in the reco result
|
|
// *pulcElem should include the # of elements used
|
|
//
|
|
HRESULT CBestPropRange::_ComputeBestFitPropRange
|
|
(
|
|
TfEditCookie ec,
|
|
ITfProperty *pProp,
|
|
ITfRange *pRangeIn,
|
|
ITfRange **ppBestPropRange,
|
|
ULONG *pulStart,
|
|
ULONG *pulcElem
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
CComPtr<ITfRange> cpPropRange ;
|
|
CComPtr<IUnknown> cpunk;
|
|
ULONG ucch;
|
|
|
|
BOOL fBeyondPropRange = FALSE;
|
|
|
|
|
|
TraceMsg(TF_GENERAL, "_ComputeBestFitPropRange is called");
|
|
|
|
// find the reco result with a span that includes the given range
|
|
Assert(pProp);
|
|
Assert(pRangeIn);
|
|
|
|
CComPtr<ITfRange> cpRange ;
|
|
hr = pRangeIn->Clone(&cpRange);
|
|
if (S_OK == hr)
|
|
{
|
|
hr = FindVisiblePropertyRange(ec, pProp, cpRange, &cpPropRange);
|
|
}
|
|
|
|
if ( hr == S_FALSE )
|
|
{
|
|
// if this is not a selection and the IP is at the last position of this region, we just try to reconvert on the possible previous
|
|
// dictated phrase.
|
|
BOOL fTryPreviousPhrase = FALSE;
|
|
BOOL fEmpty = FALSE;
|
|
|
|
// Add code here to check it meets the condition.
|
|
|
|
if ( S_OK == cpRange->IsEmpty(ec, &fEmpty) && fEmpty )
|
|
{
|
|
CComPtr<ITfRange> cpRangeTmp;
|
|
|
|
if ( S_OK == cpRange->Clone(&cpRangeTmp) )
|
|
{
|
|
LONG cch = 0;
|
|
if ( (S_OK == cpRangeTmp->ShiftStart(ec, 1, &cch, NULL)) && (cch < 1) )
|
|
{
|
|
// it is at the end of a region or entire document.
|
|
fTryPreviousPhrase = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( fTryPreviousPhrase )
|
|
{
|
|
LONG cch;
|
|
|
|
hr = cpRange->ShiftStart(ec, -1, &cch, NULL);
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
hr = cpRange->Collapse(ec, TF_ANCHOR_START);
|
|
}
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
hr = FindVisiblePropertyRange(ec, pProp, cpRange, &cpPropRange);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get a wrapper for the prop range
|
|
if (S_OK == hr)
|
|
{
|
|
hr = GetUnknownPropertyData(ec, pProp, cpPropRange, &cpunk);
|
|
}
|
|
if ((hr == S_OK) && cpunk)
|
|
{
|
|
// first calculate the # of chars upto the start anchor of the given range
|
|
//
|
|
CComPtr<ITfRange> cpClonedPropRange;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpPropRange->Clone(&cpClonedPropRange);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpClonedPropRange->ShiftEndToRange(ec, cpRange, TF_ANCHOR_START);
|
|
}
|
|
|
|
ULONG ulCchToSelection = 0;
|
|
ULONG ulCchInSelection = 0;
|
|
BOOL fEmpty;
|
|
if (S_OK == hr)
|
|
{
|
|
CSpDynamicString dstr;
|
|
while(S_OK == hr && (S_OK == cpClonedPropRange->IsEmpty(ec, &fEmpty)) && !fEmpty)
|
|
{
|
|
WCHAR sz[64];
|
|
|
|
hr = cpClonedPropRange->GetText(ec, TF_TF_MOVESTART, sz, ARRAYSIZE(sz)-1, &ucch);
|
|
if (S_OK == hr)
|
|
{
|
|
sz[ucch] = L'\0';
|
|
dstr.Append(sz);
|
|
}
|
|
}
|
|
ulCchToSelection = dstr.Length();
|
|
}
|
|
|
|
// then calc the # of chars upto the end anchor of the given range
|
|
if(S_OK == hr)
|
|
{
|
|
hr = cpRange->IsEmpty(ec, &fEmpty);
|
|
if (S_OK == hr && !fEmpty)
|
|
{
|
|
CComPtr<ITfRange> cpClonedGivenRange;
|
|
hr = cpRange->Clone(&cpClonedGivenRange);
|
|
// compare the end of the given range and proprange,
|
|
// if the given range goes beyond proprange, snap it
|
|
// within the proprange
|
|
if (S_OK == hr)
|
|
{
|
|
LONG lResult;
|
|
hr = cpClonedGivenRange->CompareEnd(ec, cpPropRange, TF_ANCHOR_END, &lResult);
|
|
if (S_OK == hr && lResult > 0)
|
|
{
|
|
// the end of the given range is beyond the proprange
|
|
// we need to snap it before getting text
|
|
hr = cpClonedGivenRange->ShiftEndToRange(ec, cpPropRange, TF_ANCHOR_END);
|
|
|
|
fBeyondPropRange = TRUE;
|
|
|
|
}
|
|
// now we get the text we use to calc the # of elements
|
|
CSpDynamicString dstr;
|
|
while(S_OK == hr && (S_OK == cpClonedGivenRange->IsEmpty(ec, &fEmpty)) && !fEmpty)
|
|
{
|
|
WCHAR sz[64];
|
|
hr = cpClonedGivenRange->GetText(ec, TF_TF_MOVESTART, sz, ARRAYSIZE(sz)-1, &ucch);
|
|
if (S_OK == hr)
|
|
{
|
|
sz[ucch] = L'\0';
|
|
dstr.Append(sz);
|
|
}
|
|
}
|
|
ulCchInSelection = dstr.Length();
|
|
|
|
// If there are some spaces in the beginning of the selection,
|
|
// we need to shift the start of the selection to the next non-space character.
|
|
|
|
if ( ulCchInSelection > 0 )
|
|
{
|
|
WCHAR *pStr;
|
|
|
|
pStr = (WCHAR *)dstr;
|
|
|
|
while ( (*pStr == L' ') || (*pStr == L'\t'))
|
|
{
|
|
ulCchInSelection --;
|
|
ulCchToSelection ++;
|
|
pStr ++;
|
|
}
|
|
|
|
if ( *pStr == L'\0' )
|
|
{
|
|
// This selection contains only spaces. no other non-space character.
|
|
// we don't want to get alternate for this selection.
|
|
// just return here.
|
|
|
|
if (ppBestPropRange != NULL )
|
|
*ppBestPropRange = NULL;
|
|
|
|
if ( pulStart != NULL )
|
|
*pulStart = 0;
|
|
|
|
if (pulcElem != NULL )
|
|
*pulcElem = 0;
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the result object cpunk points to our wrapper object
|
|
CComPtr<IServiceProvider> cpServicePrv;
|
|
CComPtr<ISpRecoResult> cpResult;
|
|
SPPHRASE *pPhrases = NULL;
|
|
CRecoResultWrap *pResWrap = NULL;
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpunk->QueryInterface(IID_IServiceProvider, (void **)&cpServicePrv);
|
|
}
|
|
// get result object
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpServicePrv->QueryService(GUID_NULL, IID_ISpRecoResult, (void **)&cpResult);
|
|
}
|
|
|
|
// now we can see how many elements we can use
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpResult->GetPhrase(&pPhrases);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpunk->QueryInterface(IID_PRIV_RESULTWRAP, (void **)&pResWrap);
|
|
}
|
|
|
|
if (S_OK == hr && pPhrases)
|
|
{
|
|
// calc the start anchor of the new range
|
|
#ifdef NOUSEELEMENTOFFSET
|
|
CSpDynamicString dstr;
|
|
#endif
|
|
long cchToElem_i = 0;
|
|
long cchAfterElem_i = 0;
|
|
BOOL fStartFound = FALSE;
|
|
ULONG i;
|
|
ULONG ulNumElements;
|
|
|
|
CComPtr<ITfRange> cpNewRange;
|
|
hr = cpRange->Clone(&cpNewRange);
|
|
|
|
if ( fBeyondPropRange )
|
|
{
|
|
hr = cpNewRange->ShiftEndToRange(ec, cpPropRange, TF_ANCHOR_END);
|
|
}
|
|
|
|
if ( ulCchInSelection > _GetMaxCandidateChars( ) )
|
|
{
|
|
// If the selection has more than MaxCandidate Chars, we need to shift the range end
|
|
// to left so that it contains at most MaxCandidate Chars in the selection.
|
|
long cch;
|
|
|
|
cch = (long)_GetMaxCandidateChars( ) - (long)ulCchInSelection;
|
|
ulCchInSelection = _GetMaxCandidateChars( );
|
|
cpNewRange->ShiftEnd(ec, cch, &cch, NULL);
|
|
}
|
|
|
|
ulNumElements = pResWrap->GetNumElements();
|
|
|
|
// get start element and # of elements via wrapper object
|
|
if ((S_OK == hr) && ulNumElements > 0 )
|
|
{
|
|
ULONG ulStart;
|
|
ULONG ulEnd;
|
|
ULONG ulOffsetStart;
|
|
ULONG ulDelta;
|
|
|
|
ulStart = pResWrap->GetStart();
|
|
ulEnd = ulStart + pResWrap->GetNumElements() - 1;
|
|
|
|
ulDelta = pResWrap->_GetOffsetDelta( );
|
|
ulOffsetStart = pResWrap->_GetElementOffsetCch(ulStart);
|
|
|
|
for (i = ulStart; i <= ulEnd; i++ )
|
|
{
|
|
#ifdef NOUSEELEMENTOFFSET
|
|
// CleanupConsider: replace this logic with pResWrap->GetElementOffsets(i)
|
|
// where we cache the calculated offsets
|
|
//
|
|
if (pPhrases->pElements[i].pszDisplayText)
|
|
{
|
|
cchToElem_i = dstr.Length();
|
|
|
|
dstr.Append(pPhrases->pElements[i].pszDisplayText);
|
|
|
|
|
|
if (pPhrases->pElements[i].bDisplayAttributes & SPAF_ONE_TRAILING_SPACE)
|
|
{
|
|
dstr.Append(L" ");
|
|
}
|
|
else if (pPhrases->pElements[i].bDisplayAttributes & SPAF_TWO_TRAILING_SPACES)
|
|
{
|
|
dstr.Append(L" ");
|
|
}
|
|
cchAfterElem_i = dstr.Length();
|
|
}
|
|
else
|
|
break;
|
|
#else
|
|
// when i < # of elements, it's guaranteed that we have n = i + 1
|
|
//
|
|
cchToElem_i = pResWrap->_GetElementOffsetCch(i) - ulOffsetStart + ulDelta;
|
|
cchAfterElem_i = pResWrap->_GetElementOffsetCch(i+1) - ulOffsetStart + ulDelta;
|
|
#endif
|
|
|
|
if ( ulCchInSelection == 0 )
|
|
{
|
|
// we need to specially handle this case that no character is in selection
|
|
// user just puts a cursor right before a character.
|
|
|
|
// We just want to find out which element would contain this IP.
|
|
// and then shift anchors to this element's start and end position.
|
|
|
|
if ( (ULONG)cchAfterElem_i > ulCchToSelection )
|
|
{
|
|
// This element is the right element to contain this IP.
|
|
long cch;
|
|
|
|
// this is usually reverse shifting
|
|
// Shift the start anchor to this element's start position.
|
|
|
|
cpNewRange->ShiftStart(ec, cchToElem_i - ulCchToSelection, &cch, NULL);
|
|
|
|
|
|
// store the starting element used
|
|
|
|
TraceMsg(TF_GENERAL, "Start element = %d", i);
|
|
|
|
if (pulStart)
|
|
{
|
|
*pulStart = i;
|
|
}
|
|
|
|
// Shift the end anchor to this element's end position.
|
|
cpNewRange->ShiftEnd(ec,
|
|
cchAfterElem_i - ulCchToSelection,
|
|
&cch, NULL);
|
|
|
|
|
|
TraceMsg(TF_GENERAL, "End Element = %d", i);
|
|
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 1) shift the start anchor of prop range based on the element offsets of
|
|
// the result object, comparing it with the start anchor (ulCchToSelection)
|
|
// of the given range
|
|
// - choose the start elements that is right before the start anchor.
|
|
//
|
|
if ((ULONG)cchAfterElem_i > ulCchToSelection && !fStartFound)
|
|
{
|
|
long cch;
|
|
// this is usually reverse shifting
|
|
cpNewRange->ShiftStart(ec, cchToElem_i - ulCchToSelection, &cch, NULL);
|
|
|
|
|
|
// store the starting element used
|
|
|
|
TraceMsg(TF_GENERAL, "Start element = %d", i);
|
|
|
|
if (pulStart)
|
|
{
|
|
*pulStart = i;
|
|
}
|
|
fStartFound = TRUE;
|
|
}
|
|
// 2) shift the end anchor of prop range based on the the element offset
|
|
// and the # of elements of result object,
|
|
// comparing it with the end anchor of the given range (ulCchToSelection+ulCchInSelection)
|
|
// - the element so the span ends right after the end anchor of the given range.
|
|
//
|
|
if ((ULONG)cchAfterElem_i >= ulCchToSelection + ulCchInSelection)
|
|
{
|
|
long cch;
|
|
|
|
if ( ulCchInSelection >= _GetMaxCandidateChars( ) )
|
|
{
|
|
// The selection contains MaxCand chars, we should make sure the char number
|
|
// in new proprange less than MaxCand.
|
|
|
|
if ( (ULONG)cchAfterElem_i > ulCchToSelection + ulCchInSelection )
|
|
{
|
|
// if keeping this element, the total char number will larger than MaxCand.
|
|
// So use the previous element as the last element.
|
|
if ( i > ulStart ) // This conditon should always be true.
|
|
{
|
|
i--;
|
|
cchAfterElem_i = pResWrap->_GetElementOffsetCch(i+1) - ulOffsetStart + ulDelta;
|
|
}
|
|
}
|
|
}
|
|
|
|
cpNewRange->ShiftEnd(ec,
|
|
cchAfterElem_i - (ulCchToSelection + ulCchInSelection),
|
|
&cch, NULL);
|
|
|
|
TraceMsg(TF_GENERAL, "End Element = %d", i);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (pulcElem && pulStart)
|
|
{
|
|
// we need to check if the current selection contains any ITN range.
|
|
// If it contains ITN range, we need to change the start and num of elements if
|
|
// the start element or end element is inside an ITN range.
|
|
|
|
BOOL fInsideITN;
|
|
ULONG ulITNStart, ulITNNumElem;
|
|
ULONG ulEndElem;
|
|
|
|
ulEndElem = i; // Current end element
|
|
|
|
if ( i > ulEnd )
|
|
ulEndElem = ulEnd;
|
|
|
|
fInsideITN = pResWrap->_CheckITNForElement(NULL, *pulStart, &ulITNStart, &ulITNNumElem, NULL );
|
|
|
|
if ( fInsideITN && (ulITNStart < *pulStart) )
|
|
*pulStart = ulITNStart;
|
|
|
|
fInsideITN = pResWrap->_CheckITNForElement(NULL, ulEndElem, &ulITNStart, &ulITNNumElem, NULL );
|
|
|
|
if ( fInsideITN && ulEndElem < (ulITNStart + ulITNNumElem - 1) )
|
|
ulEndElem = ulITNStart + ulITNNumElem - 1;
|
|
|
|
*pulcElem = ulEndElem - *pulStart + 1;
|
|
|
|
TraceMsg(TF_GENERAL, "Final Best Range: start=%d num=%d", *pulStart, *pulcElem);
|
|
|
|
}
|
|
|
|
}
|
|
CoTaskMemFree( pPhrases );
|
|
|
|
if ( ulNumElements > 0 )
|
|
{
|
|
Assert(cpNewRange);
|
|
Assert(ppBestPropRange);
|
|
*ppBestPropRange = cpNewRange;
|
|
|
|
(*ppBestPropRange)->AddRef();
|
|
}
|
|
else
|
|
hr = S_FALSE;
|
|
}
|
|
SafeRelease(pResWrap);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
//
|
|
// CCandUIFilterEventSink
|
|
//
|
|
//
|
|
STDMETHODIMP CCandUIFilterEventSink::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_ITfCandUIAutoFilterEventSink))
|
|
{
|
|
*ppvObj = SAFECAST(this, CCandUIFilterEventSink *);
|
|
}
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CCandUIFilterEventSink::AddRef(void)
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CCandUIFilterEventSink::Release(void)
|
|
{
|
|
long cr;
|
|
|
|
cr = --m_cRef;
|
|
Assert(cr >= 0);
|
|
|
|
if (cr == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
HRESULT CCandUIFilterEventSink::OnFilterEvent(CANDUIFILTEREVENT ev)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Temporally comment out the below code to fix bug 4777.
|
|
//
|
|
// To fully support the specification of filter feature, we need to change more code
|
|
// in SetFilterString( ) to use the correct parent range in current document so that the filter
|
|
// string is injected to a correct position.
|
|
//
|
|
// We also want to change code to restore the original document text when the canidate UI is
|
|
// cancelled.
|
|
//
|
|
//
|
|
|
|
// if (ev == CANDUIFEV_UPDATED)
|
|
if ( ev == CANDUIFEV_NONMATCH )
|
|
{
|
|
// When we got non-matching notification, we need to inject the previous filter string to the document.
|
|
if (m_pfnReconv)
|
|
{
|
|
Assert(m_pfnReconv);
|
|
Assert(m_pfnReconv->m_pImx);
|
|
|
|
ESDATA esData;
|
|
|
|
memset(&esData, 0, sizeof(ESDATA));
|
|
esData.pUnk = (IUnknown *)m_pCandUI;
|
|
m_pfnReconv->m_pImx->_RequestEditSession(ESCB_UPDATEFILTERSTR,TF_ES_READWRITE, &esData, m_pic);
|
|
}
|
|
}
|
|
|
|
return hr; // looks like S_OK is expected anyways
|
|
}
|
|
|
|
/* this filter event is no longer used.
|
|
|
|
HRESULT CCandUIFilterEventSink::OnAddCharToFilterStringEvent(CANDUIFILTEREVENT ev, WCHAR wch, int nItemVisible, BOOL *bEten)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( (bEten == NULL) || (ev != CANDUIFEV_ADDCHARTOFILTER))
|
|
return E_INVALIDARG;
|
|
|
|
*bEten = FALSE;
|
|
|
|
if ( nItemVisible == 0 )
|
|
{
|
|
if ( (wch <= L'9') && (wch >= L'1') )
|
|
{
|
|
// we need to select the speified candidate text.
|
|
// if candidate UI is open, we need to select the right alternate.
|
|
|
|
if ( m_pCandUI )
|
|
{
|
|
ULONG ulIndex;
|
|
|
|
ulIndex = wch - L'0';
|
|
|
|
m_pCandUI->ProcessCommand(CANDUICMD_SELECTLINE, ulIndex);
|
|
}
|
|
*bEten = TRUE;
|
|
}
|
|
else if ( wch == L' ' )
|
|
{
|
|
if ( m_pCandUI )
|
|
{
|
|
m_pCandUI->ProcessCommand(CANDUICMD_MOVESELNEXT, 0);
|
|
}
|
|
*bEten = TRUE;
|
|
}
|
|
|
|
}
|
|
return hr;
|
|
|
|
}
|
|
*/
|