// // Dictation.cpp // // This file contains functions related to dictation mode handling. // // // They are moved from sapilayr.cpp #include "private.h" #include "globals.h" #include "sapilayr.h" const GUID *s_KnownModeBias[] = { &GUID_MODEBIAS_NUMERIC }; //+--------------------------------------------------------------------------- // // CSapiIMX::InjectText // // synopsis - recieve text from ISpTask and insert it to the current selection // // //---------------------------------------------------------------------------- HRESULT CSapiIMX::InjectText(const WCHAR *pwszRecognized, LANGID langid, ITfContext *pic) { if ( pwszRecognized == NULL ) return E_INVALIDARG; ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pData = (void *)pwszRecognized; esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR); esData.lData1 = (LONG_PTR)langid; return _RequestEditSession(ESCB_PROCESSTEXT, TF_ES_READWRITE, &esData, pic); } //+--------------------------------------------------------------------------- // // CSapiIMX::InjectTextWithoutOwnerID // // synopsis - inject text to the clients doc same way InjectText does but // with GUID_PROP_TEXTOWNER cleared out // // //---------------------------------------------------------------------------- HRESULT CSapiIMX::InjectTextWithoutOwnerID(const WCHAR *pwszRecognized, LANGID langid) { if ( pwszRecognized == NULL ) return E_INVALIDARG; ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pData = (void *)pwszRecognized; esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR); esData.lData1 = (LONG_PTR)langid; return _RequestEditSession(ESCB_PROCESSTEXT_NO_OWNERID, TF_ES_READWRITE, &esData); } //+--------------------------------------------------------------------------- // // CSapiIMX::HRESULT InjectSpelledText // // synopsis - inject spelled text to the clients doc // // //---------------------------------------------------------------------------- HRESULT CSapiIMX::InjectSpelledText(WCHAR *pwszText, LANGID langid, BOOL fOwnerId) { if ( pwszText == NULL ) return E_INVALIDARG; ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pData = (void *)pwszText; esData.uByte = (wcslen(pwszText)+1) * sizeof(WCHAR); esData.lData1 = (LONG_PTR)langid; esData.fBool = fOwnerId; return _RequestEditSession(ESCB_INJECT_SPELL_TEXT, TF_ES_READWRITE, &esData); } //+--------------------------------------------------------------------------- // // CSapiIMX::InjectModebiasText // // synopsis - recieve ModeBias text from ISpTask and insert it to the current // selection // //---------------------------------------------------------------------------- HRESULT CSapiIMX::InjectModebiasText(const WCHAR *pwszRecognized, LANGID langid) { if ( pwszRecognized == NULL ) return E_INVALIDARG; ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pData = (void *)pwszRecognized; esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR); esData.lData1 = (LONG_PTR)langid; return _RequestEditSession(ESCB_PROCESS_MODEBIAS_TEXT, TF_ES_READWRITE, &esData); } //+--------------------------------------------------------------------------+ // // CSapiIMX::_ProcessModebiasText // //+--------------------------------------------------------------------------+ HRESULT CSapiIMX::_ProcessModebiasText(TfEditCookie ec, WCHAR *pwszText, LANGID langid, ITfContext *picCaller) { HRESULT hr = E_FAIL; ITfContext *pic = NULL; if (!picCaller) { GetFocusIC(&pic); } else { pic = picCaller; } hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE); if (picCaller == NULL) { SafeRelease(pic); } // Before we clear the saved ip range, we need to treat this current ip as last // saved ip range if current ip is selected by end user SaveLastUsedIPRange( ); SaveIPRange(NULL); return hr; } //+--------------------------------------------------------------------------- // // CSapiIMX::InjectFeedbackUI // // synopsis - insert dotted bar to a doc for the length of cch // // //---------------------------------------------------------------------------+ HRESULT CSapiIMX::InjectFeedbackUI(const GUID attr, LONG cch) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pData = (void *)&attr; esData.uByte = sizeof(GUID); esData.lData1 = (LONG_PTR)cch; return _RequestEditSession(ESCB_FEEDBACKUI, TF_ES_READWRITE, &esData); } //+--------------------------------------------------------------------------- // // CSapiIMX::EraseFeedbackUI // // synopsis - cleans up the feedback UI // GUID - specifies which feedback UI bar to erase // //---------------------------------------------------------------------------+ HRESULT CSapiIMX::EraseFeedbackUI() { if ( S_OK == IsActiveThread()) { return _RequestEditSession(ESCB_KILLFEEDBACKUI, TF_ES_ASYNC|TF_ES_READWRITE, NULL); } return S_OK; } //+--------------------------------------------------------------------------+ // // CSapiIMX::__AddFeedbackUI // //+--------------------------------------------------------------------------+ HRESULT CSapiIMX::_AddFeedbackUI(TfEditCookie ec, ColorType ct, LONG cch) { HRESULT hr = E_FAIL; ITfContext *pic; // // distinguish unaware applications from cicero aware apps // GUID attr = ((ct == DA_COLOR_AWARE) ? GUID_ATTR_SAPI_GREENBAR : GUID_ATTR_SAPI_GREENBAR2); if (cch > 0) { WCHAR *pwsz = (WCHAR *)cicMemAlloc((cch + 1)*sizeof(WCHAR)); if (pwsz) { for (int i = 0; i < cch; i++ ) pwsz[i] = L'.'; pwsz[i] = L'\0'; if (GetFocusIC(&pic)) { // When add feedback UI, we should not change current doc's reco result property store // so set fPreserveResult as TRUE. // Only when the final text is injected to the document, the property store will be // updated. hr = _ProcessTextInternal(ec, pwsz, attr, GetUserDefaultLangID(), pic, TRUE); SafeRelease(pic); } cicMemFree(pwsz); } } return hr; } //+--------------------------------------------------------------------------+ // // CSapiIMX::_ProcessText // //+--------------------------------------------------------------------------+ HRESULT CSapiIMX::_ProcessText(TfEditCookie ec, WCHAR *pwszText, LANGID langid, ITfContext *picCaller) { HRESULT hr = E_FAIL; ITfContext *pic = NULL; if (!picCaller) { GetFocusIC(&pic); } else { pic = picCaller; } hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE); if (picCaller == NULL) { SafeRelease(pic); } return hr; } //+--------------------------------------------------------------------------+ // // CSapiIMX::_ProcessTextInternal // // common lower level routine for injecting text to docuement // //+--------------------------------------------------------------------------+ HRESULT CSapiIMX::_ProcessTextInternal(TfEditCookie ec, WCHAR *pwszText, GUID input_attr, LANGID langid, ITfContext *pic, BOOL fPreserveResult, BOOL fSpelling) { HRESULT hr = E_FAIL; if (pic) { BOOL fPureCiceroIC; fPureCiceroIC = _IsPureCiceroIC(pic); CDocStatus ds(pic); if (ds.IsReadOnly()) return S_OK; _fEditing = TRUE; // here we compare if the current selection is // equal to the saved IP range. If they are equal, // it means that the user has not moved the IP since // the first hypothesis arrived. In this case we'll // update the selection after injecting text, and // invalidate the saved IP. // BOOL fIPIsSelection = FALSE; // by default CComPtr cpInsertionPoint; if ( cpInsertionPoint = GetSavedIP( ) ) { // this is trying to determine // if the saved IP was on this context. // if not we just ignore that CComPtr cpic; hr = cpInsertionPoint->GetContext(&cpic); if (S_OK != hr || cpic != pic) { cpInsertionPoint.Release(); } } if (cpInsertionPoint) { CComPtr cpSelection; hr = GetSelectionSimple(ec, pic, &cpSelection); if (SUCCEEDED(hr)) { hr = cpSelection->IsEqualStart(ec, cpInsertionPoint, TF_ANCHOR_START, &fIPIsSelection); } } else { // if there is no saved IP, selection is an IP fIPIsSelection = TRUE; hr = GetSelectionSimple(ec, pic, &cpInsertionPoint); } if (hr == S_OK) { // finalize the previous input for now // if this is either feedback UI or alternate selection // no need to finalize // // Only for AIMM app or CUAS app, // finalize the previous dictated phrase here. // // For full Cicero aware app, it is better to finalize the composition // after this dictated text is injected to the document. // if (!fPureCiceroIC && !fPreserveResult && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT)) { _FinalizePrevComp(ec, pic, cpInsertionPoint); } ITfProperty *pProp = NULL; // now inject text if (SUCCEEDED(hr)) { // Just check with the app in case it wants to modify // the range. // BOOL fInsertOk; hr = cpInsertionPoint->AdjustForInsert(ec, wcslen(pwszText), &fInsertOk); if (S_OK == hr && fInsertOk) { // start a composition here if we haven't already _CheckStartComposition(ec, cpInsertionPoint); // protect the reco property while we modify the text // memo: we might want to preserve the original property instead // of the current property for LM lattice info We'll check // back this later (RTM) // m_fAcceptRecoResultTextUpdates = fPreserveResult; CRecoResultWrap *pRecoWrapOrg = NULL; ITfRange *pPropRange = NULL; ITfProperty *pProp_SAPIRESULT = NULL; if (fPreserveResult == TRUE) { if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &pProp_SAPIRESULT))) { // save out the result data // hr = _PreserveResult(ec, cpInsertionPoint, pProp_SAPIRESULT, &pRecoWrapOrg, &pPropRange); } if (S_OK != hr) pRecoWrapOrg = NULL; } if ( SUCCEEDED(hr) ) { // set the text hr = cpInsertionPoint->SetText(ec, 0, pwszText, -1); // prwOrg holds a prop data if not NULL if (S_OK == hr && fPreserveResult == TRUE && pPropRange) { hr = _RestoreResult(ec, pPropRange, pProp_SAPIRESULT, pRecoWrapOrg); } } SafeReleaseClear(pPropRange); SafeReleaseClear(pProp_SAPIRESULT); SafeRelease(pRecoWrapOrg); m_fAcceptRecoResultTextUpdates = FALSE; } } // // set attribute range, use custom prop to mark speech text // if (SUCCEEDED(hr)) { if (IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT)) { hr = pic->GetProperty(GUID_PROP_SAPI_DISPATTR, &pProp); } else { hr = pic->GetProperty(GUID_PROP_ATTRIBUTE, &pProp); } } ITfRange *pAttrRange = NULL; if (S_OK == hr) { // when we insert feedback UI text, we expect the prop // range (attribute range) to be extended. // if we are attaching our custom GUID_PROP_SAPI_DISPATTR // property, we'll attach it phrase by phrase // if (!IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT)) { hr = _FindPropRange(ec, pProp, cpInsertionPoint, &pAttrRange, input_attr, TRUE); } // // findproprange can return S_FALSE when there's no property yet // if (SUCCEEDED(hr) && !pAttrRange) { hr = cpInsertionPoint->Clone(&pAttrRange); } } if (S_OK == hr && pAttrRange) { SetGUIDPropertyData(&_libTLS, ec, pProp, pAttrRange, input_attr); } // // one more prop stuff for text owner id to fix // problem with Japanese spelling // if (S_OK == hr && fSpelling && !_MasterLMEnabled()) { CComPtr cpPropTextOwner; hr = pic->GetProperty(GUID_PROP_TEXTOWNER, &cpPropTextOwner); if (S_OK == hr) { SetGUIDPropertyData(&_libTLS, ec, cpPropTextOwner, pAttrRange, GUID_NULL); } } SafeRelease(pAttrRange); SafeRelease(pProp); // // setup langid property // _SetLangID(ec, pic, cpInsertionPoint, langid); // move the caret if (fIPIsSelection) { cpInsertionPoint->Collapse(ec, TF_ANCHOR_END); SetSelectionSimple(ec, pic, cpInsertionPoint); } // Finalize the composition object here for Cicero aware app. if ((hr == S_OK) && fPureCiceroIC && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT)) { _KillFocusRange(ec, pic, NULL, _tid); } } // If candidate UI is open, we need to close it now. CloseCandUI( ); _fEditing = FALSE; } // Finally, notify stage process if we are the stage speech tip instance. if (m_fStageTip && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT) && fPreserveResult == FALSE) { SetCompartmentDWORD(_tid, _tim, GUID_COMPARTMENT_SPEECH_STAGEDICTATION, 1, FALSE); } return hr; } // // _ProcessSpelledText // // Call back function for edit session ESCB_INJECT_SPELL_TEXT // // HRESULT CSapiIMX::_ProcessSpelledText(TfEditCookie ec, ITfContext *pic, WCHAR *pwszText, LANGID langid, BOOL fOwnerId) { HRESULT hr = S_OK; CComPtr cpTextRange; CComPtr cpCurIP; if ( !pic || !pwszText ) return E_INVALIDARG; // Keep the current range. cpCurIP = GetSavedIP( ); if ( !cpCurIP ) GetSelectionSimple(ec, pic, &cpCurIP); // We want to clone current ip so that it would not be changed // after _ProcessTextInternal( ) is called. // if ( cpCurIP ) cpCurIP->Clone(&cpTextRange); if ( !cpTextRange ) return E_FAIL; // check if the current selection or empty ip is inside a middle of English word. for English only. BOOL fStartAnchorInMidWord = FALSE; // Check if the start anchor of pTextRange is in a middle of word BOOL fEndAnchorInMidWord = FALSE; // Check if the end anchor of pTextRange is in a middle of word. // When fStartAnchorInMidWord is TRUE, we don't add extra space between this text range and the previous range. // When fEndAnchoInMidWord is TRUE, we remove the trailing space in this text range. if ( langid == 0x0409 ) { WCHAR szSurrounding[3]=L" "; LONG cch; CComPtr cpClonedRange; hr = cpTextRange->Clone(&cpClonedRange); if ( hr == S_OK ) hr = cpClonedRange->Collapse(ec, TF_ANCHOR_START); if ( hr == S_OK ) hr = cpClonedRange->ShiftStart(ec, -1, &cch, NULL); if (hr == S_OK && cch != 0) hr = cpClonedRange->ShiftEnd(ec, 1, &cch, NULL); if ( hr == S_OK && cch != 0 ) hr = cpClonedRange->GetText(ec, 0, szSurrounding, 2, (ULONG *)&cch); if ( hr == S_OK && cch > 0 ) { if ( iswalnum(szSurrounding[0]) && iswalnum(szSurrounding[1])) fStartAnchorInMidWord = TRUE; } szSurrounding[0] = szSurrounding[1]=L' '; cpClonedRange.Release( ); hr = cpTextRange->Clone(&cpClonedRange); if ( hr == S_OK ) hr = cpClonedRange->Collapse(ec, TF_ANCHOR_END); if ( hr == S_OK ) hr = cpClonedRange->ShiftStart(ec, -1, &cch, NULL); if (hr == S_OK && cch != 0) hr = cpClonedRange->ShiftEnd(ec, 1, &cch, NULL); if ( hr == S_OK && cch != 0 ) hr = cpClonedRange->GetText(ec, 0, szSurrounding, 2, (ULONG *)&cch); if ( hr == S_OK && cch == 2 ) { if ( iswalnum(szSurrounding[0]) && iswalnum(szSurrounding[1]) ) fEndAnchorInMidWord = TRUE; } } // Inject text with or without owner id according to fOwnerId parameter // this is a final text injection, we don't want to preserve the speech property data // possible divided or shrinked by this text injection. // hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE, !fOwnerId); if ( hr == S_OK && !fOwnerId) { BOOL fConsumeLeadSpaces = FALSE; ULONG ulNumTrailSpace = 0; if ( iswcntrl(pwszText[0]) || iswpunct(pwszText[0]) ) fConsumeLeadSpaces = TRUE; for ( LONG i=wcslen(pwszText)-1; i > 0; i-- ) { if ( pwszText[i] == L' ' ) ulNumTrailSpace++; else break; } hr = _ProcessSpaces(ec, pic, cpTextRange, fConsumeLeadSpaces, ulNumTrailSpace, langid, fStartAnchorInMidWord, fEndAnchorInMidWord); } return hr; } // // Handle the spaces after the recogized text is injected to the document. // // The handling includes below cases: // // Consume the leading spaces. // Remove the possible spaces after the injected text. English Only // Add a space before the injected text if necessary. English Only. // HRESULT CSapiIMX::HandleSpaces(ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements, ITfRange *pTextRange, LANGID langid) { HRESULT hr = E_FAIL; if (pResult && pTextRange) { BOOL fConsumeLeadSpaces = FALSE; ULONG ulNumTrailSpace = 0; SPPHRASE *pPhrase = NULL; // Check if the first element of the pResult wants to consume the leading spaces // (the trailing spaces in the last phrase) in the documenet. // // If the disp attrib bit is set as SPAF_CONSUME_LEADING_SPACES, means it wants to consume // all the leading spaces in the document. // hr = S_OK; if ( _NeedRemovingSpaceForPunctation( ) ) { hr = pResult->GetPhrase(&pPhrase); if ( hr == S_OK ) { ULONG cElements = 0; BYTE bDispAttr; cElements = pPhrase->Rule.ulCountOfElements; if ( cElements >= (ulStartElement + ulNumElements) ) { bDispAttr = pPhrase->pElements[ulStartElement].bDisplayAttributes; if ( bDispAttr & SPAF_CONSUME_LEADING_SPACES ) fConsumeLeadSpaces = TRUE; bDispAttr = pPhrase->pElements[ulStartElement + ulNumElements - 1].bDisplayAttributes; if ( bDispAttr & SPAF_ONE_TRAILING_SPACE ) ulNumTrailSpace = 1; else if ( bDispAttr & SPAF_TWO_TRAILING_SPACES ) ulNumTrailSpace = 2; } } if (pPhrase) CoTaskMemFree(pPhrase); } if ( hr == S_OK ) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.lData1 = (LONG_PTR)langid; esData.lData2 = (LONG_PTR)ulNumTrailSpace; esData.fBool = fConsumeLeadSpaces; esData.pRange = pTextRange; hr = _RequestEditSession(ESCB_HANDLESPACES, TF_ES_READWRITE, &esData); } } return hr; } // // CSapiIMX::AttachResult // // attaches the result object and keep it *alive* // until the property is discarded // HRESULT CSapiIMX::AttachResult(ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements) { HRESULT hr = E_FAIL; if (pResult) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.lData1 = (LONG_PTR)ulStartElement; esData.lData2 = (LONG_PTR)ulNumElements; esData.pUnk = (IUnknown *)pResult; hr = _RequestEditSession(ESCB_ATTACHRECORESULTOBJ, TF_ES_READWRITE, &esData); } return hr; } // // CSapiIMX::_GetSpaceRangeBeyondText // // Get space range beyond the injected text in the document. // fBefore is TRUE, Get the space range to contain spaces between // the previous word and start anchor of the TextRange. // fBefore is FALSE,Get space range to contains spaces between // the end anchor of the TextRange and next word. // // pulNum receives the real space number. // pfRealTextBeyond indicates if there is real text before or after the text range. // // Caller is responsible to release *ppSpaceRange. // HRESULT CSapiIMX::_GetSpaceRangeBeyondText(TfEditCookie ec, ITfRange *pTextRange, BOOL fBefore, ITfRange **ppSpaceRange, BOOL *pfRealTextBeyond) { HRESULT hr = S_OK; CComPtr cpSpaceRange; LONG cch; if ( !pTextRange || !ppSpaceRange ) return E_INVALIDARG; *ppSpaceRange = NULL; if ( pfRealTextBeyond ) *pfRealTextBeyond = FALSE; hr = pTextRange->Clone(&cpSpaceRange); if ( hr == S_OK ) { hr = cpSpaceRange->Collapse(ec, fBefore ? TF_ANCHOR_START : TF_ANCHOR_END); } if ( hr == S_OK ) { if ( fBefore ) hr = cpSpaceRange->ShiftStart(ec, MAX_CHARS_FOR_BEYONDSPACE * (-1), &cch, NULL); else hr = cpSpaceRange->ShiftEnd(ec, MAX_CHARS_FOR_BEYONDSPACE, &cch, NULL); } if ( (hr == S_OK) && (cch != 0 ) ) { // There are more characters beyond the text range. // Determine the real number of spaces in the guessed range. // // if fBefore TRUE, search the number from end to start anchor. // if fBefore FASLE, search the number from start to end anchor. WCHAR *pwsz = NULL; LONG lSize = cch; LONG lNumSpaces = 0; if (cch < 0) lSize = cch * (-1); pwsz = new WCHAR[lSize + 1]; if ( pwsz ) { hr = cpSpaceRange->GetText(ec, 0, pwsz, lSize, (ULONG *)&cch); if ( hr == S_OK) { pwsz[cch] = L'\0'; // calculate the number of trailing or prefix spaces in this range. BOOL bSearchDone = FALSE; ULONG iStart; if ( fBefore ) iStart = cch - 1; // Start from the end anchor to Start Anchor. else iStart = 0; // Start from Start Anchor to End Anchor. while ( !bSearchDone ) { if ((pwsz[iStart] != L' ') && (pwsz[iStart] != L'\t')) { bSearchDone = TRUE; if ( pwsz[iStart] > 0x20 && pfRealTextBeyond) *pfRealTextBeyond = TRUE; break; } else lNumSpaces ++; if ( fBefore ) { if ( (long)iStart <= 0 ) bSearchDone = TRUE; else iStart --; } else { if ( iStart >= (ULONG)cch - 1 ) bSearchDone = TRUE; else iStart ++; } } } delete[] pwsz; if ( (hr == S_OK) && (lNumSpaces > 0)) { // Shift the range to cover only spaces. LONG NonSpaceNum; NonSpaceNum = cch - lNumSpaces; if ( fBefore ) hr = cpSpaceRange->ShiftStart(ec, NonSpaceNum, &cch, NULL); else hr = cpSpaceRange->ShiftEnd(ec, NonSpaceNum * (-1), &cch, NULL); // Return this cpSpaceRange to the caller. // Caller is responsible for releasing this object. if ( hr == S_OK ) hr = cpSpaceRange->Clone(ppSpaceRange); } } else hr = E_OUTOFMEMORY; } return hr; } // // CSapiIMX::_ProcessTrailingSpace // // If the next phrase wants to consume leading space, // we want to remove all the trailing spaces in this text range and the spaces // between this range and next text range. // This is for all languages. // HRESULT CSapiIMX::_ProcessTrailingSpace(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, ULONG ulNumTrailSpace) { HRESULT hr = S_OK; CComPtr cpNextRange; CComPtr cpSpaceRange; // Space Range between this range and next text range. BOOL fHasNextText = FALSE; CComPtr cpTrailSpaceRange; LONG cch; if ( !pTextRange ) return E_INVALIDARG; hr = pTextRange->Clone(&cpTrailSpaceRange); if (hr == S_OK) hr = cpTrailSpaceRange->Collapse(ec, TF_ANCHOR_END); // Generate the real Trailing Space Range if (hr == S_OK && ulNumTrailSpace > 0) hr = cpTrailSpaceRange->ShiftStart(ec, (LONG)ulNumTrailSpace * (-1), &cch, NULL); // Get the spaces between this range and possible next text range. if ( hr == S_OK ) hr = _GetSpaceRangeBeyondText(ec, pTextRange, FALSE, &cpSpaceRange); // if we found the space range, the trailing space range should also include this range. if ( hr == S_OK && cpSpaceRange ) hr = cpTrailSpaceRange->ShiftEndToRange(ec, cpSpaceRange, TF_ANCHOR_END); // Determine if there is Next Text range after this cpTrailSpaceRange. if (hr == S_OK) { hr = cpTrailSpaceRange->Clone(&cpNextRange); if ( hr == S_OK ) hr = cpNextRange->Collapse(ec, TF_ANCHOR_END); cch = 0; if ( hr == S_OK ) hr = cpNextRange->ShiftEnd(ec, 1, &cch, NULL); if ( hr == S_OK && cch != 0 ) fHasNextText = TRUE; } if (hr == S_OK && fHasNextText && cpNextRange) { BOOL fNextRangeConsumeSpace = FALSE; BOOL fAddOneSpace = FALSE; // this is only for Hyphen handling, // if it is TRUE, a trailing space is required // to append. // so that new text could be like A - B. WCHAR wszText[4]; hr = cpNextRange->GetText(ec, 0, wszText, 1, (ULONG *)&cch); if ((hr == S_OK) && ( iswcntrl(wszText[0]) || iswpunct(wszText[0]) )) { // if the character is a control character or punctuation character, // it means it want to consume the previous spaces. fNextRangeConsumeSpace = TRUE; if ((wszText[0] == L'-') || (wszText[0] == 0x2013)) // Specially handle Hyphen character. { // If the next text is "-xxx", there should be no space between // this range and next range. // If the next text is "- xxx", there should be a space between // this range and next range, the text would be: "nnn - xxx" HRESULT hret; hret = cpNextRange->ShiftEnd(ec, 1, &cch, NULL); if ( hret == S_OK && cch > 0 ) { hret = cpNextRange->GetText(ec, 0, wszText, 2, (ULONG *)&cch); if ( hret == S_OK && cch == 2 && wszText[1] == L' ') fAddOneSpace = TRUE; } } } if ( fNextRangeConsumeSpace ) { _CheckStartComposition(ec, cpTrailSpaceRange); if ( !fAddOneSpace ) hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0); else hr = cpTrailSpaceRange->SetText(ec, 0, L" ", 1); } } return hr; } // // CSapiIMX::_ProcessLeadingSpaces // // If this phrase wants to consume leading spaces, all the spaces before this phrase // must be removed. // if the phrase doesn't want to consume leading spaces, and there is no space between // this phrase and previous phrase for English case, leading space is required to add // between these two phrases. // HRESULT CSapiIMX::_ProcessLeadingSpaces(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, BOOL fConsumeLeadSpaces, LANGID langid, BOOL fStartInMidWord) { HRESULT hr = S_OK; if (!pTextRange || !pic) return E_INVALIDARG; // Handle Consuming leading Spaces. if (fConsumeLeadSpaces ) { CComPtr cpLeadSpaceRange; hr = _GetSpaceRangeBeyondText(ec, pTextRange, TRUE, &cpLeadSpaceRange); if ( hr == S_OK && cpLeadSpaceRange ) { // Kill all the trailing spaces in the range. // start a composition here if we haven't already _CheckStartComposition(ec, cpLeadSpaceRange); hr = cpLeadSpaceRange->SetText(ec, 0, NULL, 0); } } // Specially handle some other space cases for English if ((hr == S_OK) && (langid == 0x0409)) { // If this phrase doesn't consume the leading space, and // there is no any spaces between this text range and a real previous text word. // we need to add one space here. // if this is a spelled text, and the start anchor of selection or ip is inside // of a word, don't add extra leading space. // if ( hr == S_OK && !fConsumeLeadSpaces && !fStartInMidWord) { CComPtr cpLeadSpaceRange; BOOL fRealTextInPreviousWord = FALSE; hr = _GetSpaceRangeBeyondText(ec, pTextRange, TRUE, &cpLeadSpaceRange,&fRealTextInPreviousWord); if ( hr == S_OK && !cpLeadSpaceRange && fRealTextInPreviousWord ) { // // Specially handle the hyphen case for bug 468907 // // if the previous text is "x-", this text is "y", // the final text should be like "x-y". // we should not add one space in this case. // // if the previous text is "x -", the final text would be "x - y" // the extra space is necessary. BOOL fAddExtraSpace = TRUE; CComPtr cpPrevTextRange; WCHAR wszTrailTextInPrevRange[3]; LONG cch; // Since previous text range does exist, ( fRealTextInPreviousWord is TRUE). // and there is no space between this range and previous range. // we can just rely on pTextRange to shift to previous range and get // its trail characters. ( last two characters, saved in wszTrailTextInPrevRange). hr = pTextRange->Clone(&cpPrevTextRange); if ( hr == S_OK ) hr = cpPrevTextRange->Collapse(ec, TF_ANCHOR_START); if ( hr == S_OK ) hr = cpPrevTextRange->ShiftStart(ec, -2, &cch, NULL); if ( hr == S_OK ) hr = cpPrevTextRange->GetText(ec, 0, wszTrailTextInPrevRange, 2, (ULONG *)&cch); if ( hr == S_OK && cch == 2 ) { if ( (wszTrailTextInPrevRange[0] != L' ') && ((wszTrailTextInPrevRange[1] == L'-') || (wszTrailTextInPrevRange[1] == 0x2013)) ) fAddExtraSpace = FALSE; } if ( fAddExtraSpace ) { hr = pTextRange->Clone(&cpLeadSpaceRange); if ( hr == S_OK ) hr = cpLeadSpaceRange->Collapse(ec, TF_ANCHOR_START); if ( hr == S_OK ) { // Insert one Space to this new empty range. _CheckStartComposition(ec, cpLeadSpaceRange); hr = cpLeadSpaceRange->SetText(ec, 0, L" ", 1); } } } } } return hr; } // // CSapiIMX::_ProcessSpaces // // Edit session callback function for ESCB_HANDLESPACES. // // HRESULT CSapiIMX::_ProcessSpaces(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, BOOL fConsumeLeadSpaces, ULONG ulNumTrailSpace, LANGID langid, BOOL fStartInMidWord, BOOL fEndInMidWord ) { HRESULT hr = S_OK; if (!pTextRange || !pic) return E_INVALIDARG; hr = _ProcessLeadingSpaces(ec, pic, pTextRange,fConsumeLeadSpaces, langid, fStartInMidWord); // Specially handle some other space cases for English if ((hr == S_OK) && (langid == 0x0409)) { // Remove all the unnecessary spaces between this text range and next word. CComPtr cpTrailSpaceRange; hr = _GetSpaceRangeBeyondText(ec, pTextRange, FALSE, &cpTrailSpaceRange); if ( hr == S_OK && cpTrailSpaceRange ) { _CheckStartComposition(ec, cpTrailSpaceRange); hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0); } } if ( (hr == S_OK) && fEndInMidWord ) { // This is spelled text. // EndAnchor is in middle of a word. // we just want to remove the trail spaces injected in this text range. if ( ulNumTrailSpace ) { CComPtr cpTrailSpaceRange; LONG cch; hr = pTextRange->Clone(&cpTrailSpaceRange); if (hr == S_OK) hr = cpTrailSpaceRange->Collapse(ec, TF_ANCHOR_END); // Generate the real Trailing Space Range if (hr == S_OK) hr = cpTrailSpaceRange->ShiftStart(ec, (LONG)ulNumTrailSpace * (-1), &cch, NULL); if ( hr == S_OK && cch != 0 ) { // Remove the spaces. _CheckStartComposition(ec, cpTrailSpaceRange); hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0); } if ( hr == S_OK ) ulNumTrailSpace = 0; } } // If the next phrase wants to consume leading space, // we want to remove all the trailing spaces in this text range. // This is for all languages. if ( hr == S_OK ) hr = _ProcessTrailingSpace(ec, pic, pTextRange, ulNumTrailSpace); return hr; } // // CSapiIMX::_ProcessRecoObject // // HRESULT CSapiIMX::_ProcessRecoObject(TfEditCookie ec, ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements) { HRESULT hr; ITfContext *pic; CComPtr cpInsertionPoint; if (!GetFocusIC(&pic)) { return E_OUTOFMEMORY; } _fEditing = TRUE; if (cpInsertionPoint = GetSavedIP()) { // this is trying to determine // if the saved IP was on this context. // if not we just ignore that CComPtr cpic; hr = cpInsertionPoint->GetContext(&cpic); if (S_OK != hr || cpic != pic) { cpInsertionPoint.Release(); } } // find range to attach property if (!cpInsertionPoint) { CComPtr cpSelection; if (GetSelectionSimple(ec, pic, &cpSelection) == S_OK) { cpInsertionPoint = cpSelection; // comptr addrefs } } if (cpInsertionPoint) { CComPtr cpRange; BOOL fPrSize = _FindPrevComp(ec, pic, cpInsertionPoint, &cpRange, GUID_ATTR_SAPI_INPUT); if (!fPrSize) { hr = E_FAIL; // we may need to assert here? goto pr_exit; } CComPtr cpProp; if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp))) { CComPtr cpResultStore; CPropStoreRecoResultObject *prps = new CPropStoreRecoResultObject(this, cpRange); if (!prps) { hr = E_OUTOFMEMORY; goto pr_exit; } // determine whether this partial result has an ITN SPPHRASE *pPhrase; ULONG ulNumOfITN = 0; hr = pResult->GetPhrase(&pPhrase); if (S_OK == hr) { const SPPHRASEREPLACEMENT *pRep = pPhrase->pReplacements; for (ULONG ul = 0; ul < pPhrase->cReplacements; ul++) { // review: we need to verify if this is really a correct way to determine // whether the ITN fits in the partial result // if (pRep->ulFirstElement >= ulStartElement && (pRep->ulFirstElement + pRep->ulCountOfElements) <= (ulStartElement + ulNumElements)) { ulNumOfITN ++; } pRep++; } ::CoTaskMemFree(pPhrase); } CRecoResultWrap *prw = new CRecoResultWrap(this, ulStartElement, ulNumElements, ulNumOfITN); if (prw) { hr = prw->Init(pResult); } else hr = E_OUTOFMEMORY; // set up the result data if (S_OK == hr) { // save text CComPtr cpRangeTemp; hr = cpRange->Clone(&cpRangeTemp); if (S_OK == hr) { long cch; TF_HALTCOND hc; WCHAR *psz; hc.pHaltRange = cpRange; hc.aHaltPos = TF_ANCHOR_END; hc.dwFlags = 0; cpRangeTemp->ShiftStart(ec, LONG_MAX, &cch, &hc); psz = new WCHAR[cch+1]; if (psz) { if ( S_OK == cpRange->GetText(ec, 0, psz, cch, (ULONG *)&cch)) { prw->m_bstrCurrentText = SysAllocString(psz); delete[] psz; } } } // Init the ITN Show State list in reco wrapper. prw->_InitITNShowState(TRUE, 0, 0); hr = prps->_InitFromResultWrap(prw); // this addref's } // get ITfPropertyStore interface if (SUCCEEDED(hr)) { hr = prps->QueryInterface(IID_ITfPropertyStore, (void **)&cpResultStore); SafeRelease(prps); } // set the property store for this range property if (hr == S_OK) { hr = cpProp->SetValueStore(ec, cpRange, cpResultStore); } if (_MasterLMEnabled()) { // set up the LM lattice store, only if reco result is given // CComPtr cpLMProp; if ( S_OK == hr && SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LMLATTICE, &cpLMProp))) { CPropStoreLMLattice *prpsLMLattice = new CPropStoreLMLattice(this); CComPtr cpLatticeStore; if (prpsLMLattice && prw) { hr = prpsLMLattice->_InitFromResultWrap(prw); } else hr = E_OUTOFMEMORY; if (S_OK == hr) { hr = prpsLMLattice->QueryInterface(IID_ITfPropertyStore, (void **)&cpLatticeStore); } if (S_OK == hr) { hr = cpLMProp->SetValueStore(ec, cpRange, cpLatticeStore); } SafeRelease(prpsLMLattice); } } SafeRelease(prw); } } pr_exit: _fEditing = FALSE; pic->Release(); return hr; } HRESULT CSapiIMX::_PreserveResult(TfEditCookie ec, ITfRange *pRange, ITfProperty *pProp, CRecoResultWrap **ppRecoWrap, ITfRange **ppPropRange) { HRESULT hr; ITfRange *pPropRange; CComPtr cpunk; Assert(ppPropRange); hr = pProp->FindRange(ec, pRange, &pPropRange, TF_ANCHOR_START); // retrieve the result data and addref it // if (SUCCEEDED(hr) && pPropRange) { hr = GetUnknownPropertyData(ec, pProp, pPropRange, &cpunk); *ppPropRange = pPropRange; // would be released at the caller // pPropRange->Release(); // get the result object, cpunk points to our wrapper object CComPtr cpServicePrv; CComPtr cpResult; CRecoResultWrap *pRecoWrapOrg = 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); } if (S_OK == hr) { hr = cpunk->QueryInterface(IID_PRIV_RESULTWRAP, (void **)&pRecoWrapOrg); } // Now Create a new RecoResult Wrapper based on the org wrapper's data. // Clone a new RecoWrapper. if ( S_OK == hr ) { CRecoResultWrap *pRecoWrapNew = NULL; ULONG ulStartElement, ulNumElements, ulNumOfITN; ulStartElement = pRecoWrapOrg->GetStart( ); ulNumElements = pRecoWrapOrg->GetNumElements( ); ulNumOfITN = pRecoWrapOrg->m_ulNumOfITN; pRecoWrapNew = new CRecoResultWrap(this, ulStartElement, ulNumElements, ulNumOfITN); if ( pRecoWrapNew ) { // Init from RecoResult SR object hr = pRecoWrapNew->Init(cpResult); if ( S_OK == hr ) { pRecoWrapNew->SetOffsetDelta( pRecoWrapOrg->_GetOffsetDelta( ) ); pRecoWrapNew->SetCharsInTrail( pRecoWrapOrg->GetCharsInTrail( ) ); pRecoWrapNew->SetTrailSpaceRemoved( pRecoWrapOrg->GetTrailSpaceRemoved( ) ); pRecoWrapNew->m_bstrCurrentText = SysAllocString((WCHAR *)pRecoWrapOrg->m_bstrCurrentText); // Update ITN show-state list . if ( ulNumOfITN > 0 ) { SPITNSHOWSTATE *pITNShowStateOrg; for ( ULONG iIndex=0; iIndexm_rgITNShowState.GetPtr(iIndex); if ( pITNShowStateOrg) { ULONG ulITNStart; ULONG ulITNNumElem; BOOL fITNShown; ulITNStart = pITNShowStateOrg->ulITNStart; ulITNNumElem = pITNShowStateOrg->ulITNNumElem; fITNShown = pITNShowStateOrg->fITNShown; pRecoWrapNew->_InitITNShowState(fITNShown, ulITNStart, ulITNNumElem ); } } // for } // if // Update Offset List if ( pRecoWrapOrg->IsElementOffsetIntialized( ) ) { ULONG ulOffsetNum; ULONG i; ULONG ulOffset; ulOffsetNum = pRecoWrapOrg->GetNumElements( ) + 1; for ( i=0; i < ulOffsetNum; i ++ ) { ulOffset = pRecoWrapOrg->_GetElementOffsetCch(ulStartElement + i ); pRecoWrapNew->_SetElementNewOffset(ulStartElement + i, ulOffset); } } } SafeRelease(pRecoWrapOrg); if ( ppRecoWrap ) *ppRecoWrap = pRecoWrapNew; } else hr = E_OUTOFMEMORY; } } return hr; } HRESULT CSapiIMX::_RestoreResult(TfEditCookie ec, ITfRange *pPropRange, ITfProperty *pProp, CRecoResultWrap *pRecoWrap) { Assert(m_pCSpTask); CPropStoreRecoResultObject *prps = new CPropStoreRecoResultObject(this, pPropRange); HRESULT hr; if (prps) { ITfPropertyStore *pps; // restore the result object prps->_InitFromResultWrap(pRecoWrap); // get ITfPropertyStore interface hr = prps->QueryInterface(IID_ITfPropertyStore, (void **)&pps); prps->Release(); // re-set the property store for this range property if (hr == S_OK) { hr = pProp->SetValueStore(ec, pPropRange, pps); pps->Release(); } } else hr = E_OUTOFMEMORY; return hr; } HRESULT CSapiIMX::_FinalizePrevComp(TfEditCookie ec, ITfContext *pic, ITfRange *pRange) // // the following code assumes single IP with no simaltanious SR going on // we always remove the feedback UI and focus range everytime we receive // SR result - mainly for demonstration purpose // { // kill the Feedback UI for the entire document HRESULT hr = _KillFeedbackUI(ec, pic, NULL); // also clear the focus range and its display attribute if (SUCCEEDED(hr)) { hr = _KillFocusRange(ec, pic, NULL, _tid); } return hr; } // // bogus: very similar to Finalize prev comp. Consolidate this! // // BOOL CSapiIMX::_FindPrevComp(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, ITfRange **ppRangeOut, GUID input_attr) { HRESULT hr = E_FAIL; ITfRange *pRangeTmp; LONG l; BOOL fEmpty; BOOL fRet = FALSE; // usual stuff pRange->Clone(&pRangeTmp); // set size to 0 pRangeTmp->Collapse(ec, TF_ANCHOR_START); // shift to the previous position pRangeTmp->ShiftStart(ec, -1, &l, NULL); ITfRange *pAttrRange; ITfProperty *pProp = NULL; if (SUCCEEDED(pic->GetProperty(GUID_PROP_SAPI_DISPATTR, &pProp))) { hr = _FindPropRange(ec, pProp, pRangeTmp, &pAttrRange, input_attr); if (S_OK == hr && pAttrRange) { TfGuidAtom attr; if (SUCCEEDED(GetGUIDPropertyData(ec, pProp, pAttrRange, &attr))) { if (IsEqualTFGUIDATOM(&_libTLS, attr, input_attr)) { hr = pAttrRange->Clone(ppRangeOut); } } pAttrRange->Release(); } pProp->Release(); } pRangeTmp->Release(); if (SUCCEEDED(hr) && *ppRangeOut) { (*ppRangeOut)->IsEmpty(ec, &fEmpty); fRet = !fEmpty; } return fRet; } // // CSapiIMX::_SetLangID // // synopsis - set langid for the given text range // HRESULT CSapiIMX::_SetLangID(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, LANGID langid) { BOOL fEmpty; HRESULT hr = E_FAIL; pRange->IsEmpty(ec, &fEmpty); if (!fEmpty) { // // make langid prop // ITfProperty *pProp = NULL; // set the attrib info if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LANGID, &pProp))) { hr = SetLangIdPropertyData(ec, pProp, pRange, langid); pProp->Release(); } } return hr; } // // CSapiIMX::_FindPropRange // // HRESULT CSapiIMX::_FindPropRange(TfEditCookie ec, ITfProperty *pProp, ITfRange *pRange, ITfRange **ppAttrRange, GUID input_attr, BOOL fExtend) { // set the attrib info ITfRange *pAttrRange = NULL; ITfRange *pRangeTmp; TfGuidAtom guidAttr = TF_INVALID_GUIDATOM; HRESULT hr; // LONG l; // set the attrib info pRange->Clone(&pRangeTmp); // There is no need to shiftstart to the left again when this function is called. // This function is called in two different places, one in FindPrevComp( ) and the // other is in ProcessTextInternal( ). FindPrevComp( ) has already shift the start // anchor to left by 1 already, we don't want to shift again, otherwise, if the phrase // contains only one char, it will not find the right prev-composition string. // In function ProcessTextInternal( ), shift start anchor to left is not really required. // // Remove the below two lines will fix cicero bug 3646 & 3649 // // pRangeTmp->Collapse(ec, TF_ANCHOR_START); // pRangeTmp->ShiftStart(ec, -1, &l, NULL); hr = pProp->FindRange(ec, pRangeTmp, &pAttrRange, TF_ANCHOR_START); if (S_OK == hr && pAttrRange) { hr = GetGUIDPropertyData(ec, pProp, pAttrRange, &guidAttr); } if (SUCCEEDED(hr)) { if (!IsEqualTFGUIDATOM(&_libTLS, guidAttr, input_attr)) { SafeReleaseClear(pAttrRange); } } if (fExtend) { if (pAttrRange) { pAttrRange->ShiftEndToRange(ec, pRange, TF_ANCHOR_END); } } *ppAttrRange = pAttrRange; SafeRelease(pRangeTmp); return hr; } HRESULT CSapiIMX::_DetectFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange) { BOOL fDetected; HRESULT hr = _KillOrDetectFeedbackUI(ec, pic, pRange, &fDetected); if (S_OK == hr) { if (fDetected) { hr = _RequestEditSession(ESCB_KILLFEEDBACKUI, TF_ES_ASYNC|TF_ES_READWRITE, NULL); } } return hr; } //+--------------------------------------------------------------------------- // // _KillFeedbackUI // // get rid of the green/red bar thing within the given range // //----------------------------------------------------------------------------+ HRESULT CSapiIMX::_KillFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange) { return _KillOrDetectFeedbackUI(ec, pic, pRange, NULL); } HRESULT CSapiIMX::_KillOrDetectFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, BOOL * pfDetection) { HRESULT hr; ITfProperty *pProp = NULL; ITfRange *pAttrRange = NULL; IEnumTfRanges *pEnumPr; if (pfDetection) *pfDetection = FALSE; CDocStatus ds(pic); if (ds.IsReadOnly()) return S_OK; if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_ATTRIBUTE, &pProp))) { hr = pProp->EnumRanges(ec, &pEnumPr, pRange); if (SUCCEEDED(hr)) { TfGuidAtom guidAttr; while( pEnumPr->Next(1, &pAttrRange, NULL) == S_OK ) { if (SUCCEEDED(GetAttrPropertyData(ec, pProp, pAttrRange, &guidAttr))) { if ( IsEqualTFGUIDATOM(&_libTLS, guidAttr, GUID_ATTR_SAPI_GREENBAR) || IsEqualTFGUIDATOM(&_libTLS, guidAttr, GUID_ATTR_SAPI_GREENBAR2) ) { if (pfDetection == NULL) { // we're not detecting the feedback UI // kill this guy ITfRange *pSel; if (SUCCEEDED(pAttrRange->Clone(&pSel))) { // Because we didn't change the speech property data while // feedback text were injected. // // Now, when the feedback is killed, we don't want to affect // the original speech property data either. // // set the below flag to prevent the speech property data updated // similar way as in feedback UI injection handling // m_fAcceptRecoResultTextUpdates = TRUE; pSel->SetText(ec, 0, NULL, 0); // CUAS application will not update the composition // while the feedback text is removed based on msctfime // current text update checking logic. // // Call SetSection( ) to forcelly update the edit record // of the selection status, and then make sure CUAS // update composition string successfully. // if ( !_IsPureCiceroIC(pic) ) SetSelectionSimple(ec, pic, pSel); pSel->Release(); m_fAcceptRecoResultTextUpdates = FALSE; } } else { *pfDetection = TRUE; } } } pAttrRange->Release(); } pEnumPr->Release(); } pProp->Release(); } return hr; } //+--------------------------------------------------------------------------- // // MakeResultString // //---------------------------------------------------------------------------- HRESULT CSapiIMX::MakeResultString(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, TfClientId tid, CSpTask *pCSpTask) { TraceMsg(TF_GENERAL, "MakeResultString is Called"); HRESULT hr = S_OK; if (pCSpTask != NULL) { AbortString(ec, pic, pCSpTask); } _KillFocusRange(ec, pic, NULL, tid); return hr; } //+--------------------------------------------------------------------------- // // AbortString // //---------------------------------------------------------------------------- HRESULT CSapiIMX::AbortString(TfEditCookie ec, ITfContext *pic, CSpTask *pCSpTask) { // We may consider not to kill the entire feedback UI // because SR happens at background and there can be multi- // range dictation going at once but for now, we just kill // them all for safety. Later on, we'll re-visit this // CSpTask::_StopInput kill the feedback UI. // Assert(pCSpTask); pCSpTask->_StopInput(); _KillFeedbackUI(ec, pic, NULL); return S_OK; } HRESULT CSapiIMX::_FinalizeComposition() { return _RequestEditSession(ESCB_FINALIZECOMP, TF_ES_READWRITE); } HRESULT CSapiIMX::FinalizeAllCompositions( ) { return _RequestEditSession(ESCB_FINALIZE_ALL_COMPS, TF_ES_READWRITE); } HRESULT CSapiIMX::_FinalizeAllCompositions(TfEditCookie ec, ITfContext *pic ) { HRESULT hr = E_FAIL; IEnumITfCompositionView *pEnumComp = NULL; ITfContextComposition *picc = NULL; ITfCompositionView *pCompositionView; ITfComposition *pComposition; CLSID clsid; CICPriv *picp; BOOL fHasOtherComp = FALSE; // When there is composition which is initialized and started // by other tips, ( especially by Keyboard tips), this variable // set to TRUE // // clear any sptip compositions over the range // if (pic->QueryInterface(IID_ITfContextComposition, (void **)&picc) != S_OK) goto Exit; if (picc->FindComposition(ec, NULL, &pEnumComp) != S_OK) goto Exit; picp = GetInputContextPriv(_tid, pic); while (pEnumComp->Next(1, &pCompositionView, NULL) == S_OK) { if (pCompositionView->GetOwnerClsid(&clsid) != S_OK) goto NextComp; if (!IsEqualCLSID(clsid, CLSID_SapiLayr)) { fHasOtherComp = TRUE; goto NextComp; } if (pCompositionView->QueryInterface(IID_ITfComposition, (void **)&pComposition) != S_OK) goto NextComp; // found a composition, terminate it pComposition->EndComposition(ec); pComposition->Release(); if (picp != NULL) { picp->_ReleaseComposition(); } NextComp: pCompositionView->Release(); } SafeRelease(picp); if ( fHasOtherComp ) { // Simulate VK_RETURN to terminate composition started by other tips. HandleKey( VK_RETURN ); } hr = S_OK; Exit: SafeRelease(picc); SafeRelease(pEnumComp); SaveLastUsedIPRange( ); SaveIPRange(NULL); return hr; } //+--------------------------------------------------------------------------- // // SaveCurrentIP // // synopsis: this is for recognition handler CSpTask to call when // The first hypothesis arrives // //+--------------------------------------------------------------------------- void CSapiIMX::SaveCurrentIP(TfEditCookie ec, ITfContext *pic) { CComPtr cpSel; HRESULT hr = GetSelectionSimple(ec, pic, (ITfRange **)&cpSel); if (SUCCEEDED(hr)) { SaveIPRange(cpSel); } } //+--------------------------------------------------------------------------- // // _SyncModeBiasWithSelection // // synopsis: obtain a read cookie to process selection API // //---------------------------------------------------------------------------+ HRESULT CSapiIMX::_SyncModeBiasWithSelection(ITfContext *pic) { return _RequestEditSession(ESCB_SYNCMBWITHSEL, TF_ES_READ|TF_ES_ASYNC, NULL, pic); } HRESULT CSapiIMX::_SyncModeBiasWithSelectionCallback(TfEditCookie ec, ITfContext *pic) { ITfRange *sel; if (S_OK == GetSelectionSimple(ec, pic, &sel)) { SyncWithCurrentModeBias(ec, sel, pic); sel->Release(); } return S_OK; } //+--------------------------------------------------------------------------- // // _GetRangeText // // synopsis: obtain a read cookie to process selection API // //---------------------------------------------------------------------------+ HRESULT CSapiIMX::_GetRangeText(ITfRange *pRange, DWORD dwFlgs, WCHAR *psz, ULONG *pulcch) { HRESULT hr = E_FAIL; Assert(pulcch); Assert(psz); Assert(pRange); CComPtr cpic; hr = pRange->GetContext(&cpic); if (S_OK == hr) { CSapiEditSession *pes = new CSapiEditSession(this, cpic); if (pes) { ULONG ulInitSize; ulInitSize = *pulcch; pes->_SetEditSessionData(ESCB_GETRANGETEXT, NULL, (UINT)(ulInitSize+1) * sizeof(WCHAR), (LONG_PTR)dwFlgs, (LONG_PTR)*pulcch); pes->_SetRange(pRange); cpic->RequestEditSession(_tid, pes, TF_ES_READ | TF_ES_SYNC, &hr); if ( SUCCEEDED(hr) ) { ULONG ulNewSize; ulNewSize = (ULONG)pes->_GetRetData( ); if ( ulNewSize > 0 && ulNewSize <= ulInitSize && pes->_GetPtrData( ) != NULL) { wcsncpy(psz, (WCHAR *)pes->_GetPtrData( ), ulNewSize); psz[ulNewSize] = L'\0'; } *pulcch = ulNewSize; } pes->Release( ); } } return hr; } //+--------------------------------------------------------------------------- // // _IsRangeEmpty // // synopsis: // //---------------------------------------------------------------------------+ BOOL CSapiIMX::_IsRangeEmpty(ITfRange *pRange) { CComPtr cpic; BOOL fEmpty = FALSE; pRange->GetContext(&cpic); if ( cpic ) { _RequestEditSession(ESCB_ISRANGEEMPTY, TF_ES_READ|TF_ES_SYNC, NULL, cpic, (LONG_PTR *)&fEmpty); } return fEmpty; } HRESULT CSapiIMX::_HandleHypothesis(CSpEvent &event) { HRESULT hr = E_FAIL; m_ulHypothesisNum ++; if ( (m_ulHypothesisNum % 3) != 1 ) { TraceMsg(TF_SAPI_PERF, "Discarded hypothesis %i.", m_ulHypothesisNum % 3); return S_OK; } // if it is under hypothesis processing, don't start a new edit session. if ( m_IsInHypoProcessing ) { TraceMsg(TF_SAPI_PERF, "It is under process for previous hypothesis"); return S_OK; } m_IsInHypoProcessing = TRUE; ISpRecoResult *pResult = event.RecoResult(); if (pResult) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pUnk = (IUnknown *)pResult; // Require it to be asynchronous to guarantee we don't get called before we have had change // to process any final recognition events from SAPI. Otherwise the hypothesis gets injected // immediately and then the final recognition tries to remove it which fails. hr = _RequestEditSession(ESCB_HANDLEHYPOTHESIS, TF_ES_ASYNC | TF_ES_READWRITE, &esData); } if ( FAILED(hr) ) { // Set flag to indicate hypothesis processing is finished. m_IsInHypoProcessing = FALSE; } // When hr is succeeded, including TF_S_ASYNC, the edit session function will be called, and // it will set the flag when the edit session function exits. return hr; } void CSapiIMX::_HandleHypothesis(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec) { // if there is a selection, do not inject // feedback UI // // save the current IP if we haven't done so if (m_pCSpTask->_GotReco()) { // Optimization and bugfix. We already have a reco and hence have no need to update // the feedback bar. AND if we do, it gets left in the document at dictation off and // voice commands since it gets altered immediately before an attempt to remove it which // then silently fails. // Set flag to indicate hypothesis processing is finished. m_IsInHypoProcessing = FALSE; return; } Assert(pic); CComPtr cpRange = GetSavedIP(); if (cpRange) { CComPtr cpic; if (S_OK == cpRange->GetContext(&cpic)) { if (cpic != pic) cpRange.Release(); // this will set NULL to cpRange } } if ( !cpRange ) { SaveCurrentIP(ec, pic); cpRange = GetSavedIP(); } SPPHRASE *pPhrase = NULL; HRESULT hr = pResult->GetPhrase(&pPhrase); if (SUCCEEDED(hr) && pPhrase ) { BOOL fEmpty = FALSE; if ( cpRange ) cpRange->IsEmpty(ec, &fEmpty); if (cpRange && fEmpty && pPhrase->ullGrammarID == GRAM_ID_DICT) { CSpDynamicString dstr; hr = pResult->GetText( SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstr, NULL ); if (S_OK == hr) { int cch = wcslen(dstr); BOOL fAware = IsFocusFullAware(_tim); if ( cch > (int)m_ulHypothesisLen ) { _AddFeedbackUI(ec, fAware ? DA_COLOR_AWARE : DA_COLOR_UNAWARE, 5); m_ulHypothesisLen = cch; } } } CoTaskMemFree(pPhrase); } // Set flag to indicate hypothesis processing is finished. m_IsInHypoProcessing = FALSE; } HRESULT CSapiIMX::_HandleFalseRecognition(void) { m_ulHypothesisLen = 0; m_ulHypothesisNum = 0; return S_OK; } HRESULT CSapiIMX::_HandleRecognition(CSpEvent &event, ULONGLONG *pullGramID) { HRESULT hr = S_OK; m_ulHypothesisLen = 0; m_ulHypothesisNum = 0; ISpRecoResult *pResult = event.RecoResult(); if (pResult) { SPPHRASE *pPhrase = NULL; hr = pResult->GetPhrase(&pPhrase); if (S_OK == hr) { BOOL fCommand = FALSE; ULONGLONG ullGramId; BOOL fInjectToDoc = TRUE; ullGramId = pPhrase->ullGrammarID; if ( ullGramId != GRAM_ID_DICT && ullGramId != GRAM_ID_SPELLING ) { // This is a C&C grammar. fCommand = TRUE; } else if ( ullGramId == GRAM_ID_SPELLING ) { const WCHAR *pwszName; pwszName = pPhrase->Rule.pszName; if ( pwszName ) { if (0 == wcscmp(pwszName, c_szSpelling)) fCommand = TRUE; else if ( 0 == wcscmp(pwszName, c_szSpellMode) ) { fCommand = TRUE; ullGramId = 0; // return 0 to fool the handler for SPEI_RECOGNITION // so that it will not call _SetSpellingGrammarStatus(FALSE); } } } if (pullGramID) *pullGramID = ullGramId; if ( fCommand == TRUE) { // If we got the final recognition before a SOUND_END event we should remove the // feedback here otherwise it can and is left in the document. EraseFeedbackUI(); // Ignore HRESULT for better failure behavior. // If candidate UI is open, we need to close it now. This means a voice command (such as scratch that) // will cause the candidate UI to close if open. CloseCandUI( ); // we process this reco synchronously // _DoCommand internal will start edit session if necessary hr = m_pCSpTask->_DoCommand(pPhrase->ullGrammarID, pPhrase, pPhrase->LangID); if ( SUCCEEDED(hr) ) { // if the Command hanlder handles the command successfully, we don't // inject the result to the document. // otherwise, we just inject the text to the document. fInjectToDoc = FALSE; } } if ( fInjectToDoc ) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pUnk = (IUnknown *)pResult; hr = _RequestEditSession(ESCB_HANDLERECOGNITION, TF_ES_READWRITE, &esData); } CoTaskMemFree(pPhrase); } } else { return E_OUTOFMEMORY; } return hr; } void CSapiIMX::_HandleRecognition(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec) { _KillFeedbackUI(ec, pic, NULL); m_pCSpTask->_OnSpEventRecognition(pResult, pic, ec); // Before we clear the saved ip range, we need to treat this current ip as last // saved ip range if current ip is selected by end user SaveLastUsedIPRange( ); // clear the saved IP range SaveIPRange(NULL); }