Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3304 lines
101 KiB

  1. //
  2. // sptask.cpp
  3. //
  4. // implements a notification callback ISpTask
  5. //
  6. // created: 4/30/99
  7. //
  8. //
  9. #include "private.h"
  10. #include "globals.h"
  11. #include "sapilayr.h"
  12. #include "propstor.h"
  13. #include "dictctxt.h"
  14. #include "nui.h"
  15. #include "mui.h"
  16. #include "shlguid.h"
  17. #include "spgrmr.h"
  18. //
  19. //
  20. // CSpTask class impl
  21. //
  22. //
  23. STDMETHODIMP CSpTask::QueryInterface(REFIID riid, void **ppvObj)
  24. {
  25. *ppvObj = NULL;
  26. if (IsEqualIID(riid, IID_IUnknown)
  27. /* || IsEqualIID(riid, IID_ISpNotifyCallback) */
  28. )
  29. {
  30. *ppvObj = SAFECAST(this, CSpTask *);
  31. }
  32. if (*ppvObj)
  33. {
  34. AddRef();
  35. return S_OK;
  36. }
  37. return E_NOINTERFACE;
  38. }
  39. STDMETHODIMP_(ULONG) CSpTask::AddRef(void)
  40. {
  41. return ++m_cRef;
  42. }
  43. STDMETHODIMP_(ULONG) CSpTask::Release(void)
  44. {
  45. long cr;
  46. cr = --m_cRef;
  47. Assert(cr >= 0);
  48. if (cr == 0)
  49. {
  50. delete this;
  51. }
  52. return cr;
  53. }
  54. //
  55. // ctor
  56. //
  57. //
  58. CSpTask::CSpTask(CSapiIMX *pime)
  59. {
  60. // CSpTask is initialized with an TFX instance
  61. // so store the pointer to the TFX
  62. TraceMsg(TF_SAPI_PERF, "CSpTask is generated");
  63. m_pime = pime;
  64. // addref so it doesn't go away during session
  65. m_pime->AddRef();
  66. // init data members here
  67. m_cpResMgr = NULL;
  68. m_cpRecoCtxt = NULL;
  69. m_cpRecoCtxtForCmd = NULL;
  70. m_cpRecoEngine = NULL;
  71. m_cpVoice = NULL;
  72. m_bInSound = NULL;
  73. m_bGotReco = NULL;
  74. m_fSapiInitialized = FALSE;
  75. m_fDictationReady = FALSE;
  76. m_fInputState = FALSE;
  77. m_pLangBarSink = NULL;
  78. // M2 SAPI workaround
  79. m_fIn_Activate = FALSE;
  80. m_fIn_SetModeBias = FALSE;
  81. m_fIn_GetAlternates = FALSE;
  82. m_fIn_SetInputOnOffState = FALSE;
  83. m_fSelectStatus = FALSE; // By default, current selection is empty.
  84. m_fDictationDeactivated = FALSE;
  85. m_fSpellingModeEnabled = FALSE;
  86. m_fCallbackInitialized = FALSE;
  87. m_fSelectionEnabled = FALSE;
  88. m_fDictationInitialized = FALSE;
  89. m_fDictCtxtEnabled = FALSE;
  90. m_fCmdCtxtEnabled = FALSE;
  91. m_fTestedForOldMicrosoftEngine = FALSE;
  92. m_fOldMicrosoftEngine = FALSE;
  93. #ifdef RECOSLEEP
  94. m_pSleepClass = NULL;
  95. #endif
  96. m_cRef = 1;
  97. }
  98. CSpTask::~CSpTask()
  99. {
  100. TraceMsg(TF_SAPI_PERF, "CSpTask is destroyed");
  101. if (m_pdc)
  102. delete m_pdc;
  103. if (m_pITNFunc)
  104. delete m_pITNFunc;
  105. m_pime->Release();
  106. }
  107. //
  108. // CSpTask::_InitializeSAPIObjects
  109. //
  110. // initialize SAPI objects for SR
  111. // later we'll get other objects initialized here
  112. // (TTS, audio etc)
  113. //
  114. HRESULT CSpTask::InitializeSAPIObjects(LANGID langid)
  115. {
  116. TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is called");
  117. if (m_fSapiInitialized == TRUE)
  118. {
  119. TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is intialized already\n");
  120. return S_OK;
  121. }
  122. // m_xxx are CComPtrs from ATL
  123. //
  124. HRESULT hr = m_cpResMgr.CoCreateInstance( CLSID_SpResourceManager );
  125. TraceMsg(TF_SAPI_PERF, "CLSID_SpResourceManager is created, hr=%x", hr);
  126. if (!m_pime->IsSharedReco())
  127. {
  128. // create a recognition engine
  129. TraceMsg(TF_SAPI_PERF,"Inproc engine is generated");
  130. if( SUCCEEDED( hr ) )
  131. {
  132. hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpInprocRecognizer );
  133. }
  134. if (SUCCEEDED(hr))
  135. {
  136. CComPtr<ISpObjectToken> cpAudioToken;
  137. SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpAudioToken);
  138. if (SUCCEEDED(hr))
  139. {
  140. m_cpRecoEngine->SetInput(cpAudioToken, TRUE);
  141. }
  142. }
  143. }
  144. else
  145. {
  146. hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpSharedRecognizer );
  147. TraceMsg(TF_SAPI_PERF, "Shared Engine is generated! hr=%x", hr);
  148. }
  149. // create the recognition context
  150. if( SUCCEEDED( hr ) )
  151. {
  152. hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxt );
  153. TraceMsg(TF_SAPI_PERF, "RecoContext is generated, hr=%x", hr);
  154. }
  155. GUID guidFormatId = GUID_NULL;
  156. WAVEFORMATEX *pWaveFormatEx = NULL;
  157. if (SUCCEEDED(hr))
  158. {
  159. hr = SpConvertStreamFormatEnum(SPSF_8kHz8BitMono, &guidFormatId, &pWaveFormatEx);
  160. TraceMsg(TF_SAPI_PERF, "SpConvertStreamFormatEnum is done, hr=%x", hr);
  161. }
  162. if (SUCCEEDED(hr))
  163. {
  164. hr = m_cpRecoCtxt->SetAudioOptions(SPAO_RETAIN_AUDIO, &guidFormatId, pWaveFormatEx);
  165. TraceMsg(TF_SAPI_PERF, "RecoContext SetAudioOptions, RETAIN AUDIO, hr=%x", hr);
  166. if (pWaveFormatEx)
  167. ::CoTaskMemFree(pWaveFormatEx);
  168. }
  169. if( SUCCEEDED( hr ) )
  170. {
  171. hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
  172. TraceMsg(TF_SAPI_PERF, "SpVoice is generated, hr=%x", hr);
  173. }
  174. if ( SUCCEEDED(hr) )
  175. {
  176. // this has to be extended so that
  177. // we choose default voice as far as lang matches
  178. // and pick the best match if not
  179. //
  180. // hr = _SetVoice(langid);
  181. }
  182. //
  183. if ( SUCCEEDED(hr) )
  184. {
  185. m_langid = _GetPreferredEngineLanguage(langid);
  186. TraceMsg(TF_SAPI_PERF, "_GetPreferredEngineLanguage is Done, m_langid=%x", m_langid);
  187. }
  188. #ifdef RECOSLEEP
  189. InitSleepClass( );
  190. #endif
  191. if (SUCCEEDED(hr))
  192. m_fSapiInitialized = TRUE;
  193. TraceMsg(TF_SAPI_PERF, "InitializeSAPIObjects is Done!!!!!, hr=%x\n", hr);
  194. return hr;
  195. }
  196. //
  197. // CSpTask::_InitializeSAPIForCmd
  198. //
  199. // initialize SAPI RecoContext for Voice Command mode
  200. //
  201. // this function should be called after _InitializeSAPIObject.
  202. //
  203. HRESULT CSpTask::InitializeSAPIForCmd( )
  204. {
  205. TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd is called");
  206. HRESULT hr = S_OK;
  207. if (!m_cpRecoCtxtForCmd && m_cpRecoEngine && m_langid)
  208. {
  209. hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxtForCmd );
  210. TraceMsg(TF_SAPI_PERF, "m_cpRecoCtxtForCmd is generated, hr=%x", hr);
  211. // Set the RecoContextState as DISABLE by default to improve SAPI Perf.
  212. //
  213. // After initializing, caller must set the context state explicitly.
  214. if ( SUCCEEDED(hr) )
  215. {
  216. hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
  217. m_fCmdCtxtEnabled = FALSE;
  218. }
  219. TraceMsg(TF_SAPI_PERF, "Initialize Callback for RecoCtxtForCmd");
  220. // set recognition notification
  221. CComPtr<ISpNotifyTranslator> cpNotify;
  222. if ( SUCCEEDED(hr) )
  223. hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
  224. TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for RecoCtxtForCmd is generated, hr=%x", hr);
  225. // set this class instance to notify control object
  226. if (SUCCEEDED(hr))
  227. {
  228. m_pime->_EnsureWorkerWnd();
  229. hr = cpNotify->InitCallback( NotifyCallbackForCmd, 0, (LPARAM)this );
  230. }
  231. if (SUCCEEDED(hr))
  232. {
  233. hr = m_cpRecoCtxtForCmd->SetNotifySink(cpNotify);
  234. TraceMsg(TF_SAPI_PERF, "SetNotifySink for RecoCtxtForCmd is Done, hr=%x", hr);
  235. }
  236. // set the events we're interested in
  237. if( SUCCEEDED( hr ) )
  238. {
  239. const ULONGLONG ulInterest = SPFEI(SPEI_RECOGNITION) |
  240. SPFEI(SPEI_FALSE_RECOGNITION) |
  241. SPFEI(SPEI_RECO_OTHER_CONTEXT);
  242. hr = m_cpRecoCtxtForCmd->SetInterest(ulInterest, ulInterest);
  243. TraceMsg(TF_SAPI_PERF, "SetInterest for m_cpRecoCtxtForCmd is Done, hr=%x", hr);
  244. }
  245. TraceMsg(TF_SAPI_PERF, "InitializeCallback for m_cpRecoCtxtForCmd is done!!! hr=%x", hr);
  246. // Load the shard command grammars and activate them by default.
  247. if (SUCCEEDED(hr) )
  248. {
  249. hr = m_cpRecoCtxtForCmd->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInVoiceCmd);
  250. TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammar In Voice cmd, hr=%x", hr);
  251. }
  252. if (S_OK == hr)
  253. {
  254. hr = S_FALSE;
  255. // try resource first because loading cmd from file takes
  256. // quite long time
  257. //
  258. if (m_langid == 0x409 || // English
  259. m_langid == 0x411 || // Japanese
  260. m_langid == 0x804 ) // Simplified Chinese
  261. {
  262. hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromResource(
  263. g_hInstSpgrmr,
  264. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG),
  265. L"SRGRAMMAR",
  266. m_langid,
  267. SPLO_DYNAMIC);
  268. TraceMsg(TF_SAPI_PERF, "Load shared cmd.cfg, hr=%x", hr);
  269. }
  270. if (S_OK != hr)
  271. {
  272. // in case if we don't have built-in grammar
  273. // it provides a way for customer to localize their grammars in different languages
  274. _GetCmdFileName(m_langid);
  275. if (m_szShrdCmdFile[0])
  276. {
  277. hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC);
  278. }
  279. }
  280. // Activate the grammar by default
  281. if ( hr == S_OK )
  282. {
  283. if (m_pime->_AllCmdsEnabled( ))
  284. {
  285. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(NULL, NULL, SPRS_ACTIVE);
  286. TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSharedGrammarInVoiceCmd");
  287. }
  288. else
  289. {
  290. // Some category commands are disabled.
  291. // active them individually.
  292. hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  293. if ( hr == S_OK )
  294. hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  295. if ( hr == S_OK )
  296. hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  297. if ( hr == S_OK )
  298. hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  299. if ( hr == S_OK )
  300. hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_COMMAND_MODE );
  301. if ( hr == S_OK )
  302. hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  303. if ( hr == S_OK )
  304. hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
  305. }
  306. }
  307. if (S_OK != hr)
  308. {
  309. m_cpSharedGrammarInVoiceCmd.Release();
  310. }
  311. else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH ||
  312. PRIMARYLANGID(m_langid) == LANG_JAPANESE ||
  313. PRIMARYLANGID(m_langid) == LANG_CHINESE )
  314. {
  315. // means this language's grammar support Textbuffer commands.
  316. m_fSelectionEnabled = TRUE;
  317. }
  318. #ifdef RECOSLEEP
  319. InitSleepClass( );
  320. #endif
  321. }
  322. TraceMsg(TF_SAPI_PERF, "Finish the initalization for RecoCtxtForCmd");
  323. }
  324. TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd exits!");
  325. return hr;
  326. }
  327. #ifdef RECOSLEEP
  328. void CSpTask::InitSleepClass( )
  329. {
  330. // Load the Sleep/Wakeup grammar.
  331. if ( !m_pSleepClass )
  332. {
  333. m_pSleepClass = new CRecoSleepClass(this);
  334. if ( m_pSleepClass )
  335. m_pSleepClass->InitRecoSleepClass( );
  336. }
  337. }
  338. BOOL CSpTask::IsInSleep( )
  339. {
  340. BOOL fSleep = FALSE;
  341. if ( m_pSleepClass )
  342. fSleep = m_pSleepClass->IsInSleep( );
  343. return fSleep;
  344. }
  345. #endif
  346. HRESULT CSpTask::_SetDictRecoCtxtState( BOOL fEnable )
  347. {
  348. HRESULT hr = S_OK;
  349. TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState is called, fEnable=%d", fEnable);
  350. if ( m_cpRecoCtxt && (fEnable != m_fDictCtxtEnabled))
  351. {
  352. if (fEnable )
  353. {
  354. // if Voice command reco Context is enabled, just disable it.
  355. if (m_cpRecoCtxtForCmd && m_fCmdCtxtEnabled)
  356. {
  357. hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
  358. m_fCmdCtxtEnabled = FALSE;
  359. TraceMsg(TF_SAPI_PERF, "Disable Voice command Reco Context");
  360. }
  361. // Build toolbar grammar if it is not built out yet.
  362. if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( ))
  363. m_pLangBarSink->_OnSetFocus( );
  364. // Enable Dictation Reco Context.
  365. if ( hr == S_OK )
  366. {
  367. hr = m_cpRecoCtxt->SetContextState(SPCS_ENABLED);
  368. TraceMsg(TF_SAPI_PERF, "Enable Dictation Reco Context");
  369. if ( hr == S_OK && !m_fDictationReady )
  370. {
  371. WCHAR sz[128];
  372. sz[0] = '\0';
  373. CicLoadStringWrapW(g_hInst, IDS_NUI_BEGINDICTATION, sz, ARRAYSIZE(sz));
  374. m_pime->GetSpeechUIServer()->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
  375. m_fDictationReady = TRUE;
  376. TraceMsg(TF_SAPI_PERF, "Show Begin Dictation!");
  377. }
  378. }
  379. }
  380. else
  381. {
  382. hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
  383. TraceMsg(TF_SAPI_PERF, "Disable Dictation Reco Context");
  384. }
  385. if ( hr == S_OK )
  386. {
  387. m_fDictCtxtEnabled = fEnable;
  388. }
  389. }
  390. TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState exit");
  391. return hr;
  392. }
  393. HRESULT CSpTask::_SetCmdRecoCtxtState( BOOL fEnable )
  394. {
  395. TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState is called, fEnable=%d", fEnable);
  396. HRESULT hr = S_OK;
  397. if ( fEnable != m_fCmdCtxtEnabled )
  398. {
  399. if ( fEnable )
  400. {
  401. if ( !m_cpRecoCtxtForCmd )
  402. hr = InitializeSAPIForCmd( );
  403. if ( hr == S_OK && m_cpRecoCtxtForCmd )
  404. {
  405. // Disable Dictation Context if it is enabled now.
  406. if (m_cpRecoCtxt && m_fDictCtxtEnabled)
  407. {
  408. hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
  409. m_fDictCtxtEnabled = FALSE;
  410. TraceMsg(TF_SAPI_PERF, "DISABLE Dictation RecoContext");
  411. }
  412. if ( hr == S_OK && m_pime && !m_pime->_AllCmdsDisabled( ) )
  413. {
  414. // Build toolbar grammar if it is not built out yet.
  415. if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( ))
  416. m_pLangBarSink->_OnSetFocus( );
  417. // Fill text to selection grammar's buffer.
  418. _UpdateTextBuffer(m_cpRecoCtxtForCmd);
  419. hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_ENABLED);
  420. m_fCmdCtxtEnabled = fEnable;
  421. TraceMsg(TF_SAPI_PERF, "Enable Voice command Reco Context");
  422. }
  423. }
  424. }
  425. else if ( m_cpRecoCtxtForCmd ) // fEnable is FALSE
  426. {
  427. hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
  428. m_fCmdCtxtEnabled = FALSE;
  429. TraceMsg(TF_SAPI_PERF, "Disable Voice Command Reco Context");
  430. }
  431. }
  432. TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState exits");
  433. return hr;
  434. }
  435. LANGID CSpTask::_GetPreferredEngineLanguage(LANGID langid)
  436. {
  437. SPRECOGNIZERSTATUS stat;
  438. LANGID langidRet = 0;
  439. // (possible TODO) After M3 SPG may come up with GetAttrRank API that
  440. // would give us the info about whether a token has a particular
  441. // attrib supported. Then we could use that for checking langid
  442. // a recognizer supports without using the real engine instance.
  443. // We could also consolidate a method to check if SR is enabled
  444. // for the current language once we have that.
  445. //
  446. Assert(m_cpRecoEngine);
  447. if (S_OK == m_cpRecoEngine->GetStatus(&stat))
  448. {
  449. for (ULONG ulId = 0; ulId < stat.cLangIDs; ulId++)
  450. {
  451. if (langid == stat.aLangID[ulId])
  452. {
  453. langidRet = langid;
  454. break;
  455. }
  456. }
  457. if (!langidRet)
  458. {
  459. // if there's no match, just return the most prefered one
  460. langidRet = stat.aLangID[0];
  461. }
  462. }
  463. return langidRet;
  464. }
  465. HRESULT CSpTask::_SetVoice(LANGID langid)
  466. {
  467. CComPtr<ISpObjectToken> cpToken;
  468. char szLang[MAX_PATH];
  469. WCHAR wsz[MAX_PATH];
  470. StringCchPrintfA(szLang,ARRAYSIZE(szLang), "Language=%x", langid);
  471. MultiByteToWideChar(CP_ACP, NULL, szLang, -1, wsz, ARRAYSIZE(wsz));
  472. HRESULT hr = SpFindBestToken( SPCAT_VOICES, wsz, NULL, &cpToken);
  473. if (S_OK == hr)
  474. {
  475. hr = m_cpVoice->SetVoice(cpToken);
  476. }
  477. return hr;
  478. }
  479. //
  480. // GetSAPIInterface(riid, (void **)ppunk)
  481. //
  482. // here, try pass through the given IID
  483. // to SAPI5 interface
  484. //
  485. // CComPtr<ISpResourceManager> m_cpResMgr;
  486. // CComPtr<ISpRecoContext> m_cpRecoCtxt;
  487. // CComPtr<ISpRecognizer> m_cpRecoEngine;
  488. // CComPtr<ISpVoice> m_cpVoice;
  489. //
  490. // the above 5 interfaces are currently used by
  491. // Cicero/Sapi Layer
  492. //
  493. // if a client calls ITfFunctionProvider::GetFunction()
  494. // for a SAPI interface, we return what we've already
  495. // instantiated so the caller can setup options
  496. // for the currently used SAPI objects (reco ctxt for ex)
  497. //
  498. HRESULT CSpTask::GetSAPIInterface(REFIID riid, void **ppunk)
  499. {
  500. Assert(ppunk);
  501. *ppunk = NULL;
  502. if (IsEqualGUID(riid, IID_ISpResourceManager))
  503. {
  504. *ppunk = m_cpResMgr;
  505. }
  506. else if (IsEqualGUID(riid,IID_ISpRecoContext))
  507. {
  508. *ppunk = m_cpRecoCtxt;
  509. }
  510. else if (IsEqualGUID(riid,IID_ISpRecognizer))
  511. {
  512. *ppunk = m_cpRecoEngine;
  513. }
  514. else if (IsEqualGUID(riid,IID_ISpVoice))
  515. {
  516. *ppunk = m_cpVoice;
  517. }
  518. else if (IsEqualGUID(riid,IID_ISpRecoGrammar))
  519. {
  520. *ppunk = m_cpDictGrammar;
  521. }
  522. if(*ppunk)
  523. {
  524. ((IUnknown *)(*ppunk))->AddRef();
  525. }
  526. return *ppunk ? S_OK : E_NOTIMPL;
  527. }
  528. //
  529. // Get RecoContext for Voice Command mode.
  530. //
  531. HRESULT CSpTask::GetRecoContextForCommand(ISpRecoContext **ppRecoCtxt)
  532. {
  533. HRESULT hr = E_FAIL;
  534. Assert(ppRecoCtxt);
  535. if ( m_cpRecoCtxtForCmd )
  536. {
  537. *ppRecoCtxt = m_cpRecoCtxtForCmd;
  538. (*ppRecoCtxt)->AddRef( );
  539. hr = S_OK;
  540. }
  541. return hr;
  542. }
  543. // test: use Message callback
  544. LRESULT CALLBACK CSapiIMX::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  545. {
  546. CSapiIMX *_this = (CSapiIMX *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
  547. CSpTask *_sptask = _this ? _this->m_pCSpTask : NULL;
  548. switch(uMsg)
  549. {
  550. case WM_CREATE:
  551. {
  552. CREATESTRUCT *pcs = (CREATESTRUCT *)lParam;
  553. if (pcs)
  554. {
  555. SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)(pcs->lpCreateParams));
  556. }
  557. break;
  558. }
  559. case WM_TIMER:
  560. if ( wParam != TIMER_ID_CHARTYPED )
  561. KillTimer( hWnd, wParam );
  562. if (wParam == TIMER_ID_OPENCLOSE)
  563. {
  564. // i've seen this null case once but is it possible?
  565. TraceMsg(TF_SAPI_PERF, "TIMER_ID_OPENCLOSE is fired off ...");
  566. if (_this->_tim)
  567. _this->_HandleOpenCloseEvent(MICSTAT_ON);
  568. }
  569. else if ( wParam == TIMER_ID_CHARTYPED )
  570. {
  571. DWORD dwNumCharTyped;
  572. BOOL fDictOn;
  573. fDictOn = _this->GetOnOff( ) && _this->GetDICTATIONSTAT_DictOnOff( );
  574. dwNumCharTyped = _this->_GetNumCharTyped( );
  575. TraceMsg(TF_GENERAL, "dwNumCharTyped=%d", dwNumCharTyped);
  576. _this->_KillCharTypeTimer( );
  577. Assert(S_OK == _this->IsActiveThread());
  578. // We should never try to reactivate dictation on a thread that shouldn't be active.
  579. if ( fDictOn && _sptask && (S_OK == _this->IsActiveThread()) )
  580. {
  581. if ( dwNumCharTyped <= 1 )
  582. {
  583. // There is no more typing during this period
  584. // possible, user finished typing.
  585. //
  586. // we need to resume dication again if the Dictation mode is ON.
  587. ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) |
  588. SPFEI(SPEI_SOUND_END) |
  589. SPFEI(SPEI_PHRASE_START) |
  590. SPFEI(SPEI_RECOGNITION) |
  591. SPFEI(SPEI_FALSE_RECOGNITION) |
  592. SPFEI(SPEI_RECO_OTHER_CONTEXT) |
  593. SPFEI(SPEI_HYPOTHESIS) |
  594. SPFEI(SPEI_INTERFERENCE) |
  595. SPFEI(SPEI_ADAPTATION);
  596. _sptask->_SetDictRecoCtxtState(TRUE);
  597. _sptask->_SetRecognizerInterest(ulInterest);
  598. _sptask->_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
  599. }
  600. else
  601. {
  602. // There are more typing during this period,
  603. // we want to set another timer to watch the end of the typing.
  604. //
  605. _this->_SetCharTypeTimer( );
  606. }
  607. }
  608. }
  609. break;
  610. case WM_PRIV_FEEDCONTEXT:
  611. if (_sptask && lParam != NULL && _sptask->m_pdc)
  612. {
  613. _sptask->m_pdc->FeedContextToGrammar(_sptask->m_cpDictGrammar);
  614. delete _sptask->m_pdc;
  615. _sptask->m_pdc = NULL;
  616. }
  617. break;
  618. case WM_PRIV_LBARSETFOCUS:
  619. if (_sptask)
  620. _sptask->m_pLangBarSink->_OnSetFocus();
  621. break;
  622. case WM_PRIV_SPEECHOPTION:
  623. {
  624. _this->_ResetDefaultLang();
  625. BOOL fSREnabledForLanguage = _this->InitializeSpeechButtons();
  626. _this->SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
  627. }
  628. break;
  629. case WM_PRIV_ADDDELETE:
  630. _this->_DisplayAddDeleteUI();
  631. break;
  632. case WM_PRIV_SPEECHOPENCLOSE:
  633. TraceMsg(TF_SAPI_PERF, "WM_PRIV_SPEECHOPENCLOSE is handled");
  634. _this->_HandleOpenCloseEvent();
  635. break;
  636. case WM_PRIV_OPTIONS:
  637. _this->_InvokeSpeakerOptions();
  638. break;
  639. case WM_PRIV_DORECONVERT :
  640. _this->_DoReconvertOnRange( );
  641. break;
  642. default:
  643. return DefWindowProc(hWnd, uMsg, wParam, lParam);
  644. }
  645. return 0;
  646. }
  647. void CSpTask::NotifyCallbackForCmd(WPARAM wParam, LPARAM lParam )
  648. {
  649. CSpTask *_this = (CSpTask *)lParam;
  650. // SAPI M2 work around which is to be removed when M3 comes up.
  651. // See comments in CSpTask::_SetInputOnOffState for more detail
  652. //
  653. // TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
  654. /* if (_this->m_fInputState == FALSE)
  655. {
  656. return;
  657. }*/
  658. if (_this->m_pime->fDeactivated())
  659. return;
  660. if (!_this->m_cpRecoCtxtForCmd)
  661. {
  662. return;
  663. }
  664. _this->SharedRecoNotify(_this->m_cpRecoCtxtForCmd);
  665. return;
  666. }
  667. void CSpTask::NotifyCallback( WPARAM wParam, LPARAM lParam )
  668. {
  669. CSpTask *_this = (CSpTask *)lParam;
  670. // SAPI M2 work around which is to be removed when M3 comes up.
  671. // See comments in CSpTask::_SetInputOnOffState for more detail
  672. //
  673. // TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
  674. /* if (_this->m_fInputState == FALSE)
  675. {
  676. return;
  677. }*/
  678. if (_this->m_pime->fDeactivated())
  679. return;
  680. if (!_this->m_cpRecoCtxt)
  681. {
  682. return;
  683. }
  684. _this->SharedRecoNotify(_this->m_cpRecoCtxt);
  685. return;
  686. }
  687. // This is real handler for the recognition notification.
  688. //
  689. // it could be shared by two RecoContexts.
  690. //
  691. void CSpTask::SharedRecoNotify(ISpRecoContext *pRecoCtxt)
  692. {
  693. CSpEvent event;
  694. #ifdef SAPI_PERF_DEBUG
  695. static int iCount = 0;
  696. if ( iCount == 0 )
  697. {
  698. TraceMsg(TF_SAPI_PERF, "The first time Get Notification from Engine!!!");
  699. iCount ++;
  700. }
  701. #endif
  702. Assert (pRecoCtxt);
  703. while ( event.GetFrom(pRecoCtxt) == S_OK )
  704. {
  705. switch (event.eEventId)
  706. {
  707. case SPEI_SOUND_START:
  708. ATLASSERT(!m_bInSound);
  709. m_bInSound = TRUE;
  710. break;
  711. case SPEI_INTERFERENCE:
  712. //
  713. // we do not need interference when not in dictation
  714. // mode
  715. //
  716. if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
  717. S_OK == m_pime->IsActiveThread())
  718. {
  719. _HandleInterference((ULONG)(event.lParam));
  720. }
  721. break;
  722. case SPEI_PHRASE_START:
  723. ATLASSERT(m_bInSound);
  724. m_bGotReco = FALSE;
  725. if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
  726. S_OK == m_pime->IsActiveThread())
  727. {
  728. // Before inject feedback UI, we need to save the current IP
  729. // and check if we want to pop up the Add/Remove SR dialog UI.
  730. // And then inject FeedbackUI as usual
  731. m_pime->SaveCurIPAndHandleAddDelete_InjectFeedbackUI( );
  732. // show "dictating..." to the balloon
  733. _ShowDictatingToBalloon(TRUE);
  734. }
  735. break;
  736. case SPEI_HYPOTHESIS:
  737. ATLASSERT(!m_bGotReco);
  738. // if current microphone status is OFF
  739. // we do not want to show any hypothesis
  740. // at least
  741. //
  742. // DO NOT HAVE DEBUG CODE TO SHOW ENGINE STATE. CAN BLOCK CICERO AND CHANGE BEHAVIOR.
  743. if (!GetSystemMetrics(SM_TABLETPC))
  744. _ShowDictatingToBalloon(TRUE);
  745. //
  746. // we do not need the feedback UI when not in dictation
  747. // mode
  748. //
  749. if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
  750. S_OK == m_pime->IsActiveThread())
  751. {
  752. m_pime->_HandleHypothesis(event);
  753. }
  754. break;
  755. case SPEI_RECO_OTHER_CONTEXT:
  756. case SPEI_FALSE_RECOGNITION:
  757. {
  758. HRESULT hr = S_OK;
  759. if ( event.eEventId == SPEI_FALSE_RECOGNITION )
  760. {
  761. // Set 'What was that?' feedback text.
  762. _UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE);
  763. }
  764. // set this flag anyways
  765. //
  766. ATLASSERT(!m_bGotReco);
  767. m_bGotReco = TRUE;
  768. // Reset hypothesis counters.
  769. m_pime->_HandleFalseRecognition();
  770. hr = m_pime->EraseFeedbackUI();
  771. ATLASSERT("Failed to erase potential feedback on a false recognition." && SUCCEEDED(hr));
  772. break;
  773. }
  774. case SPEI_RECOGNITION:
  775. // Set 'Listening...' feedback text. Can be overwritten by command feedback.
  776. _UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
  777. // set this flag anyways
  778. //
  779. ATLASSERT(!m_bGotReco);
  780. m_bGotReco = TRUE;
  781. ULONGLONG ullGramID;
  782. if ( S_OK == m_pime->IsActiveThread() )
  783. {
  784. m_pime->_HandleRecognition(event, &ullGramID);
  785. }
  786. // if ( _GetSelectionStatus( ) )
  787. if (ullGramID == GRAM_ID_SPELLING)
  788. {
  789. _SetSelectionStatus(FALSE);
  790. _SetSpellingGrammarStatus(FALSE);
  791. }
  792. _UpdateTextBuffer(pRecoCtxt);
  793. if ( (ullGramID == GRAM_ID_DICT) || (ullGramID == GRAM_ID_SPELLING) )
  794. {
  795. // Update Balloon.
  796. if (!GetSystemMetrics(SM_TABLETPC))
  797. _UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
  798. // every time dictated text is injected, we want to watch
  799. // again if there is IP change after that.
  800. // so clear the flag now.
  801. m_pime->_SetIPChangeStatus( FALSE );
  802. }
  803. break;
  804. case SPEI_SOUND_END:
  805. m_bInSound = FALSE;
  806. break;
  807. case SPEI_ADAPTATION:
  808. TraceMsg(TF_GENERAL, "Get SPEI_ADAPTATION notification");
  809. if ( m_pime->_HasMoreContent( ) )
  810. {
  811. m_pime->_GetNextRangeEditSession( );
  812. }
  813. else
  814. // There is no more content for this doc.
  815. // set the interesting event value to avoid this notification.
  816. m_pime->_UpdateRecoContextInterestSet(FALSE);
  817. break;
  818. #ifdef SYSTEM_GLOBAL_MIC_STATUS
  819. case SPEI_RECO_STATE_CHANGE:
  820. m_pime->SetOnOff(_GetInputOnOffState());
  821. break;
  822. #endif
  823. default:
  824. break;
  825. }
  826. }
  827. return;
  828. }
  829. HRESULT CSpTask::_UpdateTextBuffer(ISpRecoContext *pRecoCtxt)
  830. {
  831. HRESULT hr = S_OK;
  832. if ( !_IsSelectionEnabled( ) )
  833. return S_OK;
  834. if ( !pRecoCtxt || !m_pime)
  835. return E_FAIL;
  836. if ( m_pime->_SelectCorrectCmdEnabled( ) || m_pime->_NavigationCmdEnabled( ) )
  837. {
  838. BOOL fDictOn, fCmdOn;
  839. fDictOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_DictOnOff( );
  840. fCmdOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( );
  841. if ( fDictOn && m_cpSharedGrammarInDict && !m_pime->_AllDictCmdsDisabled( ))
  842. hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInDict);
  843. else if (fCmdOn && m_cpSharedGrammarInVoiceCmd )
  844. hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInVoiceCmd);
  845. }
  846. return hr;
  847. }
  848. // When selection grammar status is changed from inactive to active
  849. // this function will be called to fill text to the grammar buffer.
  850. //
  851. HRESULT CSpTask::_UpdateSelectGramTextBufWhenStatusChanged( )
  852. {
  853. BOOL fDictOn, fCmdOn;
  854. HRESULT hr = S_OK;
  855. // Check current mode status.
  856. fDictOn = m_pime->GetDICTATIONSTAT_DictOnOff( );
  857. fCmdOn = m_pime->GetDICTATIONSTAT_CommandingOnOff( );
  858. if ( fDictOn )
  859. hr = _UpdateTextBuffer(m_cpRecoCtxt);
  860. else if ( fCmdOn )
  861. hr = _UpdateTextBuffer(m_cpRecoCtxtForCmd);
  862. return hr;
  863. }
  864. HRESULT CSpTask::_OnSpEventRecognition(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec)
  865. {
  866. HRESULT hr = S_OK;
  867. BOOL fDiscard = FALSE;
  868. BOOL fCtrlSymChar = FALSE; // Control or Punctuation character
  869. if (pResult)
  870. {
  871. static const WCHAR szUnrecognized[] = L"<Unrecognized>";
  872. LANGID langid;
  873. SPPHRASE *pPhrase;
  874. hr = pResult->GetPhrase(&pPhrase);
  875. if (SUCCEEDED(hr) && pPhrase)
  876. {
  877. // AJG - ADDED FILTERING CODE.
  878. switch (pPhrase->Rule.ulCountOfElements)
  879. {
  880. case 0:
  881. {
  882. ASSERT(pPhrase->Rule.ulCountOfElements != 0);
  883. // SHOULD NEVER OCCUR.
  884. break;
  885. }
  886. case 1:
  887. {
  888. const SPPHRASEELEMENT *pElement;
  889. pElement = pPhrase->pElements;
  890. if (!m_fTestedForOldMicrosoftEngine)
  891. {
  892. // Test token name to see if it contains MSASREnglish.
  893. CComPtr<ISpObjectToken> cpRecoToken;
  894. WCHAR *pwszCoMemTokenId;
  895. m_cpRecoEngine->GetRecognizer(&cpRecoToken);
  896. if (cpRecoToken)
  897. {
  898. if (SUCCEEDED(cpRecoToken->GetId(&pwszCoMemTokenId)))
  899. {
  900. if (wcsstr(pwszCoMemTokenId, L"MSASREnglish") != NULL)
  901. {
  902. // It is an old Microsoft engine. Check for registry key that tells us to disable the heuristic anyway.
  903. BOOL fDisableHeuristicAnyway = FALSE;
  904. if (FAILED(cpRecoToken->MatchesAttributes(L"DisableCiceroConfidence", &fDisableHeuristicAnyway)) || fDisableHeuristicAnyway == FALSE)
  905. {
  906. m_fOldMicrosoftEngine = TRUE;
  907. // Means we *will* apply single word confidence heuristic to improve performance.
  908. }
  909. }
  910. CoTaskMemFree(pwszCoMemTokenId);
  911. }
  912. }
  913. // One of lazy initialization. Do not do this again.
  914. m_fTestedForOldMicrosoftEngine = TRUE;
  915. }
  916. if (m_fOldMicrosoftEngine && m_pime->_RequireHighConfidenceForShorWord( ) )
  917. {
  918. // Only apply this heuristic to 5.x Microsoft engines (Token name contains MSASREnglish).
  919. if (pElement && pElement->ActualConfidence != 1 &&
  920. (!pElement->pszLexicalForm || wcslen(pElement->pszLexicalForm) <= 5) &&
  921. (!pElement->pszDisplayText || wcslen(pElement->pszDisplayText) <= 5) )
  922. {
  923. TraceMsg(TF_GENERAL, "Discarded Result : Single Word, Low Confidence!");
  924. _UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE );
  925. fDiscard = TRUE;
  926. }
  927. }
  928. if (pPhrase->pElements[0].pszDisplayText )
  929. {
  930. WCHAR wch;
  931. wch = pPhrase->pElements[0].pszDisplayText[0];
  932. if ( iswcntrl(wch) || iswpunct(wch) )
  933. fCtrlSymChar = TRUE;
  934. }
  935. }
  936. case 2:
  937. {
  938. // Do something here?
  939. }
  940. default:
  941. {
  942. // Do no filtering of the result.
  943. }
  944. }
  945. // AJG - CHECK WE AREN'T IN THE MIDDLE OF A WORD. NOT GENERALLY A DESIRED 'FEATURE'. CAUSES ANNOYING ERRORS.
  946. // if this is spelled text, don't check if it is inside of a word.
  947. if ((pPhrase->ullGrammarID != GRAM_ID_SPELLING) && _IsSelectionInMiddleOfWord(ec) && !fCtrlSymChar)
  948. {
  949. TraceMsg(TF_GENERAL, "Discarded Result : IP is in middle of a word!");
  950. _UpdateBalloon(IDS_BALLOON_DICTAT_PAUSED, IDS_BALLOON_TOOLTIP_IP_INSIDE_WORD);
  951. fDiscard = TRUE;
  952. }
  953. }
  954. if ( SUCCEEDED(hr) && fDiscard )
  955. {
  956. // This phrase will not be injected to the document.
  957. // the code needs to feed context to the SR engine so that
  958. // SR engine will not base on wrong assumption.
  959. if ( m_pime && m_pime->GetDICTATIONSTAT_DictOnOff() )
  960. m_pime->_SetCurrentIPtoSR();
  961. }
  962. if (SUCCEEDED(hr) && pPhrase && !fDiscard)
  963. {
  964. // retrieve LANGID from phrase
  965. langid = pPhrase->LangID;
  966. // SPPHRASE includes non-serialized text
  967. CSpDynamicString dstr;
  968. ULONG ulNumElements = pPhrase->Rule.ulCountOfElements;
  969. hr = _GetTextFromResult(pResult, langid, dstr);
  970. if ( hr == S_OK )
  971. {
  972. // check the current IP to see if it was a selection,
  973. // then see if the best hypothesis already matches the current
  974. // selection.
  975. int lCommitHypothesis = 0;
  976. for (int nthHypothesis = 1;_DoesSelectionHaveMatchingText(dstr, ec); nthHypothesis++)
  977. {
  978. CSpDynamicString dsNext;
  979. TraceMsg(TF_GENERAL, "Switched to alternate result as main result exactly matched selection!");
  980. // We could add one to request hypothesis since 1 = the main phrase and we already know that matched.
  981. // However I don't believe this is guaranteed to be the case by SAPI - it just happens to be the case
  982. // with the Microsoft engine.
  983. if (_GetNextBestHypothesis(pResult, nthHypothesis, &ulNumElements, langid, dstr, dsNext, ec))
  984. {
  985. dstr.Clear();
  986. dstr.Append(dsNext);
  987. // Need to commit phrase to prevent stored result object being out of sync with count of
  988. // elements in wrapping object.
  989. lCommitHypothesis = nthHypothesis;
  990. // Note - at this point, we don't know if we can use it. We have to loop once more to determine this.
  991. }
  992. else
  993. {
  994. TraceMsg(TF_SAPI_PERF, "No alternate found that differed from the user selection.\n");
  995. // No more alternate phrase
  996. // There is no any alt phrase which has different text from current selection.
  997. // should stop here, otherwise, infinite loop.
  998. lCommitHypothesis = 0;
  999. // Reset element count to match primary phrase.
  1000. ulNumElements = pPhrase->Rule.ulCountOfElements;
  1001. // Reset text:
  1002. dstr.Clear();
  1003. hr = _GetTextFromResult(pResult, langid, dstr);
  1004. break;
  1005. }
  1006. }
  1007. if (0 != lCommitHypothesis)
  1008. {
  1009. ULONG cAlt = lCommitHypothesis;
  1010. ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(cAlt*sizeof(ISpPhraseAlt *));
  1011. if (ppAlt)
  1012. {
  1013. memset(ppAlt, 0, cAlt * sizeof(ISpPhraseAlt *));
  1014. hr = pResult->GetAlternates( 0, ulNumElements, cAlt, ppAlt, &cAlt );
  1015. Assert( cAlt == lCommitHypothesis );
  1016. if ((S_OK == hr) && (cAlt == lCommitHypothesis))
  1017. {
  1018. ((ppAlt)[lCommitHypothesis-1])->Commit();
  1019. }
  1020. // Release references to alternate phrases.
  1021. for (UINT i = 0; i < cAlt; i++)
  1022. {
  1023. if (NULL != (ppAlt)[i])
  1024. {
  1025. ((ppAlt)[i])->Release();
  1026. }
  1027. }
  1028. cicMemFree(ppAlt);
  1029. }
  1030. }
  1031. CComPtr<ITfRange> cpTextRange;
  1032. ITfRange *pSavedIP;
  1033. pSavedIP = m_pime->GetSavedIP( );
  1034. if (pSavedIP)
  1035. pSavedIP->Clone(&cpTextRange);
  1036. // this call will have to be per element. see my comment below.
  1037. if (pPhrase->ullGrammarID == GRAM_ID_SPELLING)
  1038. {
  1039. hr = m_pime->InjectSpelledText(dstr, langid);
  1040. }
  1041. else
  1042. {
  1043. hr = m_pime->InjectText(dstr, langid);
  1044. if ( hr == S_OK )
  1045. {
  1046. // now we use the result object directly to attach
  1047. // to a docuement.
  1048. // the result object gets addref'd in the Attach()
  1049. // call.
  1050. //
  1051. hr = m_pime->AttachResult(pResult, 0, ulNumElements);
  1052. }
  1053. // Handle spaces carefully and specially.
  1054. if ( hr == S_OK && cpTextRange )
  1055. {
  1056. hr = m_pime->HandleSpaces(pResult, 0, ulNumElements, cpTextRange, langid);
  1057. }
  1058. }
  1059. }
  1060. }
  1061. if ( pPhrase)
  1062. ::CoTaskMemFree( pPhrase );
  1063. }
  1064. return hr;
  1065. }
  1066. //
  1067. // _GetTextFromResult
  1068. //
  1069. // synopsis: get text from phrase considering space control
  1070. // based on locale
  1071. //
  1072. HRESULT CSpTask::_GetTextFromResult(ISpRecoResult *pResult, LANGID langid, CSpDynamicString &dstr)
  1073. {
  1074. BYTE bAttr;
  1075. HRESULT hr = S_OK;
  1076. Assert(pResult);
  1077. if ( !pResult )
  1078. return E_INVALIDARG;
  1079. hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstr, &bAttr);
  1080. if ( hr == S_OK )
  1081. {
  1082. if (bAttr & SPAF_ONE_TRAILING_SPACE)
  1083. {
  1084. dstr.Append(L" ");
  1085. }
  1086. else if (bAttr & SPAF_TWO_TRAILING_SPACES)
  1087. {
  1088. dstr.Append(L" ");
  1089. }
  1090. if (bAttr & SPAF_CONSUME_LEADING_SPACES)
  1091. {
  1092. // we need to figure out the correct behavior based on LANGID
  1093. }
  1094. }
  1095. return hr;
  1096. }
  1097. //
  1098. // _IsSelectionInMiddleOfWord
  1099. //
  1100. // synopsis: check if the current IP is empty and inside of a word
  1101. //
  1102. BOOL CSpTask::_IsSelectionInMiddleOfWord(TfEditCookie ec)
  1103. {
  1104. BOOL fInsideWord = FALSE;
  1105. if ( m_langid == 0x0409 )
  1106. {
  1107. if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
  1108. {
  1109. WCHAR szSurrounding[3] = L" ";
  1110. // clone the IP range since we want to move the anchor
  1111. //
  1112. CComPtr<ITfRange> cpClonedRange;
  1113. cpInsertionPoint->Clone(&cpClonedRange);
  1114. BOOL fEmpty;
  1115. cpClonedRange->IsEmpty(ec, &fEmpty);
  1116. if (fEmpty)
  1117. {
  1118. LONG l1, l2;
  1119. ULONG ul;
  1120. HRESULT hr;
  1121. cpClonedRange->Collapse(ec, TF_ANCHOR_START);
  1122. cpClonedRange->ShiftStart(ec, -1, &l1, NULL);
  1123. cpClonedRange->ShiftEnd(ec, 1, &l2, NULL);
  1124. if (l1 != 0) // Not at start of document.
  1125. {
  1126. hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szSurrounding, (l2!=0)?(2):(1), &ul);
  1127. if (SUCCEEDED(hr) && iswalpha(szSurrounding[0]) && iswalpha(szSurrounding[1]) )
  1128. {
  1129. fInsideWord = TRUE;
  1130. }
  1131. }
  1132. // if l1 == 0, means the ip is at the start of document.
  1133. // fInsideWord is set to FALSE already by default.
  1134. }
  1135. }
  1136. }
  1137. return fInsideWord;
  1138. }
  1139. //
  1140. // DoesSelectionHaveMatchingText
  1141. //
  1142. // synopsis: check if the current saved IP has a selection that matches the text
  1143. // passed in
  1144. //
  1145. #define SPACEBUFFER 4
  1146. // 2 characters either side of a word or phrase.
  1147. BOOL CSpTask::_DoesSelectionHaveMatchingText(WCHAR *psz, TfEditCookie ec)
  1148. {
  1149. BOOL fMatch = FALSE;
  1150. Assert(psz);
  1151. if ( !psz )
  1152. {
  1153. return FALSE;
  1154. }
  1155. WCHAR *pszStripped = psz;
  1156. ULONG ulCch = wcslen(psz);
  1157. // Remove trailing space.
  1158. while (ulCch > 0 && psz[ulCch-1] == L' ')
  1159. {
  1160. // Do not set null terminating character as this is a passed in string.
  1161. ulCch --;
  1162. }
  1163. // Skip leading space in input text.
  1164. while (pszStripped[0] == L' ')
  1165. {
  1166. pszStripped ++;
  1167. ulCch --;
  1168. }
  1169. // Now have space-stripped word pointed to by pszTmp and with length ulCch
  1170. if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
  1171. {
  1172. WCHAR *szRange = new WCHAR[ulCch+SPACEBUFFER+1];
  1173. WCHAR *szRangeStripped = szRange;
  1174. if (szRange)
  1175. {
  1176. // clone the IP range since we want to move the anchor
  1177. //
  1178. CComPtr<ITfRange> cpClonedRange;
  1179. cpInsertionPoint->Clone(&cpClonedRange);
  1180. ULONG cchRange; // max is the reco result
  1181. HRESULT hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szRange, ulCch+SPACEBUFFER, &cchRange);
  1182. // Remove trailing space.
  1183. while (cchRange > 0 && szRange[cchRange-1] == L' ')
  1184. {
  1185. // Can set null terminating character as this is our string.
  1186. szRange[cchRange-1] = 0;
  1187. cchRange --;
  1188. }
  1189. // Skip leading space in input text.
  1190. while (szRangeStripped[0] == L' ')
  1191. {
  1192. szRangeStripped ++;
  1193. cchRange --;
  1194. }
  1195. // Now have space-stripped word pointed to by pszTmp and with length ulCch
  1196. if (S_OK == hr && cchRange > 0 && cchRange == ulCch)
  1197. {
  1198. if (wcsnicmp(pszStripped, szRangeStripped, ulCch) == 0) // Case insensitive compare.
  1199. {
  1200. fMatch = TRUE;
  1201. }
  1202. }
  1203. delete [] szRange;
  1204. }
  1205. }
  1206. return fMatch;
  1207. }
  1208. //
  1209. // GetNextBestHypothesis
  1210. //
  1211. // synopsis: this actually gets the nth alternative from the given reco result
  1212. // then adjusts the length accordingly based on the current selection
  1213. //
  1214. //
  1215. BOOL CSpTask::_GetNextBestHypothesis
  1216. (
  1217. ISpRecoResult *pResult,
  1218. ULONG nthHypothesis,
  1219. ULONG *pulNumElements,
  1220. LANGID langid,
  1221. WCHAR *pszBest,
  1222. CSpDynamicString & dsNext,
  1223. TfEditCookie ec
  1224. )
  1225. {
  1226. if ( pulNumElements )
  1227. *pulNumElements = 0;
  1228. // get the entire text & length from the saved IP
  1229. if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
  1230. {
  1231. CSpDynamicString dstr;
  1232. CComPtr<ITfRange> cpClonedRange;
  1233. CComPtr<ISpRecoResult> cpRecoResult;
  1234. // clone the range since we move the anchor
  1235. HRESULT hr = cpInsertionPoint->Clone(&cpClonedRange);
  1236. ULONG cchRangeBuf = wcslen(pszBest);
  1237. cchRangeBuf *= 2; // guess the possible # of char
  1238. WCHAR *szRangeBuf = new WCHAR[cchRangeBuf+1];
  1239. if ( !szRangeBuf )
  1240. {
  1241. // Error: Out of Memory
  1242. // Return here as FALSE.
  1243. return FALSE;
  1244. }
  1245. while(S_OK == hr && !_IsRangeEmpty(ec, cpClonedRange))
  1246. {
  1247. hr = m_pime->_GetRangeText(cpClonedRange, TF_TF_MOVESTART, szRangeBuf, &cchRangeBuf);
  1248. if (S_OK == hr)
  1249. {
  1250. szRangeBuf[cchRangeBuf] = L'\0';
  1251. dstr.Append(szRangeBuf);
  1252. }
  1253. }
  1254. delete [] szRangeBuf;
  1255. // then get a best matching length of next best hypothesis
  1256. // the current recognition should at least have a good guess for # of elements
  1257. // since it turned out to be longer than the IP range.
  1258. //
  1259. Assert(pulNumElements);
  1260. ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(nthHypothesis*sizeof(ISpPhraseAlt *));
  1261. ULONG cAlt = 0;
  1262. if (!ppAlt)
  1263. {
  1264. hr = E_OUTOFMEMORY;
  1265. }
  1266. else
  1267. {
  1268. memset(ppAlt, 0, nthHypothesis * sizeof(ISpPhraseAlt *));
  1269. hr = pResult->GetAlternates( 0, *pulNumElements, nthHypothesis, ppAlt, &cAlt );
  1270. }
  1271. if (S_OK == hr)
  1272. {
  1273. UINT i;
  1274. SPPHRASE *pPhrase;
  1275. if (nthHypothesis > cAlt)
  1276. {
  1277. *pulNumElements = 0;
  1278. goto no_more_alt;
  1279. }
  1280. Assert(nthHypothesis); // 1 based, can't be 0
  1281. hr = ((ppAlt)[nthHypothesis-1])->GetPhrase(&pPhrase);
  1282. if (S_OK == hr)
  1283. {
  1284. for (i = 0; i < pPhrase->Rule.ulCountOfElements; i++ )
  1285. {
  1286. int cchElement = wcslen(pPhrase->pElements[i].pszDisplayText) + 1;
  1287. WCHAR *szElement = new WCHAR[cchElement + 2];
  1288. if ( szElement )
  1289. {
  1290. // add +2 for trailing spaces
  1291. ParseSRElementByLocale(szElement, cchElement+2, pPhrase->pElements[i].pszDisplayText,
  1292. langid, pPhrase->pElements[i].bDisplayAttributes );
  1293. dsNext.Append(szElement);
  1294. delete [] szElement;
  1295. }
  1296. else
  1297. {
  1298. // Out of Memory.
  1299. // stop here.
  1300. break;
  1301. }
  1302. }
  1303. // now i holds the number of elements that we want to use in the result
  1304. // object
  1305. *pulNumElements = i;
  1306. ::CoTaskMemFree(pPhrase);
  1307. } // if S_OK == GetPhrase
  1308. } // if S_OK == GetAlternates
  1309. no_more_alt:
  1310. // Release phrase alternates objects.
  1311. for (UINT i = 0; i < cAlt; i++)
  1312. {
  1313. if (NULL != ((ppAlt)[i]))
  1314. {
  1315. ((ppAlt)[i])->Release();
  1316. }
  1317. }
  1318. // Free memory for array holding references to alternates objects.
  1319. if (ppAlt)
  1320. {
  1321. ::cicMemFree(ppAlt);
  1322. }
  1323. }
  1324. return *pulNumElements > 0;
  1325. }
  1326. void CSapiIMX::_EnsureWorkerWnd(void)
  1327. {
  1328. if (!m_hwndWorker)
  1329. {
  1330. m_hwndWorker = CreateWindow(c_szWorkerWndClass, "", WS_POPUP,
  1331. 0,0,0,0,
  1332. NULL, 0, g_hInst, this);
  1333. }
  1334. }
  1335. //
  1336. // CSapiIMX::_GetAppMainWnd
  1337. //
  1338. // This function gets the real main window of current application.
  1339. // This main window would be used as the parent window of Add/Delete dialog
  1340. // and Training wizard.
  1341. //
  1342. HWND CSapiIMX::_GetAppMainWnd(void)
  1343. {
  1344. HWND hParentWnd = NULL;
  1345. HWND hMainWnd = NULL;
  1346. hMainWnd = GetFocus( );
  1347. if ( hMainWnd != NULL )
  1348. {
  1349. hParentWnd = GetParent(hMainWnd);
  1350. while ( hParentWnd != NULL )
  1351. {
  1352. hMainWnd = hParentWnd;
  1353. hParentWnd = GetParent(hMainWnd);
  1354. }
  1355. }
  1356. return hMainWnd;
  1357. }
  1358. //
  1359. // CSpTask::InitializeCallback
  1360. //
  1361. //
  1362. HRESULT CSpTask::InitializeCallback()
  1363. {
  1364. TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called");
  1365. if (m_fCallbackInitialized)
  1366. {
  1367. TraceMsg(TF_SAPI_PERF, "m_fCallbackInitialized is true");
  1368. return S_OK;
  1369. }
  1370. if (!m_fSapiInitialized)
  1371. return S_FALSE; // can't do this without SAPI
  1372. // set recognition notification
  1373. CComPtr<ISpNotifyTranslator> cpNotify;
  1374. HRESULT hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
  1375. TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for Reco is generated, hr=%x", hr);
  1376. // set this class instance to notify control object
  1377. if (SUCCEEDED(hr))
  1378. {
  1379. m_pime->_EnsureWorkerWnd();
  1380. hr = cpNotify->InitCallback( NotifyCallback, 0, (LPARAM)this );
  1381. TraceMsg(TF_SAPI_PERF, "InitCallback is Done, hr=%x", hr);
  1382. }
  1383. if (SUCCEEDED(hr))
  1384. {
  1385. hr = m_cpRecoCtxt->SetNotifySink(cpNotify);
  1386. TraceMsg(TF_SAPI_PERF, "SetNotifySink is Done, hr=%x", hr);
  1387. }
  1388. // set the events we're interested in
  1389. if( SUCCEEDED( hr ) )
  1390. {
  1391. const ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) |
  1392. SPFEI(SPEI_SOUND_END) |
  1393. SPFEI(SPEI_PHRASE_START) |
  1394. SPFEI(SPEI_RECOGNITION) |
  1395. SPFEI(SPEI_RECO_OTHER_CONTEXT) |
  1396. SPFEI(SPEI_FALSE_RECOGNITION) |
  1397. SPFEI(SPEI_HYPOTHESIS) |
  1398. SPFEI(SPEI_RECO_STATE_CHANGE) |
  1399. SPFEI(SPEI_INTERFERENCE);
  1400. hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest);
  1401. TraceMsg(TF_SAPI_PERF, "SetInterest is Done, hr=%x", hr);
  1402. }
  1403. if ( SUCCEEDED(hr) && m_cpVoice)
  1404. {
  1405. // set recognition notification
  1406. CComPtr<ISpNotifyTranslator> cpNotify;
  1407. hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
  1408. TraceMsg(TF_SAPI_PERF, "Create SpNotifyTranslator for spVoice, hr=%x", hr);
  1409. // set this class instance to notify control object
  1410. if (SUCCEEDED(hr))
  1411. {
  1412. m_pime->_EnsureWorkerWnd();
  1413. hr = cpNotify->InitCallback( SpeakNotifyCallback, 0, (LPARAM)this );
  1414. TraceMsg(TF_SAPI_PERF, "InitCallback for SpVoice, hr=%x", hr);
  1415. }
  1416. if (SUCCEEDED(hr))
  1417. {
  1418. hr = m_cpVoice->SetNotifySink(cpNotify);
  1419. TraceMsg(TF_SAPI_PERF, "SetNotifySink for SpVoice, hr=%x", hr);
  1420. }
  1421. if ( hr == S_OK )
  1422. {
  1423. const ULONGLONG ulInterestSpeak = SPFEI(SPEI_WORD_BOUNDARY) |
  1424. SPFEI(SPEI_START_INPUT_STREAM) |
  1425. SPFEI(SPEI_END_INPUT_STREAM);
  1426. hr = m_cpVoice->SetInterest(ulInterestSpeak, ulInterestSpeak);
  1427. TraceMsg(TF_SAPI_PERF, "SetInterest for spVoice, hr=%x", hr);
  1428. }
  1429. }
  1430. m_fCallbackInitialized = TRUE;
  1431. TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called is done!!! hr=%x", hr);
  1432. return hr;
  1433. }
  1434. //
  1435. // _LoadGrammars
  1436. //
  1437. // synopsis - load CFG for dictation and commands available during dictation
  1438. //
  1439. HRESULT CSpTask::_LoadGrammars()
  1440. {
  1441. HRESULT hr = E_FAIL;
  1442. TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is called");
  1443. if (m_cpRecoCtxt)
  1444. {
  1445. hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_DICT, &m_cpDictGrammar);
  1446. TraceMsg(TF_SAPI_PERF, "Create Dict Grammar, hr=%x", hr);
  1447. if (SUCCEEDED(hr))
  1448. {
  1449. hr = m_cpDictGrammar->LoadDictation(NULL, SPLO_STATIC);
  1450. TraceMsg(TF_SAPI_PERF, "Load Dictation, hr = %x", hr);
  1451. }
  1452. if ( S_OK == hr && m_langid != 0x0804) // Chinese Engine doesn't support SPTOPIC_SPELLING,
  1453. // This is the temporal workaround.
  1454. {
  1455. // we keep going regardless of availabillity of spelling topic
  1456. // in the SR engine for the language so we use internal HRESULT
  1457. // for this block of code
  1458. //
  1459. HRESULT hrInternal;
  1460. // Load Spelling topic
  1461. hrInternal = m_cpRecoCtxt->CreateGrammar(GRAM_ID_SPELLING, &m_cpSpellingGrammar);
  1462. TraceMsg(TF_SAPI_PERF, "Create Spelling grammar, hrInternal=%x", hrInternal);
  1463. if (SUCCEEDED(hrInternal))
  1464. {
  1465. hrInternal = m_cpSpellingGrammar->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC);
  1466. TraceMsg(TF_SAPI_PERF, "Load Spelling dictation grammar, hrInternal=%x", hrInternal);
  1467. }
  1468. // this is now an experiment for English/Japanese only
  1469. //
  1470. if (SUCCEEDED(hrInternal))
  1471. {
  1472. hrInternal = m_cpSpellingGrammar->LoadCmdFromResource(
  1473. g_hInstSpgrmr,
  1474. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SPELLING_TOPIC_CFG),
  1475. L"SRGRAMMAR",
  1476. m_langid,
  1477. SPLO_STATIC);
  1478. TraceMsg(TF_SAPI_PERF, "Load CFG grammar spell.cfg, hr=%x", hrInternal);
  1479. }
  1480. if (S_OK == hrInternal)
  1481. {
  1482. m_fSpellingModeEnabled = TRUE;
  1483. }
  1484. else
  1485. m_fSpellingModeEnabled = FALSE;
  1486. TraceMsg(TF_SAPI_PERF, "m_fSpellingModeEnabled=%d", m_fSpellingModeEnabled);
  1487. }
  1488. //
  1489. // load the dictation mode commands
  1490. //
  1491. if (SUCCEEDED(hr) )
  1492. {
  1493. hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CCDICT, &m_cpDictCmdGrammar);
  1494. TraceMsg(TF_SAPI_PERF, "Create DictCmdGrammar, hr=%x", hr);
  1495. }
  1496. if (S_OK == hr)
  1497. {
  1498. hr = S_FALSE;
  1499. // try resource first because loading cmd from file takes
  1500. // quite long time
  1501. //
  1502. if (m_langid == 0x409 || // English
  1503. m_langid == 0x411 || // Japanese
  1504. m_langid == 0x804 ) // Simplified Chinese
  1505. {
  1506. hr = m_cpDictCmdGrammar->LoadCmdFromResource(
  1507. g_hInstSpgrmr,
  1508. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG),
  1509. L"SRGRAMMAR",
  1510. m_langid,
  1511. SPLO_DYNAMIC);
  1512. TraceMsg(TF_SAPI_PERF, "Load dictcmd.cfg, hr=%x", hr);
  1513. }
  1514. if (S_OK != hr)
  1515. {
  1516. // in case if we don't have built-in grammar
  1517. _GetCmdFileName(m_langid);
  1518. if (m_szCmdFile[0])
  1519. {
  1520. hr = m_cpDictCmdGrammar->LoadCmdFromFile(m_szCmdFile, SPLO_DYNAMIC);
  1521. }
  1522. }
  1523. if (S_OK != hr)
  1524. {
  1525. m_cpDictCmdGrammar.Release();
  1526. }
  1527. }
  1528. // load shared command grammars
  1529. if (SUCCEEDED(hr) )
  1530. {
  1531. hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInDict);
  1532. TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammarInDict, hr=%x", hr);
  1533. }
  1534. if (S_OK == hr)
  1535. {
  1536. hr = S_FALSE;
  1537. if (m_langid == 0x409 || // English
  1538. m_langid == 0x411 || // Japanese
  1539. m_langid == 0x804 ) // Simplified Chinese
  1540. {
  1541. hr = m_cpSharedGrammarInDict->LoadCmdFromResource(
  1542. g_hInstSpgrmr,
  1543. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG),
  1544. L"SRGRAMMAR",
  1545. m_langid,
  1546. SPLO_DYNAMIC);
  1547. TraceMsg(TF_SAPI_PERF, "Load Shrdcmd.cfg, hr=%x", hr);
  1548. }
  1549. if (S_OK != hr)
  1550. {
  1551. // in case if we don't have built-in grammar
  1552. // it provides a way for customer to localize their grammars in different languages
  1553. _GetCmdFileName(m_langid);
  1554. if (m_szShrdCmdFile[0])
  1555. {
  1556. hr = m_cpSharedGrammarInDict->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC);
  1557. }
  1558. }
  1559. if (S_OK != hr)
  1560. {
  1561. m_cpSharedGrammarInDict.Release();
  1562. }
  1563. else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH ||
  1564. PRIMARYLANGID(m_langid) == LANG_JAPANESE ||
  1565. PRIMARYLANGID(m_langid) == LANG_CHINESE)
  1566. {
  1567. // means this language's grammar support Textbuffer commands.
  1568. m_fSelectionEnabled = TRUE;
  1569. }
  1570. }
  1571. //
  1572. // load mode bias grammars
  1573. //
  1574. if (S_OK == hr)
  1575. {
  1576. hr = m_cpRecoCtxt->CreateGrammar(GRID_INTEGER_STANDALONE, &m_cpNumModeGrammar);
  1577. TraceMsg(TF_SAPI_PERF, "Create NumModeGrammar, hr=%x", hr);
  1578. }
  1579. if (S_OK == hr)
  1580. {
  1581. hr = S_FALSE;
  1582. // try resource first because loading cmd from file takes
  1583. // quite long time
  1584. //
  1585. if ( m_langid == 0x409 // English
  1586. || m_langid == 0x411 // Japanese
  1587. || m_langid == 0x804 // Simplified Chinese
  1588. )
  1589. {
  1590. hr = m_cpNumModeGrammar->LoadCmdFromResource(
  1591. g_hInstSpgrmr,
  1592. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_NUMMODE_COMMAND_CFG),
  1593. L"SRGRAMMAR",
  1594. m_langid,
  1595. SPLO_DYNAMIC);
  1596. TraceMsg(TF_SAPI_PERF, "Load dictnum.cfg, hr=%x", hr);
  1597. }
  1598. if (S_OK != hr)
  1599. {
  1600. // in case if we don't have buit-in grammar
  1601. //
  1602. if (m_szNumModeCmdFile[0])
  1603. {
  1604. hr = m_cpNumModeGrammar->LoadCmdFromFile(m_szNumModeCmdFile, SPLO_DYNAMIC);
  1605. }
  1606. }
  1607. if (S_OK != hr)
  1608. {
  1609. m_cpNumModeGrammar.Release();
  1610. }
  1611. }
  1612. }
  1613. // By default, Activate all the grammars and Disable the context for Perfomance.
  1614. if ( SUCCEEDED(hr) )
  1615. {
  1616. hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
  1617. m_fDictCtxtEnabled = FALSE;
  1618. }
  1619. // Activate Dictation and spell.
  1620. if ( SUCCEEDED(hr) )
  1621. {
  1622. hr = _ActiveDictOrSpell(DC_Dictation, TRUE);
  1623. if ( hr == S_OK )
  1624. hr = _ActiveDictOrSpell(DC_Dict_Spell, TRUE);
  1625. }
  1626. // Automatically active all rules in C&C grammars.
  1627. if ( SUCCEEDED(hr) )
  1628. {
  1629. if ( m_pime->_AllDictCmdsDisabled( ) )
  1630. {
  1631. hr = _ActivateCmdInDictMode(FALSE);
  1632. // Still needs to activate spelling grammar if it exists.
  1633. if ( hr == S_OK )
  1634. hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
  1635. // Needs to activate "Force Num" grammar in dication strong mode.
  1636. if ( hr == S_OK )
  1637. hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
  1638. if ( hr == S_OK )
  1639. hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1640. }
  1641. else
  1642. {
  1643. if ( m_pime->_AllCmdsEnabled( ) )
  1644. hr = _ActivateCmdInDictMode(TRUE);
  1645. else
  1646. {
  1647. // Some category commands are disabled.
  1648. // active them individually.
  1649. hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1650. if ( hr == S_OK )
  1651. hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1652. if ( hr == S_OK )
  1653. hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1654. if ( hr == S_OK )
  1655. hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1656. if ( hr == S_OK )
  1657. hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1658. if ( hr == S_OK )
  1659. hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1660. if ( hr == S_OK )
  1661. hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1662. if ( hr == S_OK )
  1663. hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
  1664. if ( hr == S_OK )
  1665. hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
  1666. }
  1667. }
  1668. }
  1669. // we don't fail even if C&C grammars are not available
  1670. TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is done!!!!");
  1671. return S_OK;
  1672. }
  1673. WCHAR * CSpTask::_GetCmdFileName(LANGID langid)
  1674. {
  1675. if (!m_szCmdFile[0])
  1676. {
  1677. _GetCmdFileName(langid, m_szCmdFile, ARRAYSIZE(m_szCmdFile), IDS_CMD_FILE);
  1678. }
  1679. // load the name of shared commands grammar
  1680. if (!m_szShrdCmdFile[0])
  1681. {
  1682. _GetCmdFileName(langid, m_szShrdCmdFile, ARRAYSIZE(m_szShrdCmdFile), IDS_SHARDCMD_FILE);
  1683. }
  1684. // load the name of optional grammar
  1685. if (!m_szNumModeCmdFile[0])
  1686. {
  1687. _GetCmdFileName(langid, m_szNumModeCmdFile, ARRAYSIZE(m_szNumModeCmdFile), IDS_NUMMODE_CMD_FILE );
  1688. }
  1689. return m_szCmdFile;
  1690. }
  1691. void CSpTask::_GetCmdFileName(LANGID langid, WCHAR *sz, int cch, DWORD dwId)
  1692. {
  1693. /*
  1694. // now we only have a command file for English/Japanese
  1695. // when cfgs are available, we'll get the name of cmd file
  1696. // and the rule names from resources using findresourceex
  1697. //
  1698. if ((PRIMARYLANGID(langid) == LANG_ENGLISH)
  1699. || (PRIMARYLANGID(langid) == LANG_JAPANESE)
  1700. || (PRIMARYLANGID(langid) == LANG_CHINESE))
  1701. {
  1702. // To supply customers a way to localize their grammars in different languages,
  1703. // we don't want the above condition check.
  1704. */
  1705. char szFilePath[MAX_PATH];
  1706. char *pszFileName;
  1707. char szCp[MAX_PATH];
  1708. int ilen;
  1709. if (!GetModuleFileName(g_hInst, szFilePath, ARRAYSIZE(szFilePath)))
  1710. return;
  1711. // is this dbcs safe?
  1712. pszFileName = strrchr(szFilePath, (int)'\\');
  1713. if (pszFileName)
  1714. {
  1715. pszFileName++;
  1716. *pszFileName = '\0';
  1717. }
  1718. else
  1719. {
  1720. szFilePath[0] = '\\';
  1721. szFilePath[1] = '\0';
  1722. pszFileName = &szFilePath[1];
  1723. }
  1724. ilen = lstrlen(szFilePath);
  1725. CicLoadStringA(g_hInst, dwId, pszFileName, ARRAYSIZE(szFilePath)-ilen);
  1726. if (GetLocaleInfo(langid, LOCALE_IDEFAULTANSICODEPAGE, szCp, ARRAYSIZE(szCp))>0)
  1727. {
  1728. int iACP = atoi(szCp);
  1729. MultiByteToWideChar(iACP, NULL, szFilePath, -1, sz, cch);
  1730. }
  1731. // }
  1732. }
  1733. void CSpTask::_ReleaseSAPI(void)
  1734. {
  1735. // - release data or memory from recognition context
  1736. // - release interfaces if they are not defined as CComPtr
  1737. _UnloadGrammars();
  1738. m_cpResMgr.Release();
  1739. if ( m_cpVoice)
  1740. m_cpVoice->SetNotifySink(NULL);
  1741. m_cpVoice.Release();
  1742. if (m_cpRecoCtxt)
  1743. m_cpRecoCtxt->SetNotifySink(NULL);
  1744. if ( m_cpRecoCtxtForCmd )
  1745. m_cpRecoCtxtForCmd->SetNotifySink(NULL);
  1746. #ifdef RECOSLEEP
  1747. if ( m_pSleepClass )
  1748. {
  1749. delete m_pSleepClass;
  1750. m_pSleepClass = NULL;
  1751. }
  1752. #endif
  1753. m_cpRecoCtxt.Release();
  1754. m_cpRecoCtxtForCmd.Release();
  1755. m_cpRecoEngine.Release();
  1756. m_fSapiInitialized = FALSE;
  1757. }
  1758. HRESULT CSpTask::_SetAudioRetainStatus(BOOL fRetain)
  1759. {
  1760. HRESULT hr = E_FAIL;
  1761. // FutureConsider: support the data format
  1762. if (m_cpRecoCtxt)
  1763. hr = m_cpRecoCtxt->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL);
  1764. if (m_cpRecoCtxtForCmd)
  1765. hr = m_cpRecoCtxtForCmd->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL);
  1766. return hr;
  1767. }
  1768. HRESULT CSpTask::_SetRecognizerInterest(ULONGLONG ulInterest)
  1769. {
  1770. HRESULT hr = S_OK;
  1771. if ( m_cpRecoCtxt )
  1772. {
  1773. hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest);
  1774. }
  1775. return hr;
  1776. }
  1777. //
  1778. //
  1779. // Activate all the command grammas in Dictation mode.
  1780. //
  1781. // By default we want to set SPRS_ACTIVE to all the command grammar rules
  1782. // in dictation mode unless user disables some of commands through dictation
  1783. // property page.
  1784. //
  1785. // Please note: Only when all the commands are enabled, this function is called.
  1786. //
  1787. // otherwise,
  1788. //
  1789. // When some of the commands are disabled, we should active individual cateogry commands by
  1790. // calling _ActiveCategoryCmds( ).
  1791. //
  1792. HRESULT CSpTask::_ActivateCmdInDictMode(BOOL fActive)
  1793. {
  1794. HRESULT hr = E_FAIL;
  1795. BOOL fRealActive = fActive;
  1796. TraceMsg(TF_SAPI_PERF, "CSpTask::_ActivateCmdInDictMode is called, fActive=%d", fActive);
  1797. if (m_cpRecoCtxt)
  1798. {
  1799. // Automatically active or inactive all rules in grammar.
  1800. // Rules in Dictcmd.cfg
  1801. if ( m_cpDictCmdGrammar )
  1802. {
  1803. hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1804. TraceMsg(TF_SAPI_PERF, "Set rules status in DictCmdGrammar, fRealActive=%d", fRealActive);
  1805. }
  1806. // Rules in Sharedcmd.cfg
  1807. if ( m_cpSharedGrammarInDict )
  1808. {
  1809. hr = m_cpSharedGrammarInDict->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1810. TraceMsg(TF_SAPI_PERF, "Set rules status in SharedCmdGrammar In Dictation Mode, fRealActive=%d", fRealActive);
  1811. }
  1812. // Rules in ITN grammar
  1813. if ( hr == S_OK && m_cpNumModeGrammar )
  1814. {
  1815. hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1816. TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpNumModeGrammar, fRealActive=%d", fRealActive);
  1817. }
  1818. // Rules in Spell grammar
  1819. if ( m_cpSpellingGrammar )
  1820. {
  1821. hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1822. TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fRealActive=%d", fRealActive);
  1823. }
  1824. }
  1825. TraceMsg(TF_SAPI_PERF, "Exit from CSpTask::_ActivateCmdInDictMode");
  1826. return hr;
  1827. }
  1828. //
  1829. // Active commands by category.
  1830. //
  1831. // Some commands are dictation mode only such as "spell that" and Number mode commands.
  1832. // some others are available in both modes,
  1833. //
  1834. // When some category commands are disabled, caller must call this function instead of
  1835. // _ActivateCmdInDictMode to set individual category commands.
  1836. //
  1837. HRESULT CSpTask::_ActiveCategoryCmds(DICT_CATCMD_ID dcId, BOOL fActive, DWORD dwMode)
  1838. {
  1839. HRESULT hr = S_OK;
  1840. BOOL fActiveDictMode, fActiveCommandMode;
  1841. if ( dcId >= DC_Max ) return E_INVALIDARG;
  1842. if (m_fIn_Activate)
  1843. return hr;
  1844. fActiveDictMode = (m_cpRecoCtxt && (dwMode & ACTIVE_IN_DICTATION_MODE) ) ? TRUE : FALSE;
  1845. if ( m_cpRecoCtxtForCmd && m_cpSharedGrammarInVoiceCmd && ( dwMode & ACTIVE_IN_COMMAND_MODE) )
  1846. fActiveCommandMode = TRUE;
  1847. else
  1848. fActiveCommandMode = FALSE;
  1849. m_fIn_Activate = TRUE;
  1850. switch (dcId)
  1851. {
  1852. case DC_CC_SelectCorrect :
  1853. // This category includes below rules in different grammars.
  1854. //
  1855. // shrdcmd.xml:
  1856. // selword, SelectThrough, SelectSimpleCmds,
  1857. //
  1858. // dictcmd.xml:
  1859. // commands
  1860. //
  1861. TraceMsg(TF_SAPI_PERF, "DC_CC_SelectCorrect status: %d, mode=%d", fActive, dwMode);
  1862. if ( fActiveDictMode)
  1863. {
  1864. // for dictation mode
  1865. if ( m_cpSharedGrammarInDict )
  1866. {
  1867. hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1868. if ( hr == S_OK )
  1869. hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1870. if ( hr == S_OK )
  1871. hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1872. }
  1873. }
  1874. if ( (hr == S_OK) && fActiveCommandMode )
  1875. {
  1876. // for voice command mode
  1877. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1878. if ( hr == S_OK )
  1879. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1880. if ( hr == S_OK )
  1881. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1882. }
  1883. break;
  1884. case DC_CC_Navigation :
  1885. // This category includes rule NavigationCmds in shrdcmd.xml
  1886. //
  1887. TraceMsg(TF_SAPI_PERF, "DC_CC_Navigation status: %d, mode=%d", fActive, dwMode);
  1888. if ( fActiveDictMode && m_cpSharedGrammarInDict)
  1889. {
  1890. // for dictation mode
  1891. hr = m_cpSharedGrammarInDict->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1892. }
  1893. if ( (hr == S_OK) && fActiveCommandMode )
  1894. {
  1895. // for voice command mode
  1896. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1897. }
  1898. break;
  1899. case DC_CC_Casing :
  1900. // This category includes rule CasingCmds in shrdcmd.xml
  1901. TraceMsg(TF_SAPI_PERF, "DC_CC_Casing status: %d, mode=%d", fActive, dwMode);
  1902. if ( fActiveDictMode && m_cpSharedGrammarInDict )
  1903. {
  1904. // for dictation mode
  1905. hr = m_cpSharedGrammarInDict->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1906. }
  1907. if ( (hr == S_OK) && fActiveCommandMode)
  1908. {
  1909. // for voice command mode
  1910. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1911. }
  1912. break;
  1913. case DC_CC_Editing :
  1914. // This category includes rule EditCmds in shrdcmd.xml
  1915. TraceMsg(TF_SAPI_PERF, "DC_CC_Editing status: %d, mode=%d", fActive, dwMode);
  1916. if ( fActiveDictMode && m_cpSharedGrammarInDict)
  1917. {
  1918. // for dictation mode
  1919. hr = m_cpSharedGrammarInDict->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1920. }
  1921. if ( (hr == S_OK) && fActiveCommandMode)
  1922. {
  1923. // for voice command mode
  1924. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1925. }
  1926. break;
  1927. case DC_CC_Keyboard :
  1928. // This category includes rule KeyboardCmds in shrdcmd.xml
  1929. TraceMsg(TF_SAPI_PERF, "DC_CC_Keyboard status: %d, mode=%d", fActive, dwMode);
  1930. if ( fActiveDictMode && m_cpSharedGrammarInDict)
  1931. {
  1932. // for dictation mode
  1933. hr = m_cpSharedGrammarInDict->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1934. }
  1935. if ( (hr == S_OK) && fActiveCommandMode)
  1936. {
  1937. // for voice command mode
  1938. hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1939. }
  1940. break;
  1941. case DC_CC_TTS :
  1942. // The rule for this category is not implemented yet!!!
  1943. break;
  1944. case DC_CC_LangBar :
  1945. // This category includes rule ToolbarCmd in dictcmd.xml for dictation mode.
  1946. // for voice command mode, it is a dynamical rule.
  1947. //
  1948. TraceMsg(TF_SAPI_PERF, "DC_CC_LangBar status: %d, mode=%d", fActive, dwMode);
  1949. if ( fActiveDictMode && m_cpDictCmdGrammar)
  1950. {
  1951. // for dictation mode
  1952. hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1953. }
  1954. if ( (hr == S_OK) && fActiveCommandMode )
  1955. {
  1956. // for voice command mode
  1957. // Change toolbar grammar status if it has already been built.
  1958. if (m_pLangBarSink && m_pLangBarSink->_IsTBGrammarBuiltOut( ))
  1959. m_pLangBarSink->_ActivateGrammar(fActive);
  1960. }
  1961. break;
  1962. case DC_CC_Num_Mode :
  1963. if (fActiveDictMode && m_cpNumModeGrammar)
  1964. {
  1965. hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
  1966. TraceMsg(TF_SAPI_PERF, "CC Number rule status changed to %d", fActive);
  1967. }
  1968. break;
  1969. case DC_CC_UrlHistory :
  1970. if (fActiveDictMode && m_cpDictCmdGrammar)
  1971. {
  1972. hr = m_cpDictCmdGrammar->SetRuleState(c_szStaticUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
  1973. if (S_OK == hr)
  1974. hr = m_cpDictCmdGrammar->SetRuleState(c_szDynUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
  1975. if (S_OK == hr && m_cpUrlSpellingGrammar)
  1976. {
  1977. hr = m_cpUrlSpellingGrammar->SetRuleState(c_szStaticUrlSpell, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
  1978. }
  1979. }
  1980. break;
  1981. case DC_CC_Spelling :
  1982. if ( fActiveDictMode && m_cpSpellingGrammar )
  1983. {
  1984. hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
  1985. TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fActive=%d", fActive);
  1986. }
  1987. break;
  1988. }
  1989. m_fIn_Activate = FALSE;
  1990. return hr;
  1991. }
  1992. // Set the status for Dictation grammar or spelling grammar In Dictation mode only.
  1993. HRESULT CSpTask::_ActiveDictOrSpell(DICT_CATCMD_ID dcId, BOOL fActive)
  1994. {
  1995. HRESULT hr = S_OK;
  1996. if ( dcId >= DC_Max ) return E_INVALIDARG;
  1997. if (m_fIn_Activate)
  1998. return hr;
  1999. m_fIn_Activate = TRUE;
  2000. switch (dcId)
  2001. {
  2002. case DC_Dictation :
  2003. if (m_cpDictGrammar)
  2004. {
  2005. hr = m_cpDictGrammar->SetDictationState(fActive ? SPRS_ACTIVE : SPRS_INACTIVE);
  2006. TraceMsg(TF_SAPI_PERF, "Dictation status changed to %d", fActive);
  2007. }
  2008. break;
  2009. case DC_Dict_Spell :
  2010. if (m_cpSpellingGrammar)
  2011. {
  2012. hr = _SetSpellingGrammarStatus(fActive);
  2013. TraceMsg(TF_SAPI_PERF, "Dict Spell status changed to %d", fActive);
  2014. }
  2015. break;
  2016. }
  2017. m_fIn_Activate = FALSE;
  2018. return hr;
  2019. }
  2020. HRESULT CSpTask::_SetSpellingGrammarStatus( BOOL fActive, BOOL fForce)
  2021. {
  2022. HRESULT hr = S_OK;
  2023. TraceMsg(TF_GENERAL, "_SetSpellingGrammarStatus is called, fActive=%d, m_fSelectStatus=%d",fActive, m_fSelectStatus);
  2024. if ( m_cpSpellingGrammar )
  2025. {
  2026. // if dictation is previously deactivated because of 'force' spelling
  2027. // we need to reactivate the dictation grammar
  2028. if (m_fDictationDeactivated)
  2029. {
  2030. hr = _ActiveDictOrSpell(DC_Dictation, TRUE);
  2031. if (S_OK == hr)
  2032. {
  2033. m_fDictationDeactivated = FALSE;
  2034. }
  2035. }
  2036. // if this is 'force' mode, we deactivate dictation for the moment
  2037. if (fForce)
  2038. {
  2039. hr = _ActiveDictOrSpell(DC_Dictation, FALSE);
  2040. if (S_OK == hr)
  2041. {
  2042. m_fDictationDeactivated = TRUE;
  2043. }
  2044. }
  2045. if ( (m_fSelectStatus || fForce) && fActive) // It is not empty
  2046. hr = m_cpSpellingGrammar->SetDictationState(SPRS_ACTIVE);
  2047. else
  2048. hr = m_cpSpellingGrammar->SetDictationState(SPRS_INACTIVE);
  2049. }
  2050. return hr;
  2051. }
  2052. HRESULT CSpTask::_AddUrlPartsToGrammar(STATURL *pStat)
  2053. {
  2054. Assert(pStat);
  2055. // get a url broken down to pieces
  2056. if (!pStat->pwcsUrl)
  2057. return S_FALSE;
  2058. WCHAR *pch = pStat->pwcsUrl;
  2059. const WCHAR c_szHttpSlash2[] = L"http://";
  2060. // skip the prefixed http: stuff because we've already added it by now
  2061. if (_wcsnicmp(pch, c_szHttpSlash2, ARRAYSIZE(c_szHttpSlash2)-1) == 0)
  2062. pch += ARRAYSIZE(c_szHttpSlash2)-1;
  2063. WCHAR *pchWord = pch;
  2064. HRESULT hr = S_OK;
  2065. // an assumption 1) people speak the first portion of URL www.microsoft.com
  2066. // as a sentence
  2067. WCHAR *pchUrl = pch; // points either biggining of url or
  2068. // right after 'http://' add the first part
  2069. BOOL fUrlAdded = FALSE; // of url that is between after this and '/'
  2070. while(S_OK == hr && *pch)
  2071. {
  2072. if (*pch == L'/')
  2073. {
  2074. if (!fUrlAdded)
  2075. {
  2076. if( pch - pchUrl > 1)
  2077. {
  2078. WCHAR ch = *pch;
  2079. *pch = L'\0';
  2080. SPPROPERTYINFO pi = {0};
  2081. pi.pszValue = pchUrl;
  2082. hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi);
  2083. *pch = ch;
  2084. }
  2085. fUrlAdded = TRUE;
  2086. }
  2087. else
  2088. {
  2089. *pch = L'\0';
  2090. break;
  2091. }
  2092. }
  2093. if (*pch == L'.' || *pch == L'/' || *pch == L'?' || *pch == '=' || *pch =='&')
  2094. {
  2095. WCHAR ch = *pch;
  2096. *pch = L'\0';
  2097. // reject 1 character parts
  2098. if (pch - pchWord > 1)
  2099. {
  2100. SPPROPERTYINFO pi = {0};
  2101. pi.pszValue = pchWord;
  2102. if (wcscmp(c_szWWW, pchWord) != 0 && wcscmp(c_szCom, pchWord) != 0)
  2103. {
  2104. // a few words can possibly return 'ambiguity' errors
  2105. // we need to ignore it and continue. so we're not checking
  2106. // the return here.
  2107. //
  2108. m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi);
  2109. }
  2110. }
  2111. *pch = ch;
  2112. pchWord = pch + 1;
  2113. }
  2114. pch++;
  2115. }
  2116. // add the last part of URL
  2117. if (S_OK == hr && *pchWord && pch - pchWord > 1)
  2118. {
  2119. SPPROPERTYINFO pi = {0};
  2120. pi.pszValue = pchWord;
  2121. hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi);
  2122. }
  2123. // add the first part of url if we haven't yet
  2124. if (S_OK == hr && !fUrlAdded && pch - pchUrl > 1)
  2125. {
  2126. SPPROPERTYINFO pi = {0};
  2127. pi.pszValue = pchUrl;
  2128. hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi);
  2129. }
  2130. return hr;
  2131. }
  2132. BOOL CSpTask::_EnsureModeBiasGrammar()
  2133. {
  2134. HRESULT hr = S_OK;
  2135. if ( m_cpDictCmdGrammar )
  2136. {
  2137. // Check if the grammar has a static rule UrlSpelling
  2138. SPSTATEHANDLE hRuleUrlSpell = 0;
  2139. hr = m_cpDictCmdGrammar->GetRule(c_szStaticUrlSpell, 0, SPRAF_TopLevel|SPRAF_Active, FALSE, &hRuleUrlSpell);
  2140. if ( !hRuleUrlSpell )
  2141. return FALSE;
  2142. }
  2143. // ensure spelling LM
  2144. if (!m_cpUrlSpellingGrammar)
  2145. {
  2146. CComPtr<ISpRecoGrammar> cpUrlSpelling;
  2147. hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_URLSPELL, &cpUrlSpelling);
  2148. // load dictation with spelling topic
  2149. if (S_OK == hr)
  2150. {
  2151. hr = cpUrlSpelling->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC);
  2152. }
  2153. // load the 'rule' for the free form dictation
  2154. if (S_OK == hr)
  2155. {
  2156. // i'm sharing the command cfg here for spelling with dictation
  2157. // command for simplicity
  2158. //
  2159. hr = cpUrlSpelling->LoadCmdFromResource( g_hInstSpgrmr,
  2160. (const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG),
  2161. L"SRGRAMMAR",
  2162. m_langid,
  2163. SPLO_STATIC);
  2164. }
  2165. if (S_OK == hr)
  2166. {
  2167. m_cpUrlSpellingGrammar = cpUrlSpelling; // add the ref count
  2168. }
  2169. }
  2170. if (m_hRuleUrlHist)
  2171. {
  2172. hr = m_cpDictCmdGrammar->ClearRule(m_hRuleUrlHist);
  2173. m_hRuleUrlHist = 0;
  2174. }
  2175. if (S_OK == hr)
  2176. hr = m_cpDictCmdGrammar->GetRule(c_szDynUrlHist, 0, SPRAF_TopLevel|SPRAF_Active|SPRAF_Dynamic, TRUE, &m_hRuleUrlHist);
  2177. // first add basic parts for URL
  2178. CComPtr<IUrlHistoryStg> cpUrlHistStg;
  2179. if (S_OK == hr)
  2180. {
  2181. hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER, IID_IUrlHistoryStg, (void **)&cpUrlHistStg);
  2182. }
  2183. CComPtr<IEnumSTATURL> cpEnumUrl;
  2184. if (S_OK == hr)
  2185. {
  2186. hr = cpUrlHistStg->EnumUrls(&cpEnumUrl);
  2187. }
  2188. if (S_OK == hr)
  2189. {
  2190. int i = 0;
  2191. STATURL stat;
  2192. stat.cbSize = SIZEOF(stat.cbSize);
  2193. while(i < 10 && S_OK == hr && cpEnumUrl->Next(1, &stat, NULL)==S_OK && stat.pwcsUrl)
  2194. {
  2195. hr = _AddUrlPartsToGrammar(&stat);
  2196. i++;
  2197. }
  2198. }
  2199. if (S_OK == hr)
  2200. {
  2201. hr = m_cpDictCmdGrammar->Commit(0);
  2202. }
  2203. return (S_OK == hr) ? TRUE : FALSE;
  2204. }
  2205. HRESULT CSpTask::_SetModeBias(BOOL fActive, REFGUID rGuid)
  2206. {
  2207. HRESULT hr = S_OK;
  2208. BOOL fKillDictation = FALSE;
  2209. if (m_fIn_SetModeBias)
  2210. return E_FAIL;
  2211. m_fIn_SetModeBias = TRUE;
  2212. if (m_cpDictGrammar)
  2213. {
  2214. fKillDictation = !m_pime->_IsModeBiasDictationEnabled();
  2215. if (fActive)
  2216. {
  2217. BOOL fUrlHistory = FALSE;
  2218. if (IsEqualGUID(GUID_MODEBIAS_URLHISTORY, rGuid)
  2219. || IsEqualGUID(GUID_MODEBIAS_FILENAME, rGuid))
  2220. fUrlHistory = TRUE;
  2221. // first deactivate rules when we are not setting them
  2222. if (!fUrlHistory && m_fUrlHistoryMode)
  2223. {
  2224. hr = _ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE);
  2225. }
  2226. // this check with m_fUrlHistoryMode is preventing us from updating url dynamic grammar
  2227. // when mic is re-opened. we think removing this won't cause much perf degredation.
  2228. //
  2229. if (fUrlHistory /* && !m_fUrlHistoryMode */)
  2230. {
  2231. if (m_cpDictCmdGrammar && m_pime->GetDICTATIONSTAT_DictOnOff() && _EnsureModeBiasGrammar())
  2232. {
  2233. hr = _ActiveCategoryCmds(DC_CC_UrlHistory, TRUE, ACTIVE_IN_DICTATION_MODE);
  2234. }
  2235. else
  2236. fUrlHistory = FALSE;
  2237. if (fUrlHistory)
  2238. {
  2239. fKillDictation = TRUE;
  2240. }
  2241. }
  2242. // sync the global status
  2243. m_fUrlHistoryMode = fUrlHistory;
  2244. }
  2245. else
  2246. {
  2247. // reset all modebias
  2248. if (m_fUrlHistoryMode)
  2249. _ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE);
  2250. }
  2251. // kill dictation grammar when mode requires it
  2252. // we should activate dictation only when deactivating
  2253. // the modebias grammar *and* when we are the focus
  2254. // thread because we've already deactivated dictation
  2255. // when focus switched away
  2256. //
  2257. if (/* !fActive && */
  2258. m_cpDictGrammar &&
  2259. m_pime->GetDICTATIONSTAT_DictOnOff() &&
  2260. S_OK == m_pime->IsActiveThread())
  2261. {
  2262. #ifdef _DEBUG_
  2263. TCHAR szModule[MAX_PATH];
  2264. GetModuleFileName(NULL, szModule, ARRAYSIZE(szModule));
  2265. TraceMsg(TF_GENERAL, "%s : CSpTask::_SetModeBias() - Turning Dictation grammar %s", szModule, fKillDictation ? "Off" : "On");
  2266. #endif
  2267. if (!fActive && fKillDictation)
  2268. {
  2269. fKillDictation = FALSE;
  2270. }
  2271. hr = _ActiveDictOrSpell(DC_Dictation, fKillDictation ? SPRS_INACTIVE : SPRS_ACTIVE);
  2272. }
  2273. }
  2274. m_fIn_SetModeBias = FALSE;
  2275. return hr;
  2276. }
  2277. void CSpTask::_SetInputOnOffState(BOOL fOn)
  2278. {
  2279. TraceMsg(TF_GENERAL, "_SetInputOnOffState is called, fOn=%d", fOn);
  2280. if (m_fIn_SetInputOnOffState)
  2281. return;
  2282. m_fIn_SetInputOnOffState = TRUE;
  2283. // here we make sure we erase feedback UI
  2284. // We only adjust these if we are the active thread. Otherwise leave in current state since we either do
  2285. // not have focus or the stage is visible. This maintains our previous behavior where we would not get here
  2286. // if we were not the active thread.
  2287. if (S_OK == m_pime->IsActiveThread())
  2288. {
  2289. if (fOn)
  2290. {
  2291. if (!m_pime->Get_SPEECH_DISABLED_DictationDisabled() && m_pime->GetDICTATIONSTAT_DictOnOff())
  2292. _SetDictRecoCtxtState(TRUE);
  2293. if ( !m_pime->Get_SPEECH_DISABLED_CommandingDisabled( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( ) )
  2294. _SetCmdRecoCtxtState(TRUE);
  2295. }
  2296. else
  2297. {
  2298. _SetDictRecoCtxtState(FALSE);
  2299. _SetCmdRecoCtxtState(FALSE);
  2300. _StopInput();
  2301. }
  2302. }
  2303. // Regardless of focus / stage visibility, we need to turn on the engine if necessary here since there
  2304. // may be *no* speech tip with focus to do this. This means we may have multiple tips turning the reco
  2305. // state on / off simultaneously.
  2306. if(m_cpRecoEngine)
  2307. {
  2308. m_fInputState = fOn;
  2309. if ( _GetInputOnOffState( ) != fOn )
  2310. {
  2311. TraceMsg(TF_GENERAL, "Call SetRecoState, %s", fOn ? "SPRST_ACTIVE" : "SPRST_INACTIVE");
  2312. m_cpRecoEngine->SetRecoState(fOn ? SPRST_ACTIVE : SPRST_INACTIVE);
  2313. }
  2314. // DO NOT ADD DEBUGGING CODE HERE TO PRINT OUT STATE - CAN BLOCK CICERO RESULTING
  2315. // IN DIFFERENT BEHAVIOR TO THE RELEASE VERSION.
  2316. }
  2317. m_fIn_SetInputOnOffState = FALSE;
  2318. }
  2319. BOOL CSpTask::_GetInputOnOffState(void)
  2320. {
  2321. BOOL fRet = FALSE;
  2322. if(m_cpRecoEngine)
  2323. {
  2324. SPRECOSTATE srs;
  2325. m_cpRecoEngine->GetRecoState(&srs);
  2326. if (srs == SPRST_ACTIVE)
  2327. {
  2328. fRet = TRUE; // on
  2329. }
  2330. else if (srs == SPRST_INACTIVE)
  2331. {
  2332. fRet = FALSE; // off
  2333. }
  2334. // anything else is 'off'
  2335. }
  2336. return fRet;
  2337. }
  2338. HRESULT CSpTask::_StopInput(void)
  2339. {
  2340. HRESULT hr = S_OK;
  2341. TraceMsg(TF_SAPI_PERF, "_StopInput is called");
  2342. if (!m_bInSound && m_pime->GetDICTATIONSTAT_DictOnOff())
  2343. {
  2344. TraceMsg(TF_SAPI_PERF, "m_bInSound is FALSE, GetDICTATIONSTAT_DictOnOff returns TRUE");
  2345. return S_OK;
  2346. }
  2347. m_pime->EraseFeedbackUI();
  2348. _ShowDictatingToBalloon(FALSE);
  2349. return S_OK;
  2350. }
  2351. // _ClearQueuedRecoEvent(void)
  2352. //
  2353. // synopsis: get rid of remaining events from reco context's
  2354. // event queue. This is only called from _StopInput()
  2355. // when TerminateComposition() is called, or Mic is
  2356. // turned off
  2357. //
  2358. //
  2359. void CSpTask::_ClearQueuedRecoEvent(void)
  2360. {
  2361. if (m_cpRecoCtxt)
  2362. {
  2363. SPEVENTSOURCEINFO esi;
  2364. if (S_OK == m_cpRecoCtxt->GetInfo(&esi))
  2365. {
  2366. ULONG ulcount = esi.ulCount;
  2367. CSpEvent event;
  2368. while(ulcount > 0)
  2369. {
  2370. if (S_OK == event.GetFrom(m_cpRecoCtxt))
  2371. {
  2372. event.Clear();
  2373. }
  2374. ulcount--;
  2375. }
  2376. }
  2377. }
  2378. }
  2379. // CSpTask::GetResltObjectFromStream()
  2380. //
  2381. // synopsis - a wrapper function that takes a stream ptr to a SAPI result blob
  2382. // and gets alternates out of the object
  2383. //
  2384. HRESULT CSpTask::GetResultObjectFromStream(IStream *pStream, ISpRecoResult **ppResult)
  2385. {
  2386. LARGE_INTEGER li0 = {0, 0};
  2387. HRESULT hr = E_INVALIDARG;
  2388. SPSERIALIZEDRESULT *pPhraseBlob = 0;
  2389. if (pStream)
  2390. {
  2391. hr = pStream->Seek(li0, STREAM_SEEK_SET, NULL);
  2392. STATSTG stg;
  2393. if (hr == S_OK)
  2394. {
  2395. hr = pStream->Stat(&stg, STATFLAG_NONAME);
  2396. }
  2397. if (SUCCEEDED(hr))
  2398. pPhraseBlob = (SPSERIALIZEDRESULT *)CoTaskMemAlloc(stg.cbSize.LowPart+sizeof(ULONG)*4);
  2399. if (pPhraseBlob)
  2400. hr = pStream->Read(pPhraseBlob, stg.cbSize.LowPart, NULL);
  2401. else
  2402. hr = E_OUTOFMEMORY;
  2403. if (SUCCEEDED(hr))
  2404. {
  2405. ISpRecoResult *pResult;
  2406. if (SUCCEEDED(m_cpRecoCtxt->DeserializeResult(pPhraseBlob, &pResult)) && pResult)
  2407. {
  2408. if (ppResult)
  2409. {
  2410. pResult->AddRef();
  2411. *ppResult = pResult;
  2412. }
  2413. pResult->Release();
  2414. }
  2415. }
  2416. }
  2417. return hr;
  2418. }
  2419. //
  2420. // GetAlternates
  2421. //
  2422. HRESULT CSpTask::GetAlternates(CRecoResultWrap *pResultWrap, ULONG ulStartElem, ULONG ulcElem, ISpPhraseAlt **ppAlt, ULONG *pcAlt, ISpRecoResult **ppRecoResult)
  2423. {
  2424. HRESULT hr = E_INVALIDARG;
  2425. if (m_fIn_GetAlternates)
  2426. return E_FAIL;
  2427. m_fIn_GetAlternates = TRUE;
  2428. Assert(pResultWrap);
  2429. Assert(ppAlt);
  2430. Assert(pcAlt);
  2431. hr = pResultWrap->GetResult(ppRecoResult);
  2432. if (S_OK == hr && *ppRecoResult)
  2433. {
  2434. hr = (*ppRecoResult)->GetAlternates(
  2435. ulStartElem,
  2436. ulcElem,
  2437. *pcAlt,
  2438. ppAlt, /* [out] ISpPhraseAlt **ppPhrases, */
  2439. pcAlt /* [out] ULONG *pcPhrasesReturned */
  2440. );
  2441. }
  2442. m_fIn_GetAlternates = FALSE;
  2443. return hr;
  2444. }
  2445. HRESULT CSpTask::_SpeakText(WCHAR *pwsz)
  2446. {
  2447. HRESULT hr = E_FAIL;
  2448. if (m_cpVoice)
  2449. hr = m_cpVoice->Speak( pwsz, /* SPF_DEFAULT */ SPF_ASYNC /* | SPF_PURGEBEFORESPEAK*/, NULL );
  2450. return hr;
  2451. }
  2452. HRESULT CSpTask::_SpeakAudio( ISpStreamFormat *pStream )
  2453. {
  2454. HRESULT hr = E_FAIL;
  2455. if ( !pStream )
  2456. return E_INVALIDARG;
  2457. if (m_cpVoice)
  2458. hr = m_cpVoice->SpeakStream(pStream, SPF_ASYNC, NULL);
  2459. return hr;
  2460. }
  2461. void CSapiIMX::RegisterWorkerClass(HINSTANCE hInstance)
  2462. {
  2463. WNDCLASSEX wndclass;
  2464. memset(&wndclass, 0, sizeof(wndclass));
  2465. wndclass.cbSize = sizeof(wndclass);
  2466. wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  2467. wndclass.hInstance = hInstance;
  2468. wndclass.lpfnWndProc = CSapiIMX::_WndProc;
  2469. wndclass.lpszClassName = c_szWorkerWndClass;
  2470. wndclass.cbWndExtra = 8;
  2471. RegisterClassEx(&wndclass);
  2472. }
  2473. //
  2474. // ParseSRElementByLocale
  2475. //
  2476. // Parse SR result elements in locale specific mannar
  2477. //
  2478. // dependency caution: this function has to be rewritten when SAPI5 changes
  2479. // SR element format, which is very likely
  2480. //
  2481. // 12/15/99 : As of SAPI1214, an element now includes display text,
  2482. // lexical form, pronunciation separately. this function
  2483. // takes the display text at szSrc.
  2484. // langid parameter is not used at this moment
  2485. //
  2486. HRESULT CSpTask::ParseSRElementByLocale(WCHAR *szDst, int cchDst, const WCHAR * szSrc, LANGID langid, BYTE bAttr)
  2487. {
  2488. if (!szDst || !szSrc || !cchDst)
  2489. {
  2490. return E_INVALIDARG;
  2491. }
  2492. // handle leading space.
  2493. if (bAttr & SPAF_CONSUME_LEADING_SPACES)
  2494. {
  2495. const WCHAR *psz = szSrc;
  2496. while(*psz && *psz == L' ')
  2497. {
  2498. psz++;
  2499. }
  2500. szSrc = psz;
  2501. }
  2502. wcsncpy(szDst, szSrc, cchDst - 2); // -2 for possible sp
  2503. // handle trailing space
  2504. if (bAttr & SPAF_ONE_TRAILING_SPACE)
  2505. {
  2506. StringCchCatW(szDst, cchDst, L" ");
  2507. }
  2508. else if (bAttr & SPAF_TWO_TRAILING_SPACES)
  2509. {
  2510. StringCchCatW(szDst,cchDst, L" ");
  2511. }
  2512. return S_OK;
  2513. }
  2514. //
  2515. // FeedDictContext
  2516. //
  2517. // synopsis: feed the text surrounding the current IP to SR engine
  2518. //
  2519. void CSpTask::FeedDictContext(CDictContext *pdc)
  2520. {
  2521. Assert(pdc);
  2522. if (!m_pime->_GetWorkerWnd())
  2523. {
  2524. delete pdc;
  2525. return ;
  2526. }
  2527. // wait until the current feeding is done
  2528. // it's not efficient to feed IP everytime user
  2529. // click around
  2530. if (m_pdc)
  2531. {
  2532. delete pdc;
  2533. return;
  2534. }
  2535. if (!m_cpDictGrammar)
  2536. {
  2537. delete pdc;
  2538. return;
  2539. }
  2540. // remove unprocessed messages from the queue
  2541. // FutureConsider: this could be moved to wndproc so that
  2542. // we can remove this private msg at the
  2543. // moment we process it. It depends on
  2544. // profiling we'll do.
  2545. //
  2546. MSG msg;
  2547. while(PeekMessage(&msg, m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, WM_PRIV_FEEDCONTEXT, TRUE))
  2548. ;
  2549. // queue up the context
  2550. m_pdc = pdc;
  2551. PostMessage(m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, 0, (LPARAM)TRUE);
  2552. }
  2553. void CSpTask::CleanupDictContext(void)
  2554. {
  2555. if (m_pdc)
  2556. delete m_pdc;
  2557. m_pdc = NULL;
  2558. }
  2559. // _UpdateBalloon( )
  2560. void CSpTask::_UpdateBalloon(ULONG uidBalloon, ULONG uidBalloonTooltip)
  2561. {
  2562. WCHAR wszBalloonText[MAX_PATH] = {0};
  2563. WCHAR wszBalloonTooltip[MAX_PATH] = {0};
  2564. #ifndef RECOSLEEP
  2565. if (!m_pime->GetSpeechUIServer())
  2566. #else
  2567. if (!m_pime->GetSpeechUIServer() || IsInSleep( ))
  2568. #endif
  2569. return;
  2570. CicLoadStringWrapW(g_hInst, uidBalloon, wszBalloonText, ARRAYSIZE(wszBalloonText));
  2571. CicLoadStringWrapW(g_hInst, uidBalloonTooltip, wszBalloonTooltip, ARRAYSIZE(wszBalloonTooltip));
  2572. if (wszBalloonText[0] && wszBalloonTooltip[0])
  2573. {
  2574. m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO,
  2575. wszBalloonText, -1,
  2576. wszBalloonTooltip, -1 );
  2577. }
  2578. return;
  2579. }
  2580. //
  2581. // ShowDictatingToBalloon
  2582. //
  2583. //
  2584. void CSpTask::_ShowDictatingToBalloon(BOOL fShow)
  2585. {
  2586. #ifndef RECOSLEEP
  2587. if (!m_pime->GetSpeechUIServer())
  2588. #else
  2589. if (!m_pime->GetSpeechUIServer() || IsInSleep( ))
  2590. #endif
  2591. return;
  2592. static WCHAR s_szDictating[MAX_PATH] = {0};
  2593. static WCHAR s_szDictatingTooltip[MAX_PATH] = {0};
  2594. if (!s_szDictating[0])
  2595. {
  2596. CicLoadStringWrapW(g_hInst, IDS_DICTATING,
  2597. s_szDictating,
  2598. ARRAYSIZE(s_szDictating));
  2599. }
  2600. if (!s_szDictatingTooltip[0])
  2601. {
  2602. CicLoadStringWrapW(g_hInst, IDS_DICTATING_TOOLTIP,
  2603. s_szDictatingTooltip,
  2604. ARRAYSIZE(s_szDictatingTooltip));
  2605. }
  2606. if (fShow && s_szDictating[0] && s_szDictatingTooltip[0])
  2607. {
  2608. m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, s_szDictating, -1, s_szDictatingTooltip, -1 );
  2609. }
  2610. else if (!fShow)
  2611. {
  2612. m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, L" ", -1, L" ", -1 );
  2613. }
  2614. }
  2615. //
  2616. // _HandleInterference
  2617. //
  2618. // synopsis: bubble up reco errors to the balloon UI
  2619. //
  2620. //
  2621. void CSpTask::_HandleInterference(ULONG lParam)
  2622. {
  2623. if (!m_pime->GetSpeechUIServer())
  2624. return;
  2625. WCHAR sz[MAX_PATH];
  2626. WCHAR szTooltip[MAX_PATH];
  2627. if (S_OK ==
  2628. _GetLocSRErrorString((SPINTERFERENCE)lParam,
  2629. sz, ARRAYSIZE(sz),
  2630. szTooltip, ARRAYSIZE(szTooltip)))
  2631. {
  2632. m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, sz, -1, szTooltip, -1 );
  2633. }
  2634. }
  2635. HRESULT CSpTask::_GetLocSRErrorString
  2636. (
  2637. SPINTERFERENCE sif,
  2638. WCHAR *psz, ULONG cch,
  2639. WCHAR *pszTooltip, ULONG cchTooltip
  2640. )
  2641. {
  2642. HRESULT hr = E_FAIL;
  2643. static struct
  2644. {
  2645. ULONG uidRes;
  2646. WCHAR szErr[MAX_PATH];
  2647. ULONG uidResTooltip;
  2648. WCHAR szTooltip[MAX_PATH];
  2649. } rgIntStr[] =
  2650. {
  2651. {IDS_INT_NONE, {0}, IDS_INTTOOLTIP_NONE, {0}},
  2652. {IDS_INT_NOISE, {0}, IDS_INTTOOLTIP_NOISE, {0}},
  2653. {IDS_INT_NOSIGNAL, {0}, IDS_INTTOOLTIP_NOSIGNAL, {0}},
  2654. {IDS_INT_TOOLOUD, {0}, IDS_INTTOOLTIP_TOOLOUD, {0}},
  2655. {IDS_INT_TOOQUIET, {0}, IDS_INTTOOLTIP_TOOQUIET, {0}},
  2656. {IDS_INT_TOOFAST, {0}, IDS_INTTOOLTIP_TOOFAST, {0}},
  2657. {IDS_INT_TOOSLOW, {0}, IDS_INTTOOLTIP_TOOSLOW, {0}}
  2658. };
  2659. if ((ULONG)sif < ARRAYSIZE(rgIntStr)-1)
  2660. {
  2661. if (!rgIntStr[sif].szErr[0])
  2662. {
  2663. hr = CicLoadStringWrapW(g_hInst,
  2664. rgIntStr[sif].uidRes,
  2665. rgIntStr[sif].szErr,
  2666. ARRAYSIZE(rgIntStr[0].szErr)) > 0 ? S_OK : E_FAIL;
  2667. if (S_OK == hr)
  2668. {
  2669. hr = CicLoadStringWrapW(g_hInst,
  2670. rgIntStr[sif].uidResTooltip,
  2671. rgIntStr[sif].szTooltip,
  2672. ARRAYSIZE(rgIntStr[0].szTooltip)) > 0 ? S_OK : E_FAIL;
  2673. }
  2674. }
  2675. else
  2676. hr = S_OK; // the value is cached
  2677. }
  2678. if (S_OK == hr)
  2679. {
  2680. Assert(psz);
  2681. wcsncpy(psz, rgIntStr[sif].szErr, cch);
  2682. Assert(pszTooltip);
  2683. wcsncpy(pszTooltip, rgIntStr[sif].szTooltip, cchTooltip);
  2684. }
  2685. return hr;
  2686. }