// // // Sapilayr TIP CLearnFromDoc implementation. // // #include "private.h" #include "sapilayr.h" #include "learndoc.h" #include "cregkey.h" // ---------------------------------------------------------------------------------------------------------- // // Implementation for CLeanrFromDoc // // ----------------------------------------------------------------------------------------------------------- CLearnFromDoc::CLearnFromDoc(CSapiIMX *psi) { m_psi = psi; m_pwszDocBlock = NULL; _pic = NULL; _pCSpTask = NULL; _cpStartRange = NULL; _cchBlockSize = -1; _fMoreContent = FALSE; _fLearnFromDoc = FALSE; } CLearnFromDoc::~CLearnFromDoc( ) { if ( m_pwszDocBlock ) cicMemFree(m_pwszDocBlock); _ClearDimList( ); }; void CLearnFromDoc::UpdateLearnDocState( ) { DWORD dw; GetCompartmentDWORD(m_psi->_tim, GUID_COMPARTMENT_SPEECH_LEARNDOC, &dw, FALSE); if ( dw != 0 ) _fLearnFromDoc = TRUE; else _fLearnFromDoc = FALSE; if ( _fLearnFromDoc == FALSE ) { if ( m_pwszDocBlock ) { cicMemFree(m_pwszDocBlock); m_pwszDocBlock = NULL; } _UpdateRecoContextInterestSet(FALSE); _ResetDimListFeedState( ); } } ULONG CLearnFromDoc::_GetDocBlockSize( ) { if (_cchBlockSize == (ULONG)-1) { CMyRegKey regkey; if (S_OK == regkey.Open(HKEY_LOCAL_MACHINE, c_szSapilayrKey, KEY_READ)) { DWORD dw; if (ERROR_SUCCESS==regkey.QueryValue(dw, c_szDocBlockSize)) { _cchBlockSize = dw; } } if (_cchBlockSize == (ULONG)-1) { _cchBlockSize = SIZE_DOCUMENT_BLOCK; } if ( _cchBlockSize < SIZE_FIRST_BLOCK ) _cchBlockSize = SIZE_FIRST_BLOCK; // the first block size is the minimize size. } return _cchBlockSize; } HRESULT CLearnFromDoc::HandleLearnFromDoc(ITfDocumentMgr *pDim ) { HRESULT hr = S_OK; CComPtr cpic=NULL; ITfDocumentMgr *dim; if ( !m_psi ) return E_FAIL; if ( m_psi->GetFocusIC(&cpic) && (GetLearnFromDoc( ) == TRUE)) { if ( !pDim ) { // Try to get the Dim from current focused IC. hr = cpic->GetDocumentMgr(&dim); } else { dim = pDim; dim->AddRef( ); } if ( !dim ) hr = E_FAIL; if ( S_OK == hr ) { // Check to see if this doc has already been fed to SR LM engine. BOOL fFedAlready = FALSE; hr = _IsDimAlreadyFed(dim, &fFedAlready); if ( (S_OK == hr) && !fFedAlready ) { // Check to if the current doc is ReadOnly or not. TF_STATUS docStatus; hr = cpic->GetStatus(&docStatus); if ( S_OK == hr && !(docStatus.dwStaticFlags & TF_SD_READONLY) ) { ESDATA esData; memset(&esData, 0, sizeof(ESDATA)); esData.pUnk = (IUnknown *)dim; hr = m_psi->_RequestEditSession(ESCB_HANDLE_LEARNFROMDOC,TF_ES_READ, &esData, cpic); } } dim->Release(); } } return hr; } //+--------------------------------------------------------------------------- // // CSapiIMX::_HandleLearnFromDoc // // Let the speech engine learn from existing document content and have // more accurate dictation recognition. // This function will be called when user clicks the speech language bar menu // and select Learn From Document... item. // //---------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_HandleLearnFromDoc(TfEditCookie ec,ITfContext *pic, ITfDocumentMgr *pDim ) { HRESULT hr = S_OK; ULONG cchBlock; BOOL fFeedAlready; // Get the Dictation Grammar TraceMsg(TF_GENERAL, "_HandleLearnFromDoc() is called"); if ( m_psi == NULL) return E_FAIL; if ( !pDim || !pic ) return E_INVALIDARG; _pic = pic; m_psi->GetSpeechTask(&_pCSpTask, FALSE); if ( _pCSpTask == NULL ) { // _sptask is not yet initialized, just return. TraceMsg(TF_GENERAL, "_HandleLearnFromDoc: _pCspTask is NULL"); goto CleanUp; } cchBlock = _GetDocBlockSize( ); if ( m_pwszDocBlock == NULL) { m_pwszDocBlock = (WCHAR *) cicMemAlloc( (cchBlock+1)*sizeof(WCHAR) ); if (m_pwszDocBlock == NULL) { TraceMsg(TF_GENERAL, "_HandleLearnFromDoc: m_pwszDocBlock Out of Memory"); hr = E_OUTOFMEMORY; goto CleanUp; } } fFeedAlready = FALSE; hr = _IsDimAlreadyFed(pDim, &fFeedAlready); if ( (hr != S_OK) || fFeedAlready ) { // This DIM has already been fed to SR Engine. // or we got problem to get the feed state for this dim. // stop here. goto CleanUp; } // Get the range for the document. _cpStartRange.Release( ); hr = _pic->GetStart(ec, &_cpStartRange); if ((hr != S_OK) || (_cpStartRange == NULL)) { TraceMsg(TF_GENERAL, "_HandleLearnFromDoc: _cpStartRange is NULL"); goto CleanUp; } // Change the sptask interersting setting _UpdateRecoContextInterestSet(TRUE); // This is the first range of the document. just small size of block. hr = _GetNextRangeContent(ec, SIZE_FIRST_BLOCK); // Feed to SR Engine. if ( (hr == S_OK) && _fMoreContent) { hr = _FeedContentRangeToSR( ); if ( hr == S_OK) { _SetDimFeedState(pDim, TRUE); } else _fMoreContent = FALSE; } CleanUp: return hr; } //+--------------------------------------------------------------------------- // // _GetNextRangeContent // // Get the Next Range (block) of document content. // // update the flag to indicate if there is effective content // // ec: EditCookie // cchSizeRange: Required block size // // At the first time, we just feed a very small size of charcters // to the Engine so that it will not interfere with current dictation // handling of engine. // // After we get the ADAPTATION notification from Engine, we will feed // normal size of block ( specified in registry ) to engine. // //---------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_GetNextRangeContent(TfEditCookie ec, ULONG cchSizeRange) { HRESULT hr = S_OK; LONG lShift = 0; TraceMsg(TF_GENERAL, "_GetNextRangeContent is Called"); if ( m_pwszDocBlock == NULL) { m_pwszDocBlock = (WCHAR *) cicMemAlloc( (_cchBlockSize+1)*sizeof(WCHAR)); if (m_pwszDocBlock == NULL) { TraceMsg(TF_GENERAL, "_GetNextRangeContent: m_pwszDocBlock Out of Memory"); hr = E_OUTOFMEMORY; return hr; } } // assume there is no more content. _fMoreContent = FALSE; // This is Cicero App's Document, we use Cicero interfaces to get the whole document. // We already get the cpStartRange. hr = _cpStartRange->GetText(ec, TF_TF_MOVESTART | TF_TF_IGNOREEND, m_pwszDocBlock, cchSizeRange, &_cchContent ); if ( hr!= S_OK ) goto CleanUp; // If the last character is not a word-break char for English case, // we don't want to keep this half word for this chunk text, it will go to next // chunk. // we just want to shift back some charaters to hit a word-breaker. WORD prilangid; prilangid = PRIMARYLANGID(m_psi->GetLangID( )); if ( prilangid != LANG_JAPANESE && prilangid != LANG_CHINESE ) { if ( _cchContent > 0 ) { while ( _cchContent > 0 ) { if ( iswalpha(m_pwszDocBlock[_cchContent-1]) ) { m_pwszDocBlock[_cchContent-1] = L'\0'; lShift ++; _cchContent--; } else break; } if ( lShift > 0 ) { lShift *= ( -1 ); long cch; _cpStartRange->ShiftEnd(ec, lShift, &cch, NULL); } } } TraceMsg(TF_GENERAL, "Text Content in Cicero Doc Buffer _cchContent=%d---------------->", _cchContent); #ifdef DEBUG { ULONG x; for (x=0; x<_cchContent; x++) { char szbuf[4]; szbuf[0] = (char)(m_pwszDocBlock[x]); szbuf[1] = '\0'; OutputDebugString(szbuf); } OutputDebugString("\n"); } #endif TraceMsg(TF_GENERAL, "Text Content in CiceroDoc Over -------------------!"); if ( _cchContent > 0 || lShift > 0) // There is more content. _fMoreContent = TRUE; else _fMoreContent = FALSE; CleanUp: return hr; } //+--------------------------------------------------------------------------- // // _FeedContentRangeToSR // // Feed the current Range (block) of document content to the SR Engine. // //---------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_FeedContentRangeToSR( ) { HRESULT hr = S_OK; WCHAR *pCoMemText; CComPtr cpRecoCtxt; TraceMsg(TF_GENERAL, "_FeedContentRangeToSR( ) is called"); hr = _pCSpTask->GetSAPIInterface(IID_ISpRecoContext, (void **)&cpRecoCtxt ); if ( (hr != S_OK) || (cpRecoCtxt == NULL) ) return E_FAIL; // Feed this block of document content to speech dictation. if ( (_cchContent > 0) && (m_pwszDocBlock != NULL)) { pCoMemText = (WCHAR *)CoTaskMemAlloc((_cchContent+1)*sizeof(WCHAR)); if ( pCoMemText ) { wcsncpy(pCoMemText, m_pwszDocBlock, _cchContent); pCoMemText[_cchContent] = L'\0'; hr = cpRecoCtxt->SetAdaptationData(pCoMemText, _cchContent); if ( hr != S_OK) TraceMsg(TF_GENERAL, "_FeedContentRangeToSR: SetAdaptationData Failed"); } } return hr; } HRESULT CLearnFromDoc::_GetNextRangeEditSession( ) { HRESULT hr = S_OK; ESDATA esData; Assert(m_psi); memset(&esData, 0, sizeof(ESDATA)); esData.lData1 = (LONG_PTR)_cchBlockSize; hr = m_psi->_RequestEditSession(ESCB_LEARNDOC_NEXTRANGE, TF_ES_READ, &esData, _pic); return hr; } //+--------------------------------------------------------------------------- // // _HandleNextRange // // Handle the Next Range (block) of document content, get and feed it and // then update the status. // // ec: EditCookie // cchSizeRange: Required block size // //---------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_HandleNextRange(TfEditCookie ec, ULONG cchSizeRange) { HRESULT hr = S_OK; hr = _GetNextRangeContent(ec, cchSizeRange); if ( hr == S_OK && _fMoreContent) { // This next range contains valid content, feed it to SR Engine. hr = _FeedContentRangeToSR( ); } if ( (hr != S_OK) || !_fMoreContent ) { // Error happened or no more content, update the interest set. hr = _UpdateRecoContextInterestSet(FALSE); } return hr; } //+----------------------------------------------------------------------------------------- // // _UpdateRecoContextInterestSet // // Update the RecoContext's interested notification event setting // // if fLearnFromDoc is TRUE, we are interested in getting notification of SPEI_ADAPTATION // if fLearnFromDoc is FALSE, we are not interested in that notification // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_UpdateRecoContextInterestSet( BOOL fLearnFromDoc ) { HRESULT hr = S_OK; CComPtr cpRecoCtxt; ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) | SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_RECO_OTHER_CONTEXT) | SPFEI(SPEI_HYPOTHESIS) | SPFEI(SPEI_INTERFERENCE) | SPFEI(SPEI_FALSE_RECOGNITION); if ( _pCSpTask == NULL ) return hr; hr = _pCSpTask->GetSAPIInterface(IID_ISpRecoContext, (void **)&cpRecoCtxt ); if ( (hr != S_OK) || (cpRecoCtxt == NULL) ) return E_FAIL; if ( fLearnFromDoc ) ulInterest |= SPFEI(SPEI_ADAPTATION); else ulInterest &= ~(SPFEI(SPEI_ADAPTATION)); hr = cpRecoCtxt->SetInterest(ulInterest, ulInterest); return hr; } //+----------------------------------------------------------------------------------------- // // _AddDimToList // // Add a DIM to the dim list, and set the feed state // // This function would be called by TIM_CODE_INITDIM callback. // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_AddDimToList(ITfDocumentMgr *pDim, BOOL fFed ) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; BOOL bFound; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_AddDimToList is called"); if ( !pDim ) return E_INVALIDARG; // Check to see if this dim is already added. bFound = FALSE; for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); if ( dimRef->pDim == pDim ) { bFound = TRUE; TraceMsg(TF_GENERAL, "This dim has already been added to the dim list"); break; } } if (bFound) { // Set the state. dimRef->_fFeed = fFed; } else { dimRef = (DIMREF *)cicMemAllocClear(sizeof(DIMREF)); if ( dimRef == NULL) return E_OUTOFMEMORY; dimRef->_fFeed = fFed; dimRef->pDim = pDim; if (!_rgDim.Insert(nCnt, 1)) { cicMemFree(dimRef); return E_OUTOFMEMORY; } pDim->AddRef( ); _rgDim.Set(nCnt, dimRef); } return hr; } //+----------------------------------------------------------------------------------------- // // _RemoveDimFromList // // Remove a DIM from the internal dim list, and release the DIM itself. // // This function would be called by TIM_CODE_UNINITDIM callback. // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_RemoveDimFromList(ITfDocumentMgr *pDim) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_RemoveDimFromList is called"); if ( pDim == NULL) return E_INVALIDARG; // Check to see if this dim is already added. for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); if ( dimRef->pDim == pDim ) { // free the DIM. (dimRef->pDim)->Release( ); dimRef->pDim = NULL; // Remove it from the list _rgDim.Remove(i, 1); // Remove the structure itself. cicMemFree(dimRef); break; } } return hr; } //+----------------------------------------------------------------------------------------- // // _SetDimFeedState // // Set the feed state for the specified DIM. // // fFed is TRUE means this DIM is already fed to the Engine. // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_SetDimFeedState(ITfDocumentMgr *pDim, BOOL fFed ) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_SetDimFeedState is called"); if ( pDim == NULL) return E_INVALIDARG; // Check to see if this dim is already added. for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); if ( dimRef->pDim == pDim ) { // Set the feed state for this DIM. dimRef->_fFeed = fFed; break; } } return hr; } //+----------------------------------------------------------------------------------------- // // _IsDimAlreadyFed // // Check to see if the dim is already fed to the Engine. // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_IsDimAlreadyFed(ITfDocumentMgr *pDim, BOOL *fFeed) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_IsDimAlreadyFed is called"); if ( (pDim == NULL) || (fFeed == NULL)) return E_INVALIDARG; *fFeed = FALSE; // Check to see if this dim is already added. for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); if ( dimRef->pDim == pDim ) { // Get the feed state for this DIM. *fFeed = dimRef->_fFeed; TraceMsg(TF_GENERAL, "IsDimAlreadyFed: pDim=%x, fFeed=%s", (UINT_PTR)pDim, *fFeed ? "TRUE":"FALSE"); break; } } return hr; } // CleanUpConsider: above _IsDimAlreadyFed and _SetDimFeedState have similar code, we may supply a new internal base function, and let // above two functions call it with different param set. //+----------------------------------------------------------------------------------------- // // _ClearDimList // // Release all the DIMs in the DIM List, and clear the list itself. // // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_ClearDimList( ) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_ClearDimList is called"); for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); // free the DIM. if ( dimRef->pDim) { (dimRef->pDim)->Release( ); dimRef->pDim = NULL; } // Remove it from the list _rgDim.Remove(i, 1); // Remove the structure itself. cicMemFree(dimRef); } return hr; } //+----------------------------------------------------------------------------------------- // // _ResetDimListFeedState // // set feed state for all the dims in internal dim list as FALSE // // This function would be called when user turns off the Learn from Doc. // //-------------------------------------------------------------------------------------------+ HRESULT CLearnFromDoc::_ResetDimListFeedState( ) { HRESULT hr = S_OK; int nCnt = _rgDim.Count(); int i; DIMREF *dimRef; TraceMsg(TF_GENERAL, "_ResetDimListFeedState is called"); for (i=0; i < nCnt; i++) { dimRef = (DIMREF *)_rgDim.Get(i); // Set the feed state for this DIM as FALSE dimRef->_fFeed = FALSE; } return hr; } // CleanUpConsider: above _ClearDimList and _ResetDimListFeedState have similar code, we may supply a new internal base function, and let // above two functions call it with different param set.