// // 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 #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; } 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; ipwszAltText = 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 cpServicePrv; CComPtr 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 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; xpwszAltText); 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 cpServicePrv; CComPtr 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; ulRepCountcReplacements; 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 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 cpProp ; HRESULT hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp); if (SUCCEEDED(hr) && cpProp) { CComPtr 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 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 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 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 cpPropRange; hr = cpProp->FindRange(ec, pRange, &cpPropRange, TF_ANCHOR_START); CComPtr 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 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 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 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 cpProp; hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp); if (S_OK == hr) { CComPtr cpPropRange; CComPtr 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 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 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 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 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 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 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 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 cpPropRange ; CComPtr 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 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 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 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 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 cpServicePrv; CComPtr 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 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; } */