|
|
//
// sptask.cpp
//
// implements a notification callback ISpTask
//
// created: 4/30/99
//
//
#include "private.h"
#include "globals.h"
#include "sapilayr.h"
#include "propstor.h"
#include "dictctxt.h"
#include "nui.h"
#include "mui.h"
#include "shlguid.h"
#include "spgrmr.h"
//
//
// CSpTask class impl
//
//
STDMETHODIMP CSpTask::QueryInterface(REFIID riid, void **ppvObj) { *ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown) /* || IsEqualIID(riid, IID_ISpNotifyCallback) */ ) { *ppvObj = SAFECAST(this, CSpTask *); }
if (*ppvObj) { AddRef(); return S_OK; }
return E_NOINTERFACE; }
STDMETHODIMP_(ULONG) CSpTask::AddRef(void) { return ++m_cRef; }
STDMETHODIMP_(ULONG) CSpTask::Release(void) { long cr;
cr = --m_cRef; Assert(cr >= 0);
if (cr == 0) { delete this; }
return cr; }
//
// ctor
//
//
CSpTask::CSpTask(CSapiIMX *pime) { // CSpTask is initialized with an TFX instance
// so store the pointer to the TFX
TraceMsg(TF_SAPI_PERF, "CSpTask is generated");
m_pime = pime; // addref so it doesn't go away during session
m_pime->AddRef(); // init data members here
m_cpResMgr = NULL; m_cpRecoCtxt = NULL; m_cpRecoCtxtForCmd = NULL; m_cpRecoEngine = NULL; m_cpVoice = NULL; m_bInSound = NULL; m_bGotReco = NULL;
m_fSapiInitialized = FALSE; m_fDictationReady = FALSE; m_fInputState = FALSE; m_pLangBarSink = NULL;
// M2 SAPI workaround
m_fIn_Activate = FALSE; m_fIn_SetModeBias = FALSE; m_fIn_GetAlternates = FALSE; m_fIn_SetInputOnOffState = FALSE;
m_fSelectStatus = FALSE; // By default, current selection is empty.
m_fDictationDeactivated = FALSE; m_fSpellingModeEnabled = FALSE; m_fCallbackInitialized = FALSE;
m_fSelectionEnabled = FALSE; m_fDictationInitialized = FALSE;
m_fDictCtxtEnabled = FALSE; m_fCmdCtxtEnabled = FALSE;
m_fTestedForOldMicrosoftEngine = FALSE; m_fOldMicrosoftEngine = FALSE;
#ifdef RECOSLEEP
m_pSleepClass = NULL; #endif
m_cRef = 1; }
CSpTask::~CSpTask() { TraceMsg(TF_SAPI_PERF, "CSpTask is destroyed"); if (m_pdc) delete m_pdc;
if (m_pITNFunc) delete m_pITNFunc;
m_pime->Release(); } //
// CSpTask::_InitializeSAPIObjects
//
// initialize SAPI objects for SR
// later we'll get other objects initialized here
// (TTS, audio etc)
//
HRESULT CSpTask::InitializeSAPIObjects(LANGID langid) {
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is called");
if (m_fSapiInitialized == TRUE) { TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is intialized already\n"); return S_OK; }
// m_xxx are CComPtrs from ATL
//
HRESULT hr = m_cpResMgr.CoCreateInstance( CLSID_SpResourceManager );
TraceMsg(TF_SAPI_PERF, "CLSID_SpResourceManager is created, hr=%x", hr);
if (!m_pime->IsSharedReco()) { // create a recognition engine
TraceMsg(TF_SAPI_PERF,"Inproc engine is generated"); if( SUCCEEDED( hr ) ) { hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpInprocRecognizer ); } if (SUCCEEDED(hr)) { CComPtr<ISpObjectToken> cpAudioToken; SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpAudioToken); if (SUCCEEDED(hr)) { m_cpRecoEngine->SetInput(cpAudioToken, TRUE); } } } else { hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpSharedRecognizer ); TraceMsg(TF_SAPI_PERF, "Shared Engine is generated! hr=%x", hr); }
// create the recognition context
if( SUCCEEDED( hr ) ) { hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxt );
TraceMsg(TF_SAPI_PERF, "RecoContext is generated, hr=%x", hr); } GUID guidFormatId = GUID_NULL; WAVEFORMATEX *pWaveFormatEx = NULL; if (SUCCEEDED(hr)) { hr = SpConvertStreamFormatEnum(SPSF_8kHz8BitMono, &guidFormatId, &pWaveFormatEx);
TraceMsg(TF_SAPI_PERF, "SpConvertStreamFormatEnum is done, hr=%x", hr); }
if (SUCCEEDED(hr)) { hr = m_cpRecoCtxt->SetAudioOptions(SPAO_RETAIN_AUDIO, &guidFormatId, pWaveFormatEx);
TraceMsg(TF_SAPI_PERF, "RecoContext SetAudioOptions, RETAIN AUDIO, hr=%x", hr);
if (pWaveFormatEx) ::CoTaskMemFree(pWaveFormatEx); }
if( SUCCEEDED( hr ) ) { hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
TraceMsg(TF_SAPI_PERF, "SpVoice is generated, hr=%x", hr); }
if ( SUCCEEDED(hr) ) { // this has to be extended so that
// we choose default voice as far as lang matches
// and pick the best match if not
//
// hr = _SetVoice(langid);
}
//
if ( SUCCEEDED(hr) ) { m_langid = _GetPreferredEngineLanguage(langid);
TraceMsg(TF_SAPI_PERF, "_GetPreferredEngineLanguage is Done, m_langid=%x", m_langid); }
#ifdef RECOSLEEP
InitSleepClass( ); #endif
if (SUCCEEDED(hr)) m_fSapiInitialized = TRUE;
TraceMsg(TF_SAPI_PERF, "InitializeSAPIObjects is Done!!!!!, hr=%x\n", hr);
return hr; }
//
// CSpTask::_InitializeSAPIForCmd
//
// initialize SAPI RecoContext for Voice Command mode
//
// this function should be called after _InitializeSAPIObject.
//
HRESULT CSpTask::InitializeSAPIForCmd( ) {
TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd is called"); HRESULT hr = S_OK;
if (!m_cpRecoCtxtForCmd && m_cpRecoEngine && m_langid) { hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxtForCmd ); TraceMsg(TF_SAPI_PERF, "m_cpRecoCtxtForCmd is generated, hr=%x", hr); // Set the RecoContextState as DISABLE by default to improve SAPI Perf.
//
// After initializing, caller must set the context state explicitly.
if ( SUCCEEDED(hr) ) { hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED); m_fCmdCtxtEnabled = FALSE; }
TraceMsg(TF_SAPI_PERF, "Initialize Callback for RecoCtxtForCmd");
// set recognition notification
CComPtr<ISpNotifyTranslator> cpNotify;
if ( SUCCEEDED(hr) ) hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for RecoCtxtForCmd is generated, hr=%x", hr);
// set this class instance to notify control object
if (SUCCEEDED(hr)) { m_pime->_EnsureWorkerWnd();
hr = cpNotify->InitCallback( NotifyCallbackForCmd, 0, (LPARAM)this ); } if (SUCCEEDED(hr)) { hr = m_cpRecoCtxtForCmd->SetNotifySink(cpNotify); TraceMsg(TF_SAPI_PERF, "SetNotifySink for RecoCtxtForCmd is Done, hr=%x", hr); }
// set the events we're interested in
if( SUCCEEDED( hr ) ) { const ULONGLONG ulInterest = SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_FALSE_RECOGNITION) | SPFEI(SPEI_RECO_OTHER_CONTEXT);
hr = m_cpRecoCtxtForCmd->SetInterest(ulInterest, ulInterest); TraceMsg(TF_SAPI_PERF, "SetInterest for m_cpRecoCtxtForCmd is Done, hr=%x", hr); }
TraceMsg(TF_SAPI_PERF, "InitializeCallback for m_cpRecoCtxtForCmd is done!!! hr=%x", hr);
// Load the shard command grammars and activate them by default.
if (SUCCEEDED(hr) ) { hr = m_cpRecoCtxtForCmd->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInVoiceCmd); TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammar In Voice cmd, hr=%x", hr); }
if (S_OK == hr) { hr = S_FALSE;
// try resource first because loading cmd from file takes
// quite long time
//
if (m_langid == 0x409 || // English
m_langid == 0x411 || // Japanese
m_langid == 0x804 ) // Simplified Chinese
{ hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG), L"SRGRAMMAR", m_langid, SPLO_DYNAMIC);
TraceMsg(TF_SAPI_PERF, "Load shared cmd.cfg, hr=%x", hr); }
if (S_OK != hr) { // in case if we don't have built-in grammar
// it provides a way for customer to localize their grammars in different languages
_GetCmdFileName(m_langid); if (m_szShrdCmdFile[0]) { hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC); } }
// Activate the grammar by default
if ( hr == S_OK ) { if (m_pime->_AllCmdsEnabled( )) { hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(NULL, NULL, SPRS_ACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSharedGrammarInVoiceCmd"); } else { // Some category commands are disabled.
// active them individually.
hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_COMMAND_MODE );
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_COMMAND_MODE); } }
if (S_OK != hr) { m_cpSharedGrammarInVoiceCmd.Release(); } else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH || PRIMARYLANGID(m_langid) == LANG_JAPANESE || PRIMARYLANGID(m_langid) == LANG_CHINESE ) { // means this language's grammar support Textbuffer commands.
m_fSelectionEnabled = TRUE; }
#ifdef RECOSLEEP
InitSleepClass( ); #endif
} TraceMsg(TF_SAPI_PERF, "Finish the initalization for RecoCtxtForCmd"); } TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd exits!");
return hr; }
#ifdef RECOSLEEP
void CSpTask::InitSleepClass( ) { // Load the Sleep/Wakeup grammar.
if ( !m_pSleepClass ) { m_pSleepClass = new CRecoSleepClass(this); if ( m_pSleepClass ) m_pSleepClass->InitRecoSleepClass( ); } }
BOOL CSpTask::IsInSleep( ) { BOOL fSleep = FALSE;
if ( m_pSleepClass ) fSleep = m_pSleepClass->IsInSleep( );
return fSleep; } #endif
HRESULT CSpTask::_SetDictRecoCtxtState( BOOL fEnable ) { HRESULT hr = S_OK;
TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState is called, fEnable=%d", fEnable);
if ( m_cpRecoCtxt && (fEnable != m_fDictCtxtEnabled)) { if (fEnable ) { // if Voice command reco Context is enabled, just disable it.
if (m_cpRecoCtxtForCmd && m_fCmdCtxtEnabled) { hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED); m_fCmdCtxtEnabled = FALSE; TraceMsg(TF_SAPI_PERF, "Disable Voice command Reco Context"); }
// Build toolbar grammar if it is not built out yet.
if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( )) m_pLangBarSink->_OnSetFocus( );
// Enable Dictation Reco Context.
if ( hr == S_OK ) { hr = m_cpRecoCtxt->SetContextState(SPCS_ENABLED); TraceMsg(TF_SAPI_PERF, "Enable Dictation Reco Context");
if ( hr == S_OK && !m_fDictationReady ) { WCHAR sz[128]; sz[0] = '\0'; CicLoadStringWrapW(g_hInst, IDS_NUI_BEGINDICTATION, sz, ARRAYSIZE(sz));
m_pime->GetSpeechUIServer()->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1); m_fDictationReady = TRUE; TraceMsg(TF_SAPI_PERF, "Show Begin Dictation!"); } } } else { hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED); TraceMsg(TF_SAPI_PERF, "Disable Dictation Reco Context"); }
if ( hr == S_OK ) { m_fDictCtxtEnabled = fEnable; } }
TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState exit");
return hr; }
HRESULT CSpTask::_SetCmdRecoCtxtState( BOOL fEnable ) { TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState is called, fEnable=%d", fEnable); HRESULT hr = S_OK;
if ( fEnable != m_fCmdCtxtEnabled ) { if ( fEnable ) { if ( !m_cpRecoCtxtForCmd ) hr = InitializeSAPIForCmd( );
if ( hr == S_OK && m_cpRecoCtxtForCmd ) { // Disable Dictation Context if it is enabled now.
if (m_cpRecoCtxt && m_fDictCtxtEnabled) { hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED); m_fDictCtxtEnabled = FALSE; TraceMsg(TF_SAPI_PERF, "DISABLE Dictation RecoContext"); }
if ( hr == S_OK && m_pime && !m_pime->_AllCmdsDisabled( ) ) { // Build toolbar grammar if it is not built out yet.
if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( )) m_pLangBarSink->_OnSetFocus( );
// Fill text to selection grammar's buffer.
_UpdateTextBuffer(m_cpRecoCtxtForCmd);
hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_ENABLED); m_fCmdCtxtEnabled = fEnable; TraceMsg(TF_SAPI_PERF, "Enable Voice command Reco Context"); } } } else if ( m_cpRecoCtxtForCmd ) // fEnable is FALSE
{ hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED); m_fCmdCtxtEnabled = FALSE; TraceMsg(TF_SAPI_PERF, "Disable Voice Command Reco Context"); } }
TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState exits"); return hr; }
LANGID CSpTask::_GetPreferredEngineLanguage(LANGID langid) { SPRECOGNIZERSTATUS stat; LANGID langidRet = 0;
// (possible TODO) After M3 SPG may come up with GetAttrRank API that
// would give us the info about whether a token has a particular
// attrib supported. Then we could use that for checking langid
// a recognizer supports without using the real engine instance.
// We could also consolidate a method to check if SR is enabled
// for the current language once we have that.
//
Assert(m_cpRecoEngine); if (S_OK == m_cpRecoEngine->GetStatus(&stat)) { for (ULONG ulId = 0; ulId < stat.cLangIDs; ulId++) { if (langid == stat.aLangID[ulId]) { langidRet = langid; break; } } if (!langidRet) { // if there's no match, just return the most prefered one
langidRet = stat.aLangID[0]; } } return langidRet; }
HRESULT CSpTask::_SetVoice(LANGID langid) { CComPtr<ISpObjectToken> cpToken;
char szLang[MAX_PATH]; WCHAR wsz[MAX_PATH];
StringCchPrintfA(szLang,ARRAYSIZE(szLang), "Language=%x", langid); MultiByteToWideChar(CP_ACP, NULL, szLang, -1, wsz, ARRAYSIZE(wsz));
HRESULT hr = SpFindBestToken( SPCAT_VOICES, wsz, NULL, &cpToken);
if (S_OK == hr) { hr = m_cpVoice->SetVoice(cpToken); } return hr; }
//
// GetSAPIInterface(riid, (void **)ppunk)
//
// here, try pass through the given IID
// to SAPI5 interface
//
// CComPtr<ISpResourceManager> m_cpResMgr;
// CComPtr<ISpRecoContext> m_cpRecoCtxt;
// CComPtr<ISpRecognizer> m_cpRecoEngine;
// CComPtr<ISpVoice> m_cpVoice;
//
// the above 5 interfaces are currently used by
// Cicero/Sapi Layer
//
// if a client calls ITfFunctionProvider::GetFunction()
// for a SAPI interface, we return what we've already
// instantiated so the caller can setup options
// for the currently used SAPI objects (reco ctxt for ex)
//
HRESULT CSpTask::GetSAPIInterface(REFIID riid, void **ppunk) { Assert(ppunk); *ppunk = NULL;
if (IsEqualGUID(riid, IID_ISpResourceManager)) { *ppunk = m_cpResMgr; } else if (IsEqualGUID(riid,IID_ISpRecoContext)) { *ppunk = m_cpRecoCtxt; } else if (IsEqualGUID(riid,IID_ISpRecognizer)) { *ppunk = m_cpRecoEngine; } else if (IsEqualGUID(riid,IID_ISpVoice)) { *ppunk = m_cpVoice; } else if (IsEqualGUID(riid,IID_ISpRecoGrammar)) { *ppunk = m_cpDictGrammar; } if(*ppunk) { ((IUnknown *)(*ppunk))->AddRef(); } return *ppunk ? S_OK : E_NOTIMPL; }
//
// Get RecoContext for Voice Command mode.
//
HRESULT CSpTask::GetRecoContextForCommand(ISpRecoContext **ppRecoCtxt) { HRESULT hr = E_FAIL;
Assert(ppRecoCtxt);
if ( m_cpRecoCtxtForCmd ) { *ppRecoCtxt = m_cpRecoCtxtForCmd; (*ppRecoCtxt)->AddRef( ); hr = S_OK; }
return hr; }
// test: use Message callback
LRESULT CALLBACK CSapiIMX::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CSapiIMX *_this = (CSapiIMX *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
CSpTask *_sptask = _this ? _this->m_pCSpTask : NULL;
switch(uMsg) { case WM_CREATE: { CREATESTRUCT *pcs = (CREATESTRUCT *)lParam; if (pcs) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)(pcs->lpCreateParams)); } break; } case WM_TIMER:
if ( wParam != TIMER_ID_CHARTYPED ) KillTimer( hWnd, wParam );
if (wParam == TIMER_ID_OPENCLOSE) { // i've seen this null case once but is it possible?
TraceMsg(TF_SAPI_PERF, "TIMER_ID_OPENCLOSE is fired off ..."); if (_this->_tim) _this->_HandleOpenCloseEvent(MICSTAT_ON); } else if ( wParam == TIMER_ID_CHARTYPED ) { DWORD dwNumCharTyped; BOOL fDictOn;
fDictOn = _this->GetOnOff( ) && _this->GetDICTATIONSTAT_DictOnOff( ); dwNumCharTyped = _this->_GetNumCharTyped( );
TraceMsg(TF_GENERAL, "dwNumCharTyped=%d", dwNumCharTyped);
_this->_KillCharTypeTimer( );
Assert(S_OK == _this->IsActiveThread()); // We should never try to reactivate dictation on a thread that shouldn't be active.
if ( fDictOn && _sptask && (S_OK == _this->IsActiveThread()) ) { if ( dwNumCharTyped <= 1 ) { // There is no more typing during this period
// possible, user finished typing.
//
// we need to resume dication again if the Dictation mode is ON.
ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) | SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_FALSE_RECOGNITION) | SPFEI(SPEI_RECO_OTHER_CONTEXT) | SPFEI(SPEI_HYPOTHESIS) | SPFEI(SPEI_INTERFERENCE) | SPFEI(SPEI_ADAPTATION);
_sptask->_SetDictRecoCtxtState(TRUE); _sptask->_SetRecognizerInterest(ulInterest); _sptask->_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP); } else { // There are more typing during this period,
// we want to set another timer to watch the end of the typing.
//
_this->_SetCharTypeTimer( ); } } }
break; case WM_PRIV_FEEDCONTEXT: if (_sptask && lParam != NULL && _sptask->m_pdc) { _sptask->m_pdc->FeedContextToGrammar(_sptask->m_cpDictGrammar); delete _sptask->m_pdc; _sptask->m_pdc = NULL; } break; case WM_PRIV_LBARSETFOCUS: if (_sptask) _sptask->m_pLangBarSink->_OnSetFocus(); break; case WM_PRIV_SPEECHOPTION: { _this->_ResetDefaultLang(); BOOL fSREnabledForLanguage = _this->InitializeSpeechButtons(); _this->SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage); } break; case WM_PRIV_ADDDELETE: _this->_DisplayAddDeleteUI(); break;
case WM_PRIV_SPEECHOPENCLOSE: TraceMsg(TF_SAPI_PERF, "WM_PRIV_SPEECHOPENCLOSE is handled"); _this->_HandleOpenCloseEvent(); break; case WM_PRIV_OPTIONS: _this->_InvokeSpeakerOptions(); break;
case WM_PRIV_DORECONVERT : _this->_DoReconvertOnRange( ); break;
default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }
void CSpTask::NotifyCallbackForCmd(WPARAM wParam, LPARAM lParam ) { CSpTask *_this = (CSpTask *)lParam;
// SAPI M2 work around which is to be removed when M3 comes up.
// See comments in CSpTask::_SetInputOnOffState for more detail
//
// TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
/* if (_this->m_fInputState == FALSE)
{ return; }*/
if (_this->m_pime->fDeactivated()) return;
if (!_this->m_cpRecoCtxtForCmd) { return; }
_this->SharedRecoNotify(_this->m_cpRecoCtxtForCmd);
return; }
void CSpTask::NotifyCallback( WPARAM wParam, LPARAM lParam ) { CSpTask *_this = (CSpTask *)lParam;
// SAPI M2 work around which is to be removed when M3 comes up.
// See comments in CSpTask::_SetInputOnOffState for more detail
//
// TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
/* if (_this->m_fInputState == FALSE)
{ return; }*/
if (_this->m_pime->fDeactivated()) return;
if (!_this->m_cpRecoCtxt) { return; }
_this->SharedRecoNotify(_this->m_cpRecoCtxt);
return; }
// This is real handler for the recognition notification.
//
// it could be shared by two RecoContexts.
//
void CSpTask::SharedRecoNotify(ISpRecoContext *pRecoCtxt) { CSpEvent event; #ifdef SAPI_PERF_DEBUG
static int iCount = 0;
if ( iCount == 0 ) { TraceMsg(TF_SAPI_PERF, "The first time Get Notification from Engine!!!"); iCount ++; } #endif
Assert (pRecoCtxt);
while ( event.GetFrom(pRecoCtxt) == S_OK ) { switch (event.eEventId) { case SPEI_SOUND_START: ATLASSERT(!m_bInSound); m_bInSound = TRUE; break;
case SPEI_INTERFERENCE: //
// we do not need interference when not in dictation
// mode
//
if (m_pime->GetDICTATIONSTAT_DictOnOff() && S_OK == m_pime->IsActiveThread()) { _HandleInterference((ULONG)(event.lParam)); } break;
case SPEI_PHRASE_START: ATLASSERT(m_bInSound); m_bGotReco = FALSE;
if (m_pime->GetDICTATIONSTAT_DictOnOff() && S_OK == m_pime->IsActiveThread()) { // Before inject feedback UI, we need to save the current IP
// and check if we want to pop up the Add/Remove SR dialog UI.
// And then inject FeedbackUI as usual
m_pime->SaveCurIPAndHandleAddDelete_InjectFeedbackUI( );
// show "dictating..." to the balloon
_ShowDictatingToBalloon(TRUE); }
break;
case SPEI_HYPOTHESIS: ATLASSERT(!m_bGotReco);
// if current microphone status is OFF
// we do not want to show any hypothesis
// at least
//
// DO NOT HAVE DEBUG CODE TO SHOW ENGINE STATE. CAN BLOCK CICERO AND CHANGE BEHAVIOR.
if (!GetSystemMetrics(SM_TABLETPC)) _ShowDictatingToBalloon(TRUE);
//
// we do not need the feedback UI when not in dictation
// mode
//
if (m_pime->GetDICTATIONSTAT_DictOnOff() && S_OK == m_pime->IsActiveThread()) { m_pime->_HandleHypothesis(event); } break;
case SPEI_RECO_OTHER_CONTEXT: case SPEI_FALSE_RECOGNITION: { HRESULT hr = S_OK;
if ( event.eEventId == SPEI_FALSE_RECOGNITION ) { // Set 'What was that?' feedback text.
_UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE); }
// set this flag anyways
//
ATLASSERT(!m_bGotReco); m_bGotReco = TRUE;
// Reset hypothesis counters.
m_pime->_HandleFalseRecognition();
hr = m_pime->EraseFeedbackUI(); ATLASSERT("Failed to erase potential feedback on a false recognition." && SUCCEEDED(hr));
break; }
case SPEI_RECOGNITION:
// Set 'Listening...' feedback text. Can be overwritten by command feedback.
_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
// set this flag anyways
//
ATLASSERT(!m_bGotReco); m_bGotReco = TRUE;
ULONGLONG ullGramID;
if ( S_OK == m_pime->IsActiveThread() ) { m_pime->_HandleRecognition(event, &ullGramID); }
// if ( _GetSelectionStatus( ) )
if (ullGramID == GRAM_ID_SPELLING) { _SetSelectionStatus(FALSE); _SetSpellingGrammarStatus(FALSE); }
_UpdateTextBuffer(pRecoCtxt);
if ( (ullGramID == GRAM_ID_DICT) || (ullGramID == GRAM_ID_SPELLING) ) { // Update Balloon.
if (!GetSystemMetrics(SM_TABLETPC)) _UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
// every time dictated text is injected, we want to watch
// again if there is IP change after that.
// so clear the flag now.
m_pime->_SetIPChangeStatus( FALSE ); }
break;
case SPEI_SOUND_END: m_bInSound = FALSE;
break; case SPEI_ADAPTATION: TraceMsg(TF_GENERAL, "Get SPEI_ADAPTATION notification");
if ( m_pime->_HasMoreContent( ) ) { m_pime->_GetNextRangeEditSession( ); } else // There is no more content for this doc.
// set the interesting event value to avoid this notification.
m_pime->_UpdateRecoContextInterestSet(FALSE);
break; #ifdef SYSTEM_GLOBAL_MIC_STATUS
case SPEI_RECO_STATE_CHANGE: m_pime->SetOnOff(_GetInputOnOffState()); break; #endif
default: break; } } return; }
HRESULT CSpTask::_UpdateTextBuffer(ISpRecoContext *pRecoCtxt) { HRESULT hr = S_OK;
if ( !_IsSelectionEnabled( ) ) return S_OK;
if ( !pRecoCtxt || !m_pime) return E_FAIL;
if ( m_pime->_SelectCorrectCmdEnabled( ) || m_pime->_NavigationCmdEnabled( ) ) { BOOL fDictOn, fCmdOn;
fDictOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_DictOnOff( ); fCmdOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( );
if ( fDictOn && m_cpSharedGrammarInDict && !m_pime->_AllDictCmdsDisabled( )) hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInDict); else if (fCmdOn && m_cpSharedGrammarInVoiceCmd ) hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInVoiceCmd); }
return hr; }
// When selection grammar status is changed from inactive to active
// this function will be called to fill text to the grammar buffer.
//
HRESULT CSpTask::_UpdateSelectGramTextBufWhenStatusChanged( ) { BOOL fDictOn, fCmdOn; HRESULT hr = S_OK;
// Check current mode status.
fDictOn = m_pime->GetDICTATIONSTAT_DictOnOff( ); fCmdOn = m_pime->GetDICTATIONSTAT_CommandingOnOff( );
if ( fDictOn ) hr = _UpdateTextBuffer(m_cpRecoCtxt); else if ( fCmdOn ) hr = _UpdateTextBuffer(m_cpRecoCtxtForCmd);
return hr; }
HRESULT CSpTask::_OnSpEventRecognition(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec) { HRESULT hr = S_OK; BOOL fDiscard = FALSE; BOOL fCtrlSymChar = FALSE; // Control or Punctuation character
if (pResult) { static const WCHAR szUnrecognized[] = L"<Unrecognized>"; LANGID langid; SPPHRASE *pPhrase;
hr = pResult->GetPhrase(&pPhrase); if (SUCCEEDED(hr) && pPhrase) { // AJG - ADDED FILTERING CODE.
switch (pPhrase->Rule.ulCountOfElements) { case 0: { ASSERT(pPhrase->Rule.ulCountOfElements != 0); // SHOULD NEVER OCCUR.
break; } case 1: { const SPPHRASEELEMENT *pElement;
pElement = pPhrase->pElements;
if (!m_fTestedForOldMicrosoftEngine) { // Test token name to see if it contains MSASREnglish.
CComPtr<ISpObjectToken> cpRecoToken; WCHAR *pwszCoMemTokenId; m_cpRecoEngine->GetRecognizer(&cpRecoToken); if (cpRecoToken) { if (SUCCEEDED(cpRecoToken->GetId(&pwszCoMemTokenId))) { if (wcsstr(pwszCoMemTokenId, L"MSASREnglish") != NULL) { // It is an old Microsoft engine. Check for registry key that tells us to disable the heuristic anyway.
BOOL fDisableHeuristicAnyway = FALSE; if (FAILED(cpRecoToken->MatchesAttributes(L"DisableCiceroConfidence", &fDisableHeuristicAnyway)) || fDisableHeuristicAnyway == FALSE) { m_fOldMicrosoftEngine = TRUE; // Means we *will* apply single word confidence heuristic to improve performance.
} } CoTaskMemFree(pwszCoMemTokenId); } }
// One of lazy initialization. Do not do this again.
m_fTestedForOldMicrosoftEngine = TRUE; } if (m_fOldMicrosoftEngine && m_pime->_RequireHighConfidenceForShorWord( ) ) { // Only apply this heuristic to 5.x Microsoft engines (Token name contains MSASREnglish).
if (pElement && pElement->ActualConfidence != 1 && (!pElement->pszLexicalForm || wcslen(pElement->pszLexicalForm) <= 5) && (!pElement->pszDisplayText || wcslen(pElement->pszDisplayText) <= 5) ) { TraceMsg(TF_GENERAL, "Discarded Result : Single Word, Low Confidence!"); _UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE ); fDiscard = TRUE; } }
if (pPhrase->pElements[0].pszDisplayText ) { WCHAR wch;
wch = pPhrase->pElements[0].pszDisplayText[0];
if ( iswcntrl(wch) || iswpunct(wch) ) fCtrlSymChar = TRUE; }
} case 2: { // Do something here?
} default: { // Do no filtering of the result.
} } // AJG - CHECK WE AREN'T IN THE MIDDLE OF A WORD. NOT GENERALLY A DESIRED 'FEATURE'. CAUSES ANNOYING ERRORS.
// if this is spelled text, don't check if it is inside of a word.
if ((pPhrase->ullGrammarID != GRAM_ID_SPELLING) && _IsSelectionInMiddleOfWord(ec) && !fCtrlSymChar) { TraceMsg(TF_GENERAL, "Discarded Result : IP is in middle of a word!"); _UpdateBalloon(IDS_BALLOON_DICTAT_PAUSED, IDS_BALLOON_TOOLTIP_IP_INSIDE_WORD); fDiscard = TRUE; } }
if ( SUCCEEDED(hr) && fDiscard ) { // This phrase will not be injected to the document.
// the code needs to feed context to the SR engine so that
// SR engine will not base on wrong assumption.
if ( m_pime && m_pime->GetDICTATIONSTAT_DictOnOff() ) m_pime->_SetCurrentIPtoSR();
}
if (SUCCEEDED(hr) && pPhrase && !fDiscard) { // retrieve LANGID from phrase
langid = pPhrase->LangID;
// SPPHRASE includes non-serialized text
CSpDynamicString dstr; ULONG ulNumElements = pPhrase->Rule.ulCountOfElements;
hr = _GetTextFromResult(pResult, langid, dstr);
if ( hr == S_OK ) { // check the current IP to see if it was a selection,
// then see if the best hypothesis already matches the current
// selection.
int lCommitHypothesis = 0; for (int nthHypothesis = 1;_DoesSelectionHaveMatchingText(dstr, ec); nthHypothesis++) { CSpDynamicString dsNext;
TraceMsg(TF_GENERAL, "Switched to alternate result as main result exactly matched selection!");
// We could add one to request hypothesis since 1 = the main phrase and we already know that matched.
// However I don't believe this is guaranteed to be the case by SAPI - it just happens to be the case
// with the Microsoft engine.
if (_GetNextBestHypothesis(pResult, nthHypothesis, &ulNumElements, langid, dstr, dsNext, ec)) { dstr.Clear(); dstr.Append(dsNext); // Need to commit phrase to prevent stored result object being out of sync with count of
// elements in wrapping object.
lCommitHypothesis = nthHypothesis; // Note - at this point, we don't know if we can use it. We have to loop once more to determine this.
} else { TraceMsg(TF_SAPI_PERF, "No alternate found that differed from the user selection.\n"); // No more alternate phrase
// There is no any alt phrase which has different text from current selection.
// should stop here, otherwise, infinite loop.
lCommitHypothesis = 0; // Reset element count to match primary phrase.
ulNumElements = pPhrase->Rule.ulCountOfElements; // Reset text:
dstr.Clear(); hr = _GetTextFromResult(pResult, langid, dstr); break; } }
if (0 != lCommitHypothesis) { ULONG cAlt = lCommitHypothesis; ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(cAlt*sizeof(ISpPhraseAlt *)); if (ppAlt) { memset(ppAlt, 0, cAlt * sizeof(ISpPhraseAlt *)); hr = pResult->GetAlternates( 0, ulNumElements, cAlt, ppAlt, &cAlt );
Assert( cAlt == lCommitHypothesis );
if ((S_OK == hr) && (cAlt == lCommitHypothesis)) { ((ppAlt)[lCommitHypothesis-1])->Commit(); }
// Release references to alternate phrases.
for (UINT i = 0; i < cAlt; i++) { if (NULL != (ppAlt)[i]) { ((ppAlt)[i])->Release(); } } cicMemFree(ppAlt); } }
CComPtr<ITfRange> cpTextRange; ITfRange *pSavedIP;
pSavedIP = m_pime->GetSavedIP( );
if (pSavedIP) pSavedIP->Clone(&cpTextRange);
// this call will have to be per element. see my comment below.
if (pPhrase->ullGrammarID == GRAM_ID_SPELLING) { hr = m_pime->InjectSpelledText(dstr, langid); } else { hr = m_pime->InjectText(dstr, langid);
if ( hr == S_OK ) { // now we use the result object directly to attach
// to a docuement.
// the result object gets addref'd in the Attach()
// call.
//
hr = m_pime->AttachResult(pResult, 0, ulNumElements); }
// Handle spaces carefully and specially.
if ( hr == S_OK && cpTextRange ) { hr = m_pime->HandleSpaces(pResult, 0, ulNumElements, cpTextRange, langid); } } } }
if ( pPhrase) ::CoTaskMemFree( pPhrase ); }
return hr; }
//
// _GetTextFromResult
//
// synopsis: get text from phrase considering space control
// based on locale
//
HRESULT CSpTask::_GetTextFromResult(ISpRecoResult *pResult, LANGID langid, CSpDynamicString &dstr) { BYTE bAttr; HRESULT hr = S_OK; Assert(pResult);
if ( !pResult ) return E_INVALIDARG;
hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstr, &bAttr);
if ( hr == S_OK ) { if (bAttr & SPAF_ONE_TRAILING_SPACE) { dstr.Append(L" "); } else if (bAttr & SPAF_TWO_TRAILING_SPACES) { dstr.Append(L" "); }
if (bAttr & SPAF_CONSUME_LEADING_SPACES) { // we need to figure out the correct behavior based on LANGID
} }
return hr; }
//
// _IsSelectionInMiddleOfWord
//
// synopsis: check if the current IP is empty and inside of a word
//
BOOL CSpTask::_IsSelectionInMiddleOfWord(TfEditCookie ec) { BOOL fInsideWord = FALSE;
if ( m_langid == 0x0409 ) { if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP()) { WCHAR szSurrounding[3] = L" ";
// clone the IP range since we want to move the anchor
//
CComPtr<ITfRange> cpClonedRange; cpInsertionPoint->Clone(&cpClonedRange);
BOOL fEmpty; cpClonedRange->IsEmpty(ec, &fEmpty); if (fEmpty) { LONG l1, l2; ULONG ul; HRESULT hr;
cpClonedRange->Collapse(ec, TF_ANCHOR_START); cpClonedRange->ShiftStart(ec, -1, &l1, NULL); cpClonedRange->ShiftEnd(ec, 1, &l2, NULL); if (l1 != 0) // Not at start of document.
{ hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szSurrounding, (l2!=0)?(2):(1), &ul); if (SUCCEEDED(hr) && iswalpha(szSurrounding[0]) && iswalpha(szSurrounding[1]) ) { fInsideWord = TRUE; } } // if l1 == 0, means the ip is at the start of document.
// fInsideWord is set to FALSE already by default.
} } } return fInsideWord; }
//
// DoesSelectionHaveMatchingText
//
// synopsis: check if the current saved IP has a selection that matches the text
// passed in
//
#define SPACEBUFFER 4
// 2 characters either side of a word or phrase.
BOOL CSpTask::_DoesSelectionHaveMatchingText(WCHAR *psz, TfEditCookie ec) { BOOL fMatch = FALSE; Assert(psz);
if ( !psz ) { return FALSE; }
WCHAR *pszStripped = psz; ULONG ulCch = wcslen(psz);
// Remove trailing space.
while (ulCch > 0 && psz[ulCch-1] == L' ') { // Do not set null terminating character as this is a passed in string.
ulCch --; } // Skip leading space in input text.
while (pszStripped[0] == L' ') { pszStripped ++; ulCch --; } // Now have space-stripped word pointed to by pszTmp and with length ulCch
if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP()) { WCHAR *szRange = new WCHAR[ulCch+SPACEBUFFER+1]; WCHAR *szRangeStripped = szRange; if (szRange) { // clone the IP range since we want to move the anchor
//
CComPtr<ITfRange> cpClonedRange; cpInsertionPoint->Clone(&cpClonedRange); ULONG cchRange; // max is the reco result
HRESULT hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szRange, ulCch+SPACEBUFFER, &cchRange); // Remove trailing space.
while (cchRange > 0 && szRange[cchRange-1] == L' ') { // Can set null terminating character as this is our string.
szRange[cchRange-1] = 0; cchRange --; } // Skip leading space in input text.
while (szRangeStripped[0] == L' ') { szRangeStripped ++; cchRange --; } // Now have space-stripped word pointed to by pszTmp and with length ulCch
if (S_OK == hr && cchRange > 0 && cchRange == ulCch) { if (wcsnicmp(pszStripped, szRangeStripped, ulCch) == 0) // Case insensitive compare.
{ fMatch = TRUE; } } delete [] szRange; } } return fMatch; }
//
// GetNextBestHypothesis
//
// synopsis: this actually gets the nth alternative from the given reco result
// then adjusts the length accordingly based on the current selection
//
//
BOOL CSpTask::_GetNextBestHypothesis ( ISpRecoResult *pResult, ULONG nthHypothesis, ULONG *pulNumElements, LANGID langid, WCHAR *pszBest, CSpDynamicString & dsNext, TfEditCookie ec ) { if ( pulNumElements ) *pulNumElements = 0;
// get the entire text & length from the saved IP
if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP()) { CSpDynamicString dstr; CComPtr<ITfRange> cpClonedRange; CComPtr<ISpRecoResult> cpRecoResult; // clone the range since we move the anchor
HRESULT hr = cpInsertionPoint->Clone(&cpClonedRange); ULONG cchRangeBuf = wcslen(pszBest); cchRangeBuf *= 2; // guess the possible # of char
WCHAR *szRangeBuf = new WCHAR[cchRangeBuf+1];
if ( !szRangeBuf ) { // Error: Out of Memory
// Return here as FALSE.
return FALSE; }
while(S_OK == hr && !_IsRangeEmpty(ec, cpClonedRange)) { hr = m_pime->_GetRangeText(cpClonedRange, TF_TF_MOVESTART, szRangeBuf, &cchRangeBuf); if (S_OK == hr) { szRangeBuf[cchRangeBuf] = L'\0'; dstr.Append(szRangeBuf); } } delete [] szRangeBuf;
// then get a best matching length of next best hypothesis
// the current recognition should at least have a good guess for # of elements
// since it turned out to be longer than the IP range.
//
Assert(pulNumElements); ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(nthHypothesis*sizeof(ISpPhraseAlt *)); ULONG cAlt = 0; if (!ppAlt) { hr = E_OUTOFMEMORY; } else { memset(ppAlt, 0, nthHypothesis * sizeof(ISpPhraseAlt *)); hr = pResult->GetAlternates( 0, *pulNumElements, nthHypothesis, ppAlt, &cAlt ); } if (S_OK == hr) { UINT i; SPPHRASE *pPhrase; if (nthHypothesis > cAlt) { *pulNumElements = 0; goto no_more_alt; } Assert(nthHypothesis); // 1 based, can't be 0
hr = ((ppAlt)[nthHypothesis-1])->GetPhrase(&pPhrase); if (S_OK == hr) { for (i = 0; i < pPhrase->Rule.ulCountOfElements; i++ ) { int cchElement = wcslen(pPhrase->pElements[i].pszDisplayText) + 1;
WCHAR *szElement = new WCHAR[cchElement + 2];
if ( szElement ) { // add +2 for trailing spaces
ParseSRElementByLocale(szElement, cchElement+2, pPhrase->pElements[i].pszDisplayText, langid, pPhrase->pElements[i].bDisplayAttributes );
dsNext.Append(szElement);
delete [] szElement; } else { // Out of Memory.
// stop here.
break; } } // now i holds the number of elements that we want to use in the result
// object
*pulNumElements = i;
::CoTaskMemFree(pPhrase); } // if S_OK == GetPhrase
} // if S_OK == GetAlternates
no_more_alt: // Release phrase alternates objects.
for (UINT i = 0; i < cAlt; i++) { if (NULL != ((ppAlt)[i])) { ((ppAlt)[i])->Release(); } } // Free memory for array holding references to alternates objects.
if (ppAlt) { ::cicMemFree(ppAlt); }
} return *pulNumElements > 0; }
void CSapiIMX::_EnsureWorkerWnd(void) { if (!m_hwndWorker) { m_hwndWorker = CreateWindow(c_szWorkerWndClass, "", WS_POPUP, 0,0,0,0, NULL, 0, g_hInst, this); }
}
//
// CSapiIMX::_GetAppMainWnd
//
// This function gets the real main window of current application.
// This main window would be used as the parent window of Add/Delete dialog
// and Training wizard.
//
HWND CSapiIMX::_GetAppMainWnd(void) {
HWND hParentWnd = NULL; HWND hMainWnd = NULL;
hMainWnd = GetFocus( );
if ( hMainWnd != NULL ) { hParentWnd = GetParent(hMainWnd);
while ( hParentWnd != NULL ) { hMainWnd = hParentWnd; hParentWnd = GetParent(hMainWnd); } }
return hMainWnd; } //
// CSpTask::InitializeCallback
//
//
HRESULT CSpTask::InitializeCallback() { TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called");
if (m_fCallbackInitialized) { TraceMsg(TF_SAPI_PERF, "m_fCallbackInitialized is true"); return S_OK; }
if (!m_fSapiInitialized) return S_FALSE; // can't do this without SAPI
// set recognition notification
CComPtr<ISpNotifyTranslator> cpNotify; HRESULT hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator); TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for Reco is generated, hr=%x", hr);
// set this class instance to notify control object
if (SUCCEEDED(hr)) { m_pime->_EnsureWorkerWnd();
hr = cpNotify->InitCallback( NotifyCallback, 0, (LPARAM)this ); TraceMsg(TF_SAPI_PERF, "InitCallback is Done, hr=%x", hr); } if (SUCCEEDED(hr)) { hr = m_cpRecoCtxt->SetNotifySink(cpNotify); TraceMsg(TF_SAPI_PERF, "SetNotifySink is Done, hr=%x", hr); }
// set the events we're interested in
if( SUCCEEDED( hr ) ) { const ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) | SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_RECO_OTHER_CONTEXT) | SPFEI(SPEI_FALSE_RECOGNITION) | SPFEI(SPEI_HYPOTHESIS) | SPFEI(SPEI_RECO_STATE_CHANGE) | SPFEI(SPEI_INTERFERENCE);
hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest); TraceMsg(TF_SAPI_PERF, "SetInterest is Done, hr=%x", hr); }
if ( SUCCEEDED(hr) && m_cpVoice) { // set recognition notification
CComPtr<ISpNotifyTranslator> cpNotify; hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
TraceMsg(TF_SAPI_PERF, "Create SpNotifyTranslator for spVoice, hr=%x", hr);
// set this class instance to notify control object
if (SUCCEEDED(hr)) { m_pime->_EnsureWorkerWnd();
hr = cpNotify->InitCallback( SpeakNotifyCallback, 0, (LPARAM)this ); TraceMsg(TF_SAPI_PERF, "InitCallback for SpVoice, hr=%x", hr); } if (SUCCEEDED(hr)) { hr = m_cpVoice->SetNotifySink(cpNotify); TraceMsg(TF_SAPI_PERF, "SetNotifySink for SpVoice, hr=%x", hr); }
if ( hr == S_OK ) { const ULONGLONG ulInterestSpeak = SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_END_INPUT_STREAM); hr = m_cpVoice->SetInterest(ulInterestSpeak, ulInterestSpeak); TraceMsg(TF_SAPI_PERF, "SetInterest for spVoice, hr=%x", hr); } }
m_fCallbackInitialized = TRUE;
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called is done!!! hr=%x", hr);
return hr; }
//
// _LoadGrammars
//
// synopsis - load CFG for dictation and commands available during dictation
//
HRESULT CSpTask::_LoadGrammars() { HRESULT hr = E_FAIL;
TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is called"); if (m_cpRecoCtxt) { hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_DICT, &m_cpDictGrammar);
TraceMsg(TF_SAPI_PERF, "Create Dict Grammar, hr=%x", hr); if (SUCCEEDED(hr)) { hr = m_cpDictGrammar->LoadDictation(NULL, SPLO_STATIC); TraceMsg(TF_SAPI_PERF, "Load Dictation, hr = %x", hr); }
if ( S_OK == hr && m_langid != 0x0804) // Chinese Engine doesn't support SPTOPIC_SPELLING,
// This is the temporal workaround.
{ // we keep going regardless of availabillity of spelling topic
// in the SR engine for the language so we use internal HRESULT
// for this block of code
//
HRESULT hrInternal;
// Load Spelling topic
hrInternal = m_cpRecoCtxt->CreateGrammar(GRAM_ID_SPELLING, &m_cpSpellingGrammar);
TraceMsg(TF_SAPI_PERF, "Create Spelling grammar, hrInternal=%x", hrInternal);
if (SUCCEEDED(hrInternal)) { hrInternal = m_cpSpellingGrammar->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC); TraceMsg(TF_SAPI_PERF, "Load Spelling dictation grammar, hrInternal=%x", hrInternal); }
// this is now an experiment for English/Japanese only
//
if (SUCCEEDED(hrInternal)) { hrInternal = m_cpSpellingGrammar->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SPELLING_TOPIC_CFG), L"SRGRAMMAR", m_langid, SPLO_STATIC);
TraceMsg(TF_SAPI_PERF, "Load CFG grammar spell.cfg, hr=%x", hrInternal);
}
if (S_OK == hrInternal) { m_fSpellingModeEnabled = TRUE; } else m_fSpellingModeEnabled = FALSE;
TraceMsg(TF_SAPI_PERF, "m_fSpellingModeEnabled=%d", m_fSpellingModeEnabled); } //
// load the dictation mode commands
//
if (SUCCEEDED(hr) ) { hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CCDICT, &m_cpDictCmdGrammar); TraceMsg(TF_SAPI_PERF, "Create DictCmdGrammar, hr=%x", hr); } if (S_OK == hr) { hr = S_FALSE;
// try resource first because loading cmd from file takes
// quite long time
//
if (m_langid == 0x409 || // English
m_langid == 0x411 || // Japanese
m_langid == 0x804 ) // Simplified Chinese
{ hr = m_cpDictCmdGrammar->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG), L"SRGRAMMAR", m_langid, SPLO_DYNAMIC);
TraceMsg(TF_SAPI_PERF, "Load dictcmd.cfg, hr=%x", hr); }
if (S_OK != hr) { // in case if we don't have built-in grammar
_GetCmdFileName(m_langid); if (m_szCmdFile[0]) { hr = m_cpDictCmdGrammar->LoadCmdFromFile(m_szCmdFile, SPLO_DYNAMIC); } } if (S_OK != hr) { m_cpDictCmdGrammar.Release(); } }
// load shared command grammars
if (SUCCEEDED(hr) ) { hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInDict); TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammarInDict, hr=%x", hr); }
if (S_OK == hr) { hr = S_FALSE;
if (m_langid == 0x409 || // English
m_langid == 0x411 || // Japanese
m_langid == 0x804 ) // Simplified Chinese
{ hr = m_cpSharedGrammarInDict->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG), L"SRGRAMMAR", m_langid, SPLO_DYNAMIC);
TraceMsg(TF_SAPI_PERF, "Load Shrdcmd.cfg, hr=%x", hr); }
if (S_OK != hr) { // in case if we don't have built-in grammar
// it provides a way for customer to localize their grammars in different languages
_GetCmdFileName(m_langid); if (m_szShrdCmdFile[0]) { hr = m_cpSharedGrammarInDict->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC); } }
if (S_OK != hr) { m_cpSharedGrammarInDict.Release(); } else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH || PRIMARYLANGID(m_langid) == LANG_JAPANESE || PRIMARYLANGID(m_langid) == LANG_CHINESE) { // means this language's grammar support Textbuffer commands.
m_fSelectionEnabled = TRUE; }
} //
// load mode bias grammars
//
if (S_OK == hr) { hr = m_cpRecoCtxt->CreateGrammar(GRID_INTEGER_STANDALONE, &m_cpNumModeGrammar); TraceMsg(TF_SAPI_PERF, "Create NumModeGrammar, hr=%x", hr); } if (S_OK == hr) { hr = S_FALSE;
// try resource first because loading cmd from file takes
// quite long time
//
if ( m_langid == 0x409 // English
|| m_langid == 0x411 // Japanese
|| m_langid == 0x804 // Simplified Chinese
) { hr = m_cpNumModeGrammar->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_NUMMODE_COMMAND_CFG), L"SRGRAMMAR", m_langid, SPLO_DYNAMIC);
TraceMsg(TF_SAPI_PERF, "Load dictnum.cfg, hr=%x", hr); }
if (S_OK != hr) { // in case if we don't have buit-in grammar
//
if (m_szNumModeCmdFile[0]) { hr = m_cpNumModeGrammar->LoadCmdFromFile(m_szNumModeCmdFile, SPLO_DYNAMIC); } }
if (S_OK != hr) { m_cpNumModeGrammar.Release(); } } }
// By default, Activate all the grammars and Disable the context for Perfomance.
if ( SUCCEEDED(hr) ) { hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED); m_fDictCtxtEnabled = FALSE; }
// Activate Dictation and spell.
if ( SUCCEEDED(hr) ) { hr = _ActiveDictOrSpell(DC_Dictation, TRUE);
if ( hr == S_OK ) hr = _ActiveDictOrSpell(DC_Dict_Spell, TRUE); }
// Automatically active all rules in C&C grammars.
if ( SUCCEEDED(hr) ) { if ( m_pime->_AllDictCmdsDisabled( ) ) { hr = _ActivateCmdInDictMode(FALSE);
// Still needs to activate spelling grammar if it exists.
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
// Needs to activate "Force Num" grammar in dication strong mode.
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE); } else { if ( m_pime->_AllCmdsEnabled( ) ) hr = _ActivateCmdInDictMode(TRUE); else { // Some category commands are disabled.
// active them individually.
hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
if ( hr == S_OK ) hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE); } } }
// we don't fail even if C&C grammars are not available
TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is done!!!!"); return S_OK; }
WCHAR * CSpTask::_GetCmdFileName(LANGID langid) {
if (!m_szCmdFile[0]) { _GetCmdFileName(langid, m_szCmdFile, ARRAYSIZE(m_szCmdFile), IDS_CMD_FILE); }
// load the name of shared commands grammar
if (!m_szShrdCmdFile[0]) { _GetCmdFileName(langid, m_szShrdCmdFile, ARRAYSIZE(m_szShrdCmdFile), IDS_SHARDCMD_FILE); }
// load the name of optional grammar
if (!m_szNumModeCmdFile[0]) { _GetCmdFileName(langid, m_szNumModeCmdFile, ARRAYSIZE(m_szNumModeCmdFile), IDS_NUMMODE_CMD_FILE ); } return m_szCmdFile; }
void CSpTask::_GetCmdFileName(LANGID langid, WCHAR *sz, int cch, DWORD dwId) { /*
// now we only have a command file for English/Japanese
// when cfgs are available, we'll get the name of cmd file
// and the rule names from resources using findresourceex
//
if ((PRIMARYLANGID(langid) == LANG_ENGLISH) || (PRIMARYLANGID(langid) == LANG_JAPANESE) || (PRIMARYLANGID(langid) == LANG_CHINESE)) {
// To supply customers a way to localize their grammars in different languages,
// we don't want the above condition check.
*/ char szFilePath[MAX_PATH]; char *pszFileName; char szCp[MAX_PATH]; int ilen;
if (!GetModuleFileName(g_hInst, szFilePath, ARRAYSIZE(szFilePath))) return; // is this dbcs safe?
pszFileName = strrchr(szFilePath, (int)'\\'); if (pszFileName) { pszFileName++; *pszFileName = '\0'; } else { szFilePath[0] = '\\'; szFilePath[1] = '\0'; pszFileName = &szFilePath[1]; }
ilen = lstrlen(szFilePath); CicLoadStringA(g_hInst, dwId, pszFileName, ARRAYSIZE(szFilePath)-ilen); if (GetLocaleInfo(langid, LOCALE_IDEFAULTANSICODEPAGE, szCp, ARRAYSIZE(szCp))>0) { int iACP = atoi(szCp); MultiByteToWideChar(iACP, NULL, szFilePath, -1, sz, cch); } // }
}
void CSpTask::_ReleaseSAPI(void) { // - release data or memory from recognition context
// - release interfaces if they are not defined as CComPtr
_UnloadGrammars();
m_cpResMgr.Release();
if ( m_cpVoice) m_cpVoice->SetNotifySink(NULL);
m_cpVoice.Release();
if (m_cpRecoCtxt) m_cpRecoCtxt->SetNotifySink(NULL);
if ( m_cpRecoCtxtForCmd ) m_cpRecoCtxtForCmd->SetNotifySink(NULL);
#ifdef RECOSLEEP
if ( m_pSleepClass ) { delete m_pSleepClass; m_pSleepClass = NULL; } #endif
m_cpRecoCtxt.Release(); m_cpRecoCtxtForCmd.Release(); m_cpRecoEngine.Release(); m_fSapiInitialized = FALSE; }
HRESULT CSpTask::_SetAudioRetainStatus(BOOL fRetain) { HRESULT hr = E_FAIL; // FutureConsider: support the data format
if (m_cpRecoCtxt) hr = m_cpRecoCtxt->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL);
if (m_cpRecoCtxtForCmd) hr = m_cpRecoCtxtForCmd->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL); return hr; }
HRESULT CSpTask::_SetRecognizerInterest(ULONGLONG ulInterest) { HRESULT hr = S_OK;
if ( m_cpRecoCtxt ) { hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest); }
return hr; } //
//
// Activate all the command grammas in Dictation mode.
//
// By default we want to set SPRS_ACTIVE to all the command grammar rules
// in dictation mode unless user disables some of commands through dictation
// property page.
//
// Please note: Only when all the commands are enabled, this function is called.
//
// otherwise,
//
// When some of the commands are disabled, we should active individual cateogry commands by
// calling _ActiveCategoryCmds( ).
//
HRESULT CSpTask::_ActivateCmdInDictMode(BOOL fActive) { HRESULT hr = E_FAIL; BOOL fRealActive = fActive;
TraceMsg(TF_SAPI_PERF, "CSpTask::_ActivateCmdInDictMode is called, fActive=%d", fActive);
if (m_cpRecoCtxt) { // Automatically active or inactive all rules in grammar.
// Rules in Dictcmd.cfg
if ( m_cpDictCmdGrammar ) { hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in DictCmdGrammar, fRealActive=%d", fRealActive); }
// Rules in Sharedcmd.cfg
if ( m_cpSharedGrammarInDict ) { hr = m_cpSharedGrammarInDict->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in SharedCmdGrammar In Dictation Mode, fRealActive=%d", fRealActive); }
// Rules in ITN grammar
if ( hr == S_OK && m_cpNumModeGrammar ) { hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpNumModeGrammar, fRealActive=%d", fRealActive); }
// Rules in Spell grammar
if ( m_cpSpellingGrammar ) { hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fRealActive=%d", fRealActive); } } TraceMsg(TF_SAPI_PERF, "Exit from CSpTask::_ActivateCmdInDictMode");
return hr; }
//
// Active commands by category.
//
// Some commands are dictation mode only such as "spell that" and Number mode commands.
// some others are available in both modes,
//
// When some category commands are disabled, caller must call this function instead of
// _ActivateCmdInDictMode to set individual category commands.
//
HRESULT CSpTask::_ActiveCategoryCmds(DICT_CATCMD_ID dcId, BOOL fActive, DWORD dwMode) { HRESULT hr = S_OK; BOOL fActiveDictMode, fActiveCommandMode;
if ( dcId >= DC_Max ) return E_INVALIDARG;
if (m_fIn_Activate) return hr;
fActiveDictMode = (m_cpRecoCtxt && (dwMode & ACTIVE_IN_DICTATION_MODE) ) ? TRUE : FALSE;
if ( m_cpRecoCtxtForCmd && m_cpSharedGrammarInVoiceCmd && ( dwMode & ACTIVE_IN_COMMAND_MODE) ) fActiveCommandMode = TRUE; else fActiveCommandMode = FALSE;
m_fIn_Activate = TRUE;
switch (dcId) { case DC_CC_SelectCorrect :
// This category includes below rules in different grammars.
//
// shrdcmd.xml:
// selword, SelectThrough, SelectSimpleCmds,
//
// dictcmd.xml:
// commands
//
TraceMsg(TF_SAPI_PERF, "DC_CC_SelectCorrect status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode) { // for dictation mode
if ( m_cpSharedGrammarInDict ) { hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
if ( hr == S_OK ) hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
if ( hr == S_OK ) hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); } }
if ( (hr == S_OK) && fActiveCommandMode ) { // for voice command mode
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
if ( hr == S_OK ) hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
if ( hr == S_OK ) hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
break;
case DC_CC_Navigation :
// This category includes rule NavigationCmds in shrdcmd.xml
//
TraceMsg(TF_SAPI_PERF, "DC_CC_Navigation status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode && m_cpSharedGrammarInDict) { // for dictation mode
hr = m_cpSharedGrammarInDict->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
if ( (hr == S_OK) && fActiveCommandMode ) { // for voice command mode
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); } break;
case DC_CC_Casing :
// This category includes rule CasingCmds in shrdcmd.xml
TraceMsg(TF_SAPI_PERF, "DC_CC_Casing status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode && m_cpSharedGrammarInDict ) { // for dictation mode
hr = m_cpSharedGrammarInDict->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
if ( (hr == S_OK) && fActiveCommandMode) { // for voice command mode
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
break;
case DC_CC_Editing :
// This category includes rule EditCmds in shrdcmd.xml
TraceMsg(TF_SAPI_PERF, "DC_CC_Editing status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode && m_cpSharedGrammarInDict) { // for dictation mode
hr = m_cpSharedGrammarInDict->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
if ( (hr == S_OK) && fActiveCommandMode) { // for voice command mode
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
break;
case DC_CC_Keyboard :
// This category includes rule KeyboardCmds in shrdcmd.xml
TraceMsg(TF_SAPI_PERF, "DC_CC_Keyboard status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode && m_cpSharedGrammarInDict) { // for dictation mode
hr = m_cpSharedGrammarInDict->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
if ( (hr == S_OK) && fActiveCommandMode) { // for voice command mode
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
break;
case DC_CC_TTS :
// The rule for this category is not implemented yet!!!
break;
case DC_CC_LangBar :
// This category includes rule ToolbarCmd in dictcmd.xml for dictation mode.
// for voice command mode, it is a dynamical rule.
//
TraceMsg(TF_SAPI_PERF, "DC_CC_LangBar status: %d, mode=%d", fActive, dwMode);
if ( fActiveDictMode && m_cpDictCmdGrammar) { // for dictation mode
hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); }
if ( (hr == S_OK) && fActiveCommandMode ) { // for voice command mode
// Change toolbar grammar status if it has already been built.
if (m_pLangBarSink && m_pLangBarSink->_IsTBGrammarBuiltOut( )) m_pLangBarSink->_ActivateGrammar(fActive); }
break;
case DC_CC_Num_Mode : if (fActiveDictMode && m_cpNumModeGrammar) { hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "CC Number rule status changed to %d", fActive); } break;
case DC_CC_UrlHistory : if (fActiveDictMode && m_cpDictCmdGrammar) { hr = m_cpDictCmdGrammar->SetRuleState(c_szStaticUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE); if (S_OK == hr) hr = m_cpDictCmdGrammar->SetRuleState(c_szDynUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE); if (S_OK == hr && m_cpUrlSpellingGrammar) { hr = m_cpUrlSpellingGrammar->SetRuleState(c_szStaticUrlSpell, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
} } break;
case DC_CC_Spelling :
if ( fActiveDictMode && m_cpSpellingGrammar ) { hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fActive=%d", fActive); } break; }
m_fIn_Activate = FALSE;
return hr; }
// Set the status for Dictation grammar or spelling grammar In Dictation mode only.
HRESULT CSpTask::_ActiveDictOrSpell(DICT_CATCMD_ID dcId, BOOL fActive) { HRESULT hr = S_OK;
if ( dcId >= DC_Max ) return E_INVALIDARG;
if (m_fIn_Activate) return hr;
m_fIn_Activate = TRUE;
switch (dcId) { case DC_Dictation : if (m_cpDictGrammar) { hr = m_cpDictGrammar->SetDictationState(fActive ? SPRS_ACTIVE : SPRS_INACTIVE); TraceMsg(TF_SAPI_PERF, "Dictation status changed to %d", fActive); } break; case DC_Dict_Spell : if (m_cpSpellingGrammar) { hr = _SetSpellingGrammarStatus(fActive); TraceMsg(TF_SAPI_PERF, "Dict Spell status changed to %d", fActive); } break; }
m_fIn_Activate = FALSE;
return hr; }
HRESULT CSpTask::_SetSpellingGrammarStatus( BOOL fActive, BOOL fForce) { HRESULT hr = S_OK;
TraceMsg(TF_GENERAL, "_SetSpellingGrammarStatus is called, fActive=%d, m_fSelectStatus=%d",fActive, m_fSelectStatus);
if ( m_cpSpellingGrammar ) { // if dictation is previously deactivated because of 'force' spelling
// we need to reactivate the dictation grammar
if (m_fDictationDeactivated) { hr = _ActiveDictOrSpell(DC_Dictation, TRUE); if (S_OK == hr) { m_fDictationDeactivated = FALSE; } }
// if this is 'force' mode, we deactivate dictation for the moment
if (fForce) { hr = _ActiveDictOrSpell(DC_Dictation, FALSE); if (S_OK == hr) { m_fDictationDeactivated = TRUE; } }
if ( (m_fSelectStatus || fForce) && fActive) // It is not empty
hr = m_cpSpellingGrammar->SetDictationState(SPRS_ACTIVE); else hr = m_cpSpellingGrammar->SetDictationState(SPRS_INACTIVE); }
return hr; }
HRESULT CSpTask::_AddUrlPartsToGrammar(STATURL *pStat) { Assert(pStat);
// get a url broken down to pieces
if (!pStat->pwcsUrl) return S_FALSE;
WCHAR *pch = pStat->pwcsUrl; const WCHAR c_szHttpSlash2[] = L"http://";
// skip the prefixed http: stuff because we've already added it by now
if (_wcsnicmp(pch, c_szHttpSlash2, ARRAYSIZE(c_szHttpSlash2)-1) == 0) pch += ARRAYSIZE(c_szHttpSlash2)-1;
WCHAR *pchWord = pch; HRESULT hr = S_OK;
// an assumption 1) people speak the first portion of URL www.microsoft.com
// as a sentence
WCHAR *pchUrl = pch; // points either biggining of url or
// right after 'http://' add the first part
BOOL fUrlAdded = FALSE; // of url that is between after this and '/'
while(S_OK == hr && *pch) { if (*pch == L'/') { if (!fUrlAdded) { if( pch - pchUrl > 1) { WCHAR ch = *pch; *pch = L'\0';
SPPROPERTYINFO pi = {0}; pi.pszValue = pchUrl; hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi); *pch = ch; } fUrlAdded = TRUE; } else { *pch = L'\0';
break; } }
if (*pch == L'.' || *pch == L'/' || *pch == L'?' || *pch == '=' || *pch =='&') { WCHAR ch = *pch;
*pch = L'\0';
// reject 1 character parts
if (pch - pchWord > 1) { SPPROPERTYINFO pi = {0}; pi.pszValue = pchWord;
if (wcscmp(c_szWWW, pchWord) != 0 && wcscmp(c_szCom, pchWord) != 0) { // a few words can possibly return 'ambiguity' errors
// we need to ignore it and continue. so we're not checking
// the return here.
//
m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi); } } *pch = ch;
pchWord = pch + 1; } pch++; }
// add the last part of URL
if (S_OK == hr && *pchWord && pch - pchWord > 1) { SPPROPERTYINFO pi = {0}; pi.pszValue = pchWord; hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi); } // add the first part of url if we haven't yet
if (S_OK == hr && !fUrlAdded && pch - pchUrl > 1) { SPPROPERTYINFO pi = {0}; pi.pszValue = pchUrl; hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi); }
return hr; }
BOOL CSpTask::_EnsureModeBiasGrammar() { HRESULT hr = S_OK;
if ( m_cpDictCmdGrammar ) { // Check if the grammar has a static rule UrlSpelling
SPSTATEHANDLE hRuleUrlSpell = 0; hr = m_cpDictCmdGrammar->GetRule(c_szStaticUrlSpell, 0, SPRAF_TopLevel|SPRAF_Active, FALSE, &hRuleUrlSpell);
if ( !hRuleUrlSpell ) return FALSE; }
// ensure spelling LM
if (!m_cpUrlSpellingGrammar) { CComPtr<ISpRecoGrammar> cpUrlSpelling; hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_URLSPELL, &cpUrlSpelling);
// load dictation with spelling topic
if (S_OK == hr) { hr = cpUrlSpelling->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC); }
// load the 'rule' for the free form dictation
if (S_OK == hr) { // i'm sharing the command cfg here for spelling with dictation
// command for simplicity
//
hr = cpUrlSpelling->LoadCmdFromResource( g_hInstSpgrmr, (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG), L"SRGRAMMAR", m_langid, SPLO_STATIC); }
if (S_OK == hr) { m_cpUrlSpellingGrammar = cpUrlSpelling; // add the ref count
} }
if (m_hRuleUrlHist) { hr = m_cpDictCmdGrammar->ClearRule(m_hRuleUrlHist); m_hRuleUrlHist = 0; }
if (S_OK == hr) hr = m_cpDictCmdGrammar->GetRule(c_szDynUrlHist, 0, SPRAF_TopLevel|SPRAF_Active|SPRAF_Dynamic, TRUE, &m_hRuleUrlHist);
// first add basic parts for URL
CComPtr<IUrlHistoryStg> cpUrlHistStg; if (S_OK == hr) { hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER, IID_IUrlHistoryStg, (void **)&cpUrlHistStg); }
CComPtr<IEnumSTATURL> cpEnumUrl; if (S_OK == hr) { hr = cpUrlHistStg->EnumUrls(&cpEnumUrl); }
if (S_OK == hr) { int i = 0; STATURL stat; stat.cbSize = SIZEOF(stat.cbSize); while(i < 10 && S_OK == hr && cpEnumUrl->Next(1, &stat, NULL)==S_OK && stat.pwcsUrl) { hr = _AddUrlPartsToGrammar(&stat); i++; } }
if (S_OK == hr) { hr = m_cpDictCmdGrammar->Commit(0); }
return (S_OK == hr) ? TRUE : FALSE; }
HRESULT CSpTask::_SetModeBias(BOOL fActive, REFGUID rGuid) { HRESULT hr = S_OK; BOOL fKillDictation = FALSE;
if (m_fIn_SetModeBias) return E_FAIL;
m_fIn_SetModeBias = TRUE; if (m_cpDictGrammar) { fKillDictation = !m_pime->_IsModeBiasDictationEnabled(); if (fActive) { BOOL fUrlHistory = FALSE;
if (IsEqualGUID(GUID_MODEBIAS_URLHISTORY, rGuid) || IsEqualGUID(GUID_MODEBIAS_FILENAME, rGuid)) fUrlHistory = TRUE;
// first deactivate rules when we are not setting them
if (!fUrlHistory && m_fUrlHistoryMode) { hr = _ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE); }
// this check with m_fUrlHistoryMode is preventing us from updating url dynamic grammar
// when mic is re-opened. we think removing this won't cause much perf degredation.
//
if (fUrlHistory /* && !m_fUrlHistoryMode */) { if (m_cpDictCmdGrammar && m_pime->GetDICTATIONSTAT_DictOnOff() && _EnsureModeBiasGrammar()) { hr = _ActiveCategoryCmds(DC_CC_UrlHistory, TRUE, ACTIVE_IN_DICTATION_MODE); } else fUrlHistory = FALSE;
if (fUrlHistory) { fKillDictation = TRUE; } }
// sync the global status
m_fUrlHistoryMode = fUrlHistory; } else { // reset all modebias
if (m_fUrlHistoryMode) _ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE); }
// kill dictation grammar when mode requires it
// we should activate dictation only when deactivating
// the modebias grammar *and* when we are the focus
// thread because we've already deactivated dictation
// when focus switched away
//
if (/* !fActive && */ m_cpDictGrammar && m_pime->GetDICTATIONSTAT_DictOnOff() && S_OK == m_pime->IsActiveThread()) { #ifdef _DEBUG_
TCHAR szModule[MAX_PATH]; GetModuleFileName(NULL, szModule, ARRAYSIZE(szModule));
TraceMsg(TF_GENERAL, "%s : CSpTask::_SetModeBias() - Turning Dictation grammar %s", szModule, fKillDictation ? "Off" : "On"); #endif
if (!fActive && fKillDictation) { fKillDictation = FALSE; }
hr = _ActiveDictOrSpell(DC_Dictation, fKillDictation ? SPRS_INACTIVE : SPRS_ACTIVE); } } m_fIn_SetModeBias = FALSE; return hr; }
void CSpTask::_SetInputOnOffState(BOOL fOn) { TraceMsg(TF_GENERAL, "_SetInputOnOffState is called, fOn=%d", fOn);
if (m_fIn_SetInputOnOffState) return;
m_fIn_SetInputOnOffState = TRUE;
// here we make sure we erase feedback UI
// We only adjust these if we are the active thread. Otherwise leave in current state since we either do
// not have focus or the stage is visible. This maintains our previous behavior where we would not get here
// if we were not the active thread.
if (S_OK == m_pime->IsActiveThread()) { if (fOn) { if (!m_pime->Get_SPEECH_DISABLED_DictationDisabled() && m_pime->GetDICTATIONSTAT_DictOnOff()) _SetDictRecoCtxtState(TRUE);
if ( !m_pime->Get_SPEECH_DISABLED_CommandingDisabled( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( ) ) _SetCmdRecoCtxtState(TRUE); } else { _SetDictRecoCtxtState(FALSE); _SetCmdRecoCtxtState(FALSE); _StopInput(); } }
// Regardless of focus / stage visibility, we need to turn on the engine if necessary here since there
// may be *no* speech tip with focus to do this. This means we may have multiple tips turning the reco
// state on / off simultaneously.
if(m_cpRecoEngine) { m_fInputState = fOn;
if ( _GetInputOnOffState( ) != fOn ) { TraceMsg(TF_GENERAL, "Call SetRecoState, %s", fOn ? "SPRST_ACTIVE" : "SPRST_INACTIVE");
m_cpRecoEngine->SetRecoState(fOn ? SPRST_ACTIVE : SPRST_INACTIVE); }
// DO NOT ADD DEBUGGING CODE HERE TO PRINT OUT STATE - CAN BLOCK CICERO RESULTING
// IN DIFFERENT BEHAVIOR TO THE RELEASE VERSION.
}
m_fIn_SetInputOnOffState = FALSE; }
BOOL CSpTask::_GetInputOnOffState(void) { BOOL fRet = FALSE;
if(m_cpRecoEngine) { SPRECOSTATE srs; m_cpRecoEngine->GetRecoState(&srs);
if (srs == SPRST_ACTIVE) { fRet = TRUE; // on
} else if (srs == SPRST_INACTIVE) { fRet = FALSE; // off
} // anything else is 'off'
} return fRet; }
HRESULT CSpTask::_StopInput(void) { HRESULT hr = S_OK;
TraceMsg(TF_SAPI_PERF, "_StopInput is called");
if (!m_bInSound && m_pime->GetDICTATIONSTAT_DictOnOff()) { TraceMsg(TF_SAPI_PERF, "m_bInSound is FALSE, GetDICTATIONSTAT_DictOnOff returns TRUE");
return S_OK; }
m_pime->EraseFeedbackUI();
_ShowDictatingToBalloon(FALSE);
return S_OK; }
// _ClearQueuedRecoEvent(void)
//
// synopsis: get rid of remaining events from reco context's
// event queue. This is only called from _StopInput()
// when TerminateComposition() is called, or Mic is
// turned off
//
//
void CSpTask::_ClearQueuedRecoEvent(void) { if (m_cpRecoCtxt) { SPEVENTSOURCEINFO esi;
if (S_OK == m_cpRecoCtxt->GetInfo(&esi)) { ULONG ulcount = esi.ulCount; CSpEvent event; while(ulcount > 0) { if (S_OK == event.GetFrom(m_cpRecoCtxt)) { event.Clear(); } ulcount--; } } } }
// CSpTask::GetResltObjectFromStream()
//
// synopsis - a wrapper function that takes a stream ptr to a SAPI result blob
// and gets alternates out of the object
//
HRESULT CSpTask::GetResultObjectFromStream(IStream *pStream, ISpRecoResult **ppResult) { LARGE_INTEGER li0 = {0, 0}; HRESULT hr = E_INVALIDARG; SPSERIALIZEDRESULT *pPhraseBlob = 0; if (pStream) { hr = pStream->Seek(li0, STREAM_SEEK_SET, NULL);
STATSTG stg; if (hr == S_OK) { hr = pStream->Stat(&stg, STATFLAG_NONAME); } if (SUCCEEDED(hr)) pPhraseBlob = (SPSERIALIZEDRESULT *)CoTaskMemAlloc(stg.cbSize.LowPart+sizeof(ULONG)*4);
if (pPhraseBlob) hr = pStream->Read(pPhraseBlob, stg.cbSize.LowPart, NULL); else hr = E_OUTOFMEMORY;
if (SUCCEEDED(hr)) { ISpRecoResult *pResult;
if (SUCCEEDED(m_cpRecoCtxt->DeserializeResult(pPhraseBlob, &pResult)) && pResult) { if (ppResult) { pResult->AddRef(); *ppResult = pResult; } pResult->Release(); } } } return hr; }
//
// GetAlternates
//
HRESULT CSpTask::GetAlternates(CRecoResultWrap *pResultWrap, ULONG ulStartElem, ULONG ulcElem, ISpPhraseAlt **ppAlt, ULONG *pcAlt, ISpRecoResult **ppRecoResult) { HRESULT hr = E_INVALIDARG;
if (m_fIn_GetAlternates) return E_FAIL; m_fIn_GetAlternates = TRUE;
Assert(pResultWrap); Assert(ppAlt); Assert(pcAlt); hr = pResultWrap->GetResult(ppRecoResult); if (S_OK == hr && *ppRecoResult) { hr = (*ppRecoResult)->GetAlternates( ulStartElem, ulcElem, *pcAlt, ppAlt, /* [out] ISpPhraseAlt **ppPhrases, */ pcAlt /* [out] ULONG *pcPhrasesReturned */ ); } m_fIn_GetAlternates = FALSE; return hr; }
HRESULT CSpTask::_SpeakText(WCHAR *pwsz) { HRESULT hr = E_FAIL;
if (m_cpVoice) hr = m_cpVoice->Speak( pwsz, /* SPF_DEFAULT */ SPF_ASYNC /* | SPF_PURGEBEFORESPEAK*/, NULL );
return hr; }
HRESULT CSpTask::_SpeakAudio( ISpStreamFormat *pStream ) { HRESULT hr = E_FAIL;
if ( !pStream ) return E_INVALIDARG;
if (m_cpVoice) hr = m_cpVoice->SpeakStream(pStream, SPF_ASYNC, NULL);
return hr; }
void CSapiIMX::RegisterWorkerClass(HINSTANCE hInstance) { WNDCLASSEX wndclass;
memset(&wndclass, 0, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = CSapiIMX::_WndProc; wndclass.lpszClassName = c_szWorkerWndClass; wndclass.cbWndExtra = 8; RegisterClassEx(&wndclass); }
//
// ParseSRElementByLocale
//
// Parse SR result elements in locale specific mannar
//
// dependency caution: this function has to be rewritten when SAPI5 changes
// SR element format, which is very likely
//
// 12/15/99 : As of SAPI1214, an element now includes display text,
// lexical form, pronunciation separately. this function
// takes the display text at szSrc.
// langid parameter is not used at this moment
//
HRESULT CSpTask::ParseSRElementByLocale(WCHAR *szDst, int cchDst, const WCHAR * szSrc, LANGID langid, BYTE bAttr) { if (!szDst || !szSrc || !cchDst) { return E_INVALIDARG; }
// handle leading space.
if (bAttr & SPAF_CONSUME_LEADING_SPACES) { const WCHAR *psz = szSrc; while(*psz && *psz == L' ') { psz++; } szSrc = psz; } wcsncpy(szDst, szSrc, cchDst - 2); // -2 for possible sp
// handle trailing space
if (bAttr & SPAF_ONE_TRAILING_SPACE) { StringCchCatW(szDst, cchDst, L" "); } else if (bAttr & SPAF_TWO_TRAILING_SPACES) { StringCchCatW(szDst,cchDst, L" "); }
return S_OK; }
//
// FeedDictContext
//
// synopsis: feed the text surrounding the current IP to SR engine
//
void CSpTask::FeedDictContext(CDictContext *pdc) { Assert(pdc); if (!m_pime->_GetWorkerWnd()) { delete pdc; return ; } // wait until the current feeding is done
// it's not efficient to feed IP everytime user
// click around
if (m_pdc) { delete pdc; return; }
if (!m_cpDictGrammar) { delete pdc; return; }
// remove unprocessed messages from the queue
// FutureConsider: this could be moved to wndproc so that
// we can remove this private msg at the
// moment we process it. It depends on
// profiling we'll do.
//
MSG msg; while(PeekMessage(&msg, m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, WM_PRIV_FEEDCONTEXT, TRUE)) ; // queue up the context
m_pdc = pdc; PostMessage(m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, 0, (LPARAM)TRUE); }
void CSpTask::CleanupDictContext(void) { if (m_pdc) delete m_pdc;
m_pdc = NULL; }
// _UpdateBalloon( )
void CSpTask::_UpdateBalloon(ULONG uidBalloon, ULONG uidBalloonTooltip) { WCHAR wszBalloonText[MAX_PATH] = {0}; WCHAR wszBalloonTooltip[MAX_PATH] = {0};
#ifndef RECOSLEEP
if (!m_pime->GetSpeechUIServer()) #else
if (!m_pime->GetSpeechUIServer() || IsInSleep( )) #endif
return;
CicLoadStringWrapW(g_hInst, uidBalloon, wszBalloonText, ARRAYSIZE(wszBalloonText)); CicLoadStringWrapW(g_hInst, uidBalloonTooltip, wszBalloonTooltip, ARRAYSIZE(wszBalloonTooltip));
if (wszBalloonText[0] && wszBalloonTooltip[0]) { m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, wszBalloonText, -1, wszBalloonTooltip, -1 ); }
return; }
//
// ShowDictatingToBalloon
//
//
void CSpTask::_ShowDictatingToBalloon(BOOL fShow) { #ifndef RECOSLEEP
if (!m_pime->GetSpeechUIServer()) #else
if (!m_pime->GetSpeechUIServer() || IsInSleep( )) #endif
return;
static WCHAR s_szDictating[MAX_PATH] = {0}; static WCHAR s_szDictatingTooltip[MAX_PATH] = {0};
if (!s_szDictating[0]) { CicLoadStringWrapW(g_hInst, IDS_DICTATING, s_szDictating, ARRAYSIZE(s_szDictating)); } if (!s_szDictatingTooltip[0]) { CicLoadStringWrapW(g_hInst, IDS_DICTATING_TOOLTIP, s_szDictatingTooltip, ARRAYSIZE(s_szDictatingTooltip)); }
if (fShow && s_szDictating[0] && s_szDictatingTooltip[0]) { m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, s_szDictating, -1, s_szDictatingTooltip, -1 ); } else if (!fShow) { m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, L" ", -1, L" ", -1 ); } } //
// _HandleInterference
//
// synopsis: bubble up reco errors to the balloon UI
//
//
void CSpTask::_HandleInterference(ULONG lParam) { if (!m_pime->GetSpeechUIServer()) return;
WCHAR sz[MAX_PATH]; WCHAR szTooltip[MAX_PATH]; if (S_OK == _GetLocSRErrorString((SPINTERFERENCE)lParam, sz, ARRAYSIZE(sz), szTooltip, ARRAYSIZE(szTooltip))) { m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, sz, -1, szTooltip, -1 ); } }
HRESULT CSpTask::_GetLocSRErrorString ( SPINTERFERENCE sif, WCHAR *psz, ULONG cch, WCHAR *pszTooltip, ULONG cchTooltip ) { HRESULT hr = E_FAIL;
static struct { ULONG uidRes; WCHAR szErr[MAX_PATH]; ULONG uidResTooltip; WCHAR szTooltip[MAX_PATH]; } rgIntStr[] = { {IDS_INT_NONE, {0}, IDS_INTTOOLTIP_NONE, {0}}, {IDS_INT_NOISE, {0}, IDS_INTTOOLTIP_NOISE, {0}}, {IDS_INT_NOSIGNAL, {0}, IDS_INTTOOLTIP_NOSIGNAL, {0}}, {IDS_INT_TOOLOUD, {0}, IDS_INTTOOLTIP_TOOLOUD, {0}}, {IDS_INT_TOOQUIET, {0}, IDS_INTTOOLTIP_TOOQUIET, {0}}, {IDS_INT_TOOFAST, {0}, IDS_INTTOOLTIP_TOOFAST, {0}}, {IDS_INT_TOOSLOW, {0}, IDS_INTTOOLTIP_TOOSLOW, {0}} }; if ((ULONG)sif < ARRAYSIZE(rgIntStr)-1) { if (!rgIntStr[sif].szErr[0]) { hr = CicLoadStringWrapW(g_hInst, rgIntStr[sif].uidRes, rgIntStr[sif].szErr, ARRAYSIZE(rgIntStr[0].szErr)) > 0 ? S_OK : E_FAIL; if (S_OK == hr) { hr = CicLoadStringWrapW(g_hInst, rgIntStr[sif].uidResTooltip, rgIntStr[sif].szTooltip, ARRAYSIZE(rgIntStr[0].szTooltip)) > 0 ? S_OK : E_FAIL; } } else hr = S_OK; // the value is cached
} if (S_OK == hr) { Assert(psz); wcsncpy(psz, rgIntStr[sif].szErr, cch);
Assert(pszTooltip); wcsncpy(pszTooltip, rgIntStr[sif].szTooltip, cchTooltip); } return hr; }
|