Leaked source code of windows server 2003
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

//
// 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;
}
*/