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.

2896 lines
87 KiB

  1. //
  2. // sapilayr.cpp
  3. //
  4. // implementation of CSapiIMX class body
  5. //
  6. #include "private.h"
  7. #include "immxutil.h"
  8. #include "sapilayr.h"
  9. #include "globals.h"
  10. #include "propstor.h"
  11. #include "timsink.h"
  12. #include "kes.h"
  13. #include "nui.h"
  14. #include "dispattr.h"
  15. #include "lbarsink.h"
  16. #include "miscfunc.h"
  17. #include "nuibase.h"
  18. #include "xstring.h"
  19. #include "dictctxt.h"
  20. #include "mui.h"
  21. #include "cregkey.h"
  22. #include "oleacc.h"
  23. // {9597CB34-CF6A-11d3-8D69-00500486C135}
  24. static const GUID GUID_OfficeSpeechMode = {
  25. 0x9597cb34,
  26. 0xcf6a,
  27. 0x11d3,
  28. { 0x8d, 0x69, 0x0, 0x50, 0x4, 0x86, 0xc1, 0x35}
  29. };
  30. STDAPI CICPriv::QueryInterface(REFIID riid, void **ppvObj)
  31. {
  32. *ppvObj = NULL;
  33. if (IsEqualIID(riid, IID_IUnknown))
  34. {
  35. *ppvObj = SAFECAST(this, IUnknown *);
  36. }
  37. if (*ppvObj)
  38. {
  39. AddRef();
  40. return S_OK;
  41. }
  42. return E_NOINTERFACE;
  43. }
  44. STDAPI_(ULONG) CICPriv::AddRef()
  45. {
  46. return ++_cRef;
  47. }
  48. STDAPI_(ULONG) CICPriv::Release()
  49. {
  50. long cr;
  51. cr = --_cRef;
  52. Assert(cr >= 0);
  53. if (cr == 0)
  54. {
  55. delete this;
  56. }
  57. return cr;
  58. }
  59. CICPriv *GetInputContextPriv(TfClientId tid, ITfContext *pic)
  60. {
  61. CICPriv *picp;
  62. IUnknown *punk;
  63. GetCompartmentUnknown(pic, GUID_IC_PRIVATE, &punk);
  64. if (!punk)
  65. {
  66. // need to init priv data
  67. if (picp = new CICPriv(pic))
  68. {
  69. SetCompartmentUnknown(tid, pic, GUID_IC_PRIVATE, picp);
  70. }
  71. }
  72. else
  73. {
  74. picp = (CICPriv *)punk;
  75. }
  76. return picp;
  77. }
  78. CICPriv *EnsureInputContextPriv(CSapiIMX *pimx, ITfContext *pic)
  79. {
  80. CICPriv *picp = GetInputContextPriv(pimx->_GetId(), pic);
  81. return picp;
  82. }
  83. //+---------------------------------------------------------------------------
  84. //
  85. // ctor
  86. //
  87. //----------------------------------------------------------------------------
  88. CSapiIMX::CSapiIMX() : CFnConfigure(this), CLearnFromDoc(this), CAddDeleteWord(this), CSelectWord(this),
  89. CTextToSpeech(this), CCorrectionHandler(this)
  90. {
  91. m_pCSpTask = NULL;
  92. m_hwndWorker = NULL;
  93. _fDeactivated = TRUE;
  94. _fEditing = FALSE;
  95. _tid = TF_CLIENTID_NULL;
  96. _cRef = 1;
  97. //
  98. // Init DisplayAttribute Provider
  99. //
  100. // default
  101. COLORREF crBk = GetNewLookColor();
  102. COLORREF crText = GetTextColor();
  103. // add feedback UI colors
  104. TF_DISPLAYATTRIBUTE da;
  105. StringCchCopyW(szProviderName, ARRAYSIZE(szProviderName), L"SAPI Layer");
  106. SetAttributeColor(&da.crText, crText);
  107. SetAttributeColor(&da.crBk, crBk);
  108. da.lsStyle = TF_LS_NONE;
  109. da.fBoldLine = FALSE;
  110. ClearAttributeColor(&da.crLine);
  111. da.bAttr = TF_ATTR_INPUT;
  112. Add(GUID_ATTR_SAPI_GREENBAR, L"SAPI Feedback Bar", &da);
  113. // color for level 2 application (for CUAS)
  114. crBk = GetNewLookColor(DA_COLOR_UNAWARE);
  115. SetAttributeColor(&da.crText, crText);
  116. SetAttributeColor(&da.crBk, crBk);
  117. da.lsStyle = TF_LS_NONE;
  118. da.fBoldLine = FALSE;
  119. ClearAttributeColor(&da.crLine);
  120. da.bAttr = TF_ATTR_INPUT;
  121. Add(GUID_ATTR_SAPI_GREENBAR2, L"SAPI Feedback Bar for Unaware app", &da);
  122. SetAttributeColor(&da.crText, crText);
  123. SetAttributeColor(&da.crBk, RGB(255, 0, 0));
  124. da.lsStyle = TF_LS_NONE;
  125. da.fBoldLine = FALSE;
  126. ClearAttributeColor(&da.crLine);
  127. da.bAttr = TF_ATTR_INPUT;
  128. Add(GUID_ATTR_SAPI_REDBAR, L"SAPI Red bar", &da);
  129. // create another dap for simulate 'inverted text' for selection
  130. SetAttributeColor(&da.crBk, GetSysColor( COLOR_HIGHLIGHT ));
  131. SetAttributeColor(&da.crText, GetSysColor( COLOR_HIGHLIGHTTEXT ));
  132. da.lsStyle = TF_LS_NONE;
  133. da.fBoldLine = FALSE;
  134. ClearAttributeColor(&da.crLine);
  135. da.bAttr = TF_ATTR_TARGET_CONVERTED;
  136. Add(GUID_ATTR_SAPI_SELECTION, L"SPTIP selection ", &da);
  137. m_fSharedReco = TRUE;
  138. m_fShowBalloon = FALSE;
  139. m_pLanguageChangeNotifySink = NULL;
  140. m_pSpeechUIServer = NULL;
  141. m_szCplPath[0] = _T('\0');
  142. m_pCapCmdHandler = NULL;
  143. m_fIPIsUpdated = FALSE;
  144. m_dwNumCharTyped = 0;
  145. m_ulSimulatedKey = 0;
  146. m_pSpButtonControl = NULL;
  147. m_fModeKeyRegistered = FALSE;
  148. m_fStartingComposition = FALSE;
  149. m_fStageTip = FALSE;
  150. m_fStageVisible = FALSE;
  151. m_hwndStage = NULL;
  152. m_ulHypothesisLen = 0;
  153. m_ulHypothesisNum = 0;
  154. m_IsInHypoProcessing = FALSE;
  155. _pCandUIEx = NULL;
  156. m_pMouseSink = NULL;
  157. m_fMouseDown = FALSE;
  158. m_ichMouseSel = 0;
  159. m_uLastEdge = 0;
  160. m_lTimeLastClk = 0;
  161. #ifdef SUPPORT_INTERNAL_WIDGET
  162. m_fNoCorrectionUI = FALSE;
  163. #endif
  164. }
  165. //+---------------------------------------------------------------------------
  166. //
  167. // dtor
  168. //
  169. //----------------------------------------------------------------------------
  170. CSapiIMX::~CSapiIMX()
  171. {
  172. if (m_pSpeechUIServer)
  173. {
  174. m_pSpeechUIServer->SetIMX(NULL);
  175. m_pSpeechUIServer->Release();
  176. m_pSpeechUIServer = NULL;
  177. }
  178. ClearCandUI( );
  179. if (m_hwndWorker)
  180. {
  181. DestroyWindow(m_hwndWorker);
  182. }
  183. if ( m_pCapCmdHandler )
  184. delete m_pCapCmdHandler;
  185. }
  186. //+---------------------------------------------------------------------------
  187. //
  188. // PrivateAPI for profile stuff
  189. //
  190. //
  191. //----------------------------------------------------------------------------
  192. extern "C"
  193. HRESULT WINAPI TF_CreateLangProfileUtil(ITfFnLangProfileUtil **ppFnLangUtil)
  194. {
  195. return CSapiIMX::CreateInstance(NULL, IID_ITfFnLangProfileUtil, (void **)ppFnLangUtil);
  196. }
  197. //+---------------------------------------------------------------------------
  198. //
  199. // OnSetThreadFocus
  200. //
  201. //----------------------------------------------------------------------------
  202. STDAPI CSapiIMX::OnSetThreadFocus()
  203. {
  204. TraceMsg(TF_SAPI_PERF, "OnSetThreadFocus is called");
  205. BOOL fOn = GetOnOff( );
  206. // When Microphone is OFF, don't set any speech status to the local compartment.
  207. // this will cause Office App initialize their SAPI objects if the mode is C&C
  208. // even if Microphone OFF.
  209. // Later when the Microphone is ON, this mode data will be updated correctly
  210. // inside the MICROPHONE_OPENCLOSE handling.
  211. //
  212. if ( fOn )
  213. {
  214. // TABLETPC
  215. // We switch states to match the global state whenever we get focus.
  216. // This may or may not trigger changes depending on the the stage visibility and dictation state.
  217. DWORD dwLocal, dwGlobal;
  218. GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dwLocal, FALSE);
  219. GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_GLOBALSTATE, &dwGlobal, TRUE);
  220. dwGlobal = dwGlobal & (TF_DICTATION_ON | TF_COMMANDING_ON);
  221. if ( (dwLocal & (TF_DICTATION_ON | TF_COMMANDING_ON)) != dwGlobal)
  222. {
  223. dwLocal = (dwLocal & ~(TF_DICTATION_ON | TF_COMMANDING_ON)) + dwGlobal;
  224. SetCompartmentDWORD(_tid, _tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, dwLocal, FALSE);
  225. // Now we are guaranteed the local dictation state matches the global one.
  226. }
  227. }
  228. #ifdef SYSTEM_GLOBAL_MIC_STATUS
  229. //
  230. // The microphone UI status compartment is updated whenever the reco state
  231. // changes. What we need to do here is to reset the dictation status
  232. // and others things that we skip doing when the thread doesn't have
  233. // a focus
  234. //
  235. //
  236. MIC_STATUS ms = MICSTAT_NA;
  237. if (m_pCSpTask)
  238. {
  239. ms = m_pCSpTask->_GetInputOnOffState() ? MICSTAT_ON : MICSTAT_OFF;
  240. }
  241. _HandleOpenCloseEvent(ms);
  242. #else
  243. _HandleOpenCloseEvent();
  244. #endif
  245. #ifdef SUPPORT_INTERNAL_WIDGET
  246. // create widget instance here
  247. if (!m_fNoCorrectionUI && !m_cpCorrectionUI)
  248. {
  249. if (S_OK == m_cpCorrectionUI.CoCreateInstance(CLSID_CorrectionIMX))
  250. {
  251. // the real widget is installed
  252. m_cpCorrectionUI.Release();
  253. m_fNoCorrectionUI = TRUE;
  254. }
  255. else if (SUCCEEDED(CCorrectionIMX::CreateInstance(NULL, IID_ITfTextInputProcessor, (void **)&m_cpCorrectionUI)))
  256. {
  257. m_cpCorrectionUI->Activate(_tim, _tid);
  258. }
  259. }
  260. #endif
  261. return S_OK;
  262. }
  263. //+---------------------------------------------------------------------------
  264. //
  265. // OnKillThreadFocus
  266. //
  267. //----------------------------------------------------------------------------
  268. STDAPI CSapiIMX::OnKillThreadFocus()
  269. {
  270. // When the Application gets focus again, it will rely on the
  271. // current status from compartment, and then decides which RecoContext
  272. // needs to be activated.
  273. //
  274. TraceMsg(TF_SAPI_PERF, "CSapiIMX::OnKillThreadFocus is called");
  275. // TABLETPC
  276. if (m_pCSpTask && S_OK != IsActiveThread())
  277. {
  278. m_pCSpTask->_SetDictRecoCtxtState(FALSE);
  279. m_pCSpTask->_SetCmdRecoCtxtState(FALSE);
  280. }
  281. // close candidate UI forcefully when focus shifts
  282. CloseCandUI( );
  283. return S_OK;
  284. }
  285. BOOL CSapiIMX::InitializeSpeechButtons()
  286. {
  287. BOOL fSREnabled = _DictationEnabled();
  288. SetDICTATIONSTAT_DictEnabled(fSREnabled);
  289. // We need to see if the app has commanding if it is, then it
  290. // needs the mic even when dictation is disabled
  291. //
  292. if (m_pSpeechUIServer)
  293. {
  294. BOOL fShow = (fSREnabled || IsDICTATIONSTAT_CommandingEnable());
  295. m_pSpeechUIServer->ShowUI(fShow);
  296. }
  297. return fSREnabled;
  298. }
  299. //+---------------------------------------------------------------------------
  300. //
  301. // Activate
  302. //
  303. //----------------------------------------------------------------------------
  304. STDAPI CSapiIMX::Activate(ITfThreadMgr *ptim, TfClientId tid)
  305. {
  306. ITfLangBarItemMgr *plbim = NULL;
  307. ITfKeystrokeMgr_P *pksm = NULL;
  308. ITfSourceSingle *sourceSingle;
  309. ITfSource *source;
  310. ITfContext *pic = NULL;
  311. BOOL fSREnabledForLanguage = FALSE;
  312. TfClientId tidLast = _tid;
  313. _tid = tid;
  314. // Load spgrmr.dll module for speech grammar.
  315. LoadSpgrmrModule();
  316. // register notify UI stuff
  317. HRESULT hr = GetService(ptim, IID_ITfLangBarItemMgr, (IUnknown **)&plbim);
  318. if (SUCCEEDED(hr))
  319. {
  320. plbim->GetItem(GUID_TFCAT_TIP_SPEECH, &m_cpMicButton);
  321. SafeRelease(plbim);
  322. }
  323. // regular stuff for activate
  324. Assert(_tim == NULL);
  325. _tim = ptim;
  326. _tim->AddRef();
  327. if (_tim->QueryInterface(IID_ITfSource, (void **)&source) == S_OK)
  328. {
  329. source->AdviseSink(IID_ITfThreadFocusSink, (ITfThreadFocusSink *)this, &_dwThreadFocusCookie);
  330. source->AdviseSink(IID_ITfKeyTraceEventSink, (ITfKeyTraceEventSink *)this, &_dwKeyTraceCookie);
  331. source->Release();
  332. }
  333. // force data options to get set
  334. SetAudioOnOff(TRUE);
  335. // Register compartment sink for TIP status
  336. if (!(m_pCes = new CCompartmentEventSink(_CompEventSinkCallback, this)))
  337. {
  338. hr = E_OUTOFMEMORY;
  339. goto Exit;
  340. }
  341. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SAPI_AUDIO, FALSE);
  342. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_OPENCLOSE, TRUE);
  343. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, FALSE);
  344. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_LEARNDOC, FALSE);
  345. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_CFGMENU, FALSE);
  346. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE, TRUE);
  347. #ifdef TF_DISABLE_SPEECH
  348. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_DISABLED, FALSE);
  349. #endif
  350. //TABLETPC
  351. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_STAGE, FALSE);
  352. m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, TRUE);
  353. // Get initial stage visibility. Note - keep after above _Advise for stage change event.
  354. DWORD dw = 0;
  355. GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, &dw, TRUE);
  356. m_fStageVisible = dw ? TRUE : FALSE;
  357. // ENDTABLETPC
  358. // profile activation sink
  359. if (!m_pActiveLanguageProfileNotifySink)
  360. {
  361. if (!(m_pActiveLanguageProfileNotifySink =
  362. new CActiveLanguageProfileNotifySink(_ActiveTipNotifySinkCallback, this)))
  363. {
  364. hr = E_OUTOFMEMORY;
  365. goto Exit;
  366. }
  367. m_pActiveLanguageProfileNotifySink->_Advise(_tim);
  368. }
  369. if (!m_pSpeechUIServer &&
  370. FAILED(CSpeechUIServer::CreateInstance(NULL,
  371. IID_PRIV_CSPEECHUISERVER,
  372. (void **)&m_pSpeechUIServer)))
  373. {
  374. hr = E_OUTOFMEMORY;
  375. goto Exit;
  376. }
  377. SetCompartmentDWORD(_tid,_tim,GUID_COMPARTMENT_SPEECH_LEARNDOC,_LMASupportEnabled(),FALSE);
  378. if (m_pSpeechUIServer)
  379. {
  380. m_pSpeechUIServer->SetIMX(this);
  381. m_pSpeechUIServer->Initialize();
  382. m_pSpeechUIServer->ShowUI(TRUE);
  383. }
  384. fSREnabledForLanguage = InitializeSpeechButtons();
  385. SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
  386. // language change notification sink
  387. // this call better be after calling InitializeSpeechButtons because we
  388. // want to skip calling _EnsureProfiles to get ITfLanguageProfileNotifySink
  389. //
  390. if (!m_pLanguageChangeNotifySink)
  391. {
  392. if (!(m_pLanguageChangeNotifySink =
  393. new CLanguageProfileNotifySink(_LangChangeNotifySinkCallback, this)))
  394. {
  395. hr = E_OUTOFMEMORY;
  396. goto Exit;
  397. }
  398. m_pLanguageChangeNotifySink->_Advise(m_cpProfileMgr);
  399. }
  400. // now we inherit what is previously set as a mic status
  401. //
  402. #ifdef SYSTEM_GLOBAL_MIC_STATUS
  403. if (m_pCSpTask)
  404. {
  405. SetOnOff(m_pCSpTask->_GetInputOnOffState());
  406. }
  407. #else
  408. // see if microphone is 'ON' and if so, check if we're indeed running
  409. // we check tidLast because it is normal we get activated again with
  410. // same client id, and it means we've kept our life across sessions
  411. // we don't want to reject global mic status in this case, otherwise
  412. // we'll see bugs like cicero#3386
  413. //
  414. if (GetOnOff() && tidLast != tid)
  415. {
  416. // this code has to stay before the first call to _EnsureWorkerWnd()
  417. HWND hwnd = FindWindow(c_szWorkerWndClass, NULL);
  418. if (!IsWindow(hwnd))
  419. {
  420. // no one is running us but we somehow persisted the state
  421. // let's just kill the 'on' state here
  422. // SetOnOff(FALSE);
  423. }
  424. }
  425. #endif
  426. // show / hide balloon following global compartment
  427. m_fShowBalloon = GetBalloonStatus();
  428. // thread event sink init
  429. if ((m_timEventSink = new CThreadMgrEventSink(_DIMCallback, _ICCallback, this)) == NULL)
  430. {
  431. hr = E_OUTOFMEMORY;
  432. goto Exit;
  433. }
  434. else
  435. {
  436. m_timEventSink->_Advise(_tim);
  437. m_timEventSink->_InitDIMs(TRUE);
  438. }
  439. if (SUCCEEDED(GetService(_tim, IID_ITfKeystrokeMgr_P, (IUnknown **)&pksm)))
  440. {
  441. if (_pkes = new CSptipKeyEventSink(_KeyEventCallback, _PreKeyEventCallback, this))
  442. {
  443. pksm->AdviseKeyEventSink(_tid, _pkes, FALSE);
  444. _pkes->_Register(_tim, _tid, g_prekeyList);
  445. // register mode button hotkeys if they are enabled.
  446. HandleModeKeySettingChange(TRUE);
  447. }
  448. pksm->Release();
  449. }
  450. // func provider registeration
  451. IUnknown *punk;
  452. if (SUCCEEDED(QueryInterface(IID_IUnknown, (void **)&punk)))
  453. {
  454. if (SUCCEEDED(_tim->QueryInterface(IID_ITfSourceSingle, (void **)&sourceSingle)))
  455. {
  456. sourceSingle->AdviseSingleSink(_tid, IID_ITfFunctionProvider, punk);
  457. sourceSingle->Release();
  458. }
  459. punk->Release();
  460. }
  461. Assert(_fDeactivated);
  462. _fDeactivated = FALSE;
  463. // TABLETPC
  464. if (S_OK == IsActiveThread())
  465. {
  466. // init any UI
  467. OnSetThreadFocus();
  468. }
  469. hr = S_OK;
  470. Exit:
  471. return hr;
  472. }
  473. //+---------------------------------------------------------------------------
  474. //
  475. // Deactivate
  476. //
  477. //----------------------------------------------------------------------------
  478. STDAPI CSapiIMX::Deactivate()
  479. {
  480. ITfKeystrokeMgr *pksm = NULL;
  481. ITfSourceSingle *sourceSingle;
  482. ITfSource *source;
  483. // this ensures no context feed activity is taken place
  484. SetDICTATIONSTAT_DictOnOff(FALSE);
  485. // finalize any pending compositions, this may be async
  486. CleanupAllContexts(_tim, _tid, this);
  487. // Free the system reconvertion function if it is set.
  488. _ReleaseSystemReconvFunc( );
  489. // delete SpButtonControl object
  490. if ( m_pSpButtonControl )
  491. {
  492. delete m_pSpButtonControl;
  493. m_pSpButtonControl = NULL;
  494. }
  495. // TABLETPC
  496. if (S_OK != IsActiveThread())
  497. {
  498. // shutdown any UI
  499. OnKillThreadFocus();
  500. }
  501. if (_tim->QueryInterface(IID_ITfSource, (void **)&source) == S_OK)
  502. {
  503. source->UnadviseSink(_dwThreadFocusCookie);
  504. source->UnadviseSink(_dwKeyTraceCookie);
  505. source->Release();
  506. }
  507. // unregister notify UI stuff
  508. if (m_pSpeechUIServer && !IsDICTATIONSTAT_CommandingEnable())
  509. {
  510. m_pSpeechUIServer->SetIMX(NULL);
  511. m_pSpeechUIServer->Release();
  512. m_pSpeechUIServer = NULL;
  513. }
  514. #ifdef SUPPORT_INTERNAL_WIDGET
  515. // deactivate the widget correction
  516. if (m_cpCorrectionUI)
  517. {
  518. m_cpCorrectionUI->Deactivate();
  519. m_cpCorrectionUI.Release();
  520. }
  521. #endif
  522. // regular stuff for activate
  523. ClearCandUI( );
  524. if (SUCCEEDED(_tim->QueryInterface(IID_ITfSourceSingle, (void **)&sourceSingle)))
  525. {
  526. sourceSingle->UnadviseSingleSink(_tid, IID_ITfFunctionProvider);
  527. sourceSingle->Release();
  528. }
  529. // thread event sink deinit
  530. if (m_timEventSink)
  531. {
  532. m_timEventSink->_InitDIMs(FALSE);
  533. m_timEventSink->_Unadvise();
  534. SafeReleaseClear(m_timEventSink);
  535. }
  536. if (_pkes != NULL)
  537. {
  538. _pkes->_Unregister(_tim, _tid, g_prekeyList);
  539. if ( m_fModeKeyRegistered )
  540. {
  541. _pkes->_Unregister(_tim, _tid, (const KESPRESERVEDKEY *)g_prekeyList_Mode);
  542. m_fModeKeyRegistered = FALSE;
  543. }
  544. SafeReleaseClear(_pkes);
  545. }
  546. if (SUCCEEDED(GetService(_tim, IID_ITfKeystrokeMgr, (IUnknown **)&pksm)))
  547. {
  548. pksm->UnadviseKeyEventSink(_tid);
  549. pksm->Release();
  550. }
  551. if (m_pCes)
  552. {
  553. m_pCes->_Unadvise();
  554. SafeReleaseClear(m_pCes);
  555. }
  556. if (m_pLBarItemSink)
  557. {
  558. m_pLBarItemSink->_Unadvise();
  559. SafeReleaseClear(m_pLBarItemSink);
  560. }
  561. if (m_pMouseSink)
  562. {
  563. m_pMouseSink->_Unadvise();
  564. SafeReleaseClear(m_pMouseSink);
  565. }
  566. // clean up active notify sink
  567. if (m_pActiveLanguageProfileNotifySink)
  568. {
  569. m_pActiveLanguageProfileNotifySink->_Unadvise();
  570. SafeReleaseClear(m_pActiveLanguageProfileNotifySink);
  571. }
  572. if (m_pLanguageChangeNotifySink)
  573. {
  574. m_pLanguageChangeNotifySink->_Unadvise();
  575. SafeReleaseClear(m_pLanguageChangeNotifySink);
  576. }
  577. if (m_hwndWorker)
  578. {
  579. DestroyWindow(m_hwndWorker);
  580. m_hwndWorker = NULL;
  581. }
  582. DeinitializeSAPI();
  583. SafeReleaseClear(_tim);
  584. TFUninitLib_Thread(&_libTLS);
  585. Assert(!_fDeactivated);
  586. _fDeactivated = TRUE;
  587. return S_OK;
  588. }
  589. HRESULT CSapiIMX::InitializeSAPI(BOOL fLangOverride)
  590. {
  591. HRESULT hr = S_OK;
  592. TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is called");
  593. if (m_pCSpTask)
  594. {
  595. if (!m_pCSpTask->_IsCallbackInitialized())
  596. {
  597. hr = m_pCSpTask->InitializeCallback();
  598. }
  599. TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is initialized, hr=%x\n", hr);
  600. return hr;
  601. }
  602. HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
  603. // create CSpTask instance
  604. m_pCSpTask = new CSpTask(this);
  605. if (m_pCSpTask)
  606. {
  607. // LANGID m_langid;
  608. // check to see if profile lang matches with SR lang
  609. m_fDictationEnabled = _DictationEnabled(&m_langid);
  610. hr = m_pCSpTask->InitializeSAPIObjects(m_langid);
  611. if (S_OK == hr && !_fDeactivated &&
  612. (m_fDictationEnabled || IsDICTATIONSTAT_CommandingEnable()))
  613. {
  614. // set callback
  615. hr = m_pCSpTask->InitializeCallback();
  616. }
  617. if (S_OK == hr)
  618. {
  619. hr = m_pCSpTask->_LoadGrammars();
  620. }
  621. if (S_OK == hr)
  622. {
  623. // toolbar command
  624. m_pCSpTask->_InitToolbarCommand(fLangOverride);
  625. }
  626. }
  627. if (hCur)
  628. SetCursor(hCur);
  629. TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is done!!!!! hr=%x\n", hr);
  630. return hr;
  631. }
  632. HRESULT CSapiIMX::DeinitializeSAPI()
  633. {
  634. TraceMsg(TF_SAPI_PERF, "DeinitializeSAPI is called");
  635. if (m_pCSpTask)
  636. {
  637. // toolbar command
  638. m_pCSpTask->_UnInitToolbarCommand();
  639. // set dication status
  640. SetDICTATIONSTAT_DictOnOff(FALSE);
  641. // - deinitialize SAPI
  642. m_pCSpTask->_ReleaseSAPI();
  643. delete m_pCSpTask;
  644. m_pCSpTask = NULL;
  645. }
  646. return S_OK;
  647. }
  648. HRESULT CSapiIMX::_ActiveTipNotifySinkCallback(REFCLSID clsid, REFGUID guidProfile, BOOL fActivated, void *pv)
  649. {
  650. if (IsEqualGUID(clsid, CLSID_SapiLayr))
  651. {
  652. CSapiIMX *pimx = (CSapiIMX *)pv;
  653. if (fActivated)
  654. {
  655. BOOL fSREnabledForLanguage = pimx->InitializeSpeechButtons();
  656. pimx->SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
  657. }
  658. else
  659. {
  660. // finalize any pending compositions, this may be async
  661. CleanupAllContexts(pimx->_tim, pimx->_tid, pimx);
  662. // when deactivating, we have to deinitialize SAPI so that
  663. // we can re-initialize SAPI after getting a new assembly
  664. pimx->DeinitializeSAPI();
  665. }
  666. }
  667. return S_OK;
  668. }
  669. HRESULT CSapiIMX::_LangChangeNotifySinkCallback(BOOL fChanged, LANGID langid, BOOL *pfAccept, void *pv)
  670. {
  671. CSapiIMX *pimx = (CSapiIMX *)pv;
  672. if (fChanged)
  673. {
  674. pimx->m_fDictationEnabled = pimx->InitializeSpeechButtons();
  675. pimx->SetDICTATIONSTAT_DictEnabled(pimx->m_fDictationEnabled);
  676. if (!pimx->m_fDictationEnabled)
  677. {
  678. // finalize any pending compositions, this may be async
  679. CleanupAllContexts(pimx->_tim, pimx->_tid, pimx);
  680. if (!pimx->IsDICTATIONSTAT_CommandingEnable())
  681. pimx->DeinitializeSAPI();
  682. }
  683. /* With the Global Mode state supporting, we don't want this message for languag switch handling.
  684. else
  685. {
  686. if (pimx->_GetWorkerWnd())
  687. {
  688. TraceMsg(TF_SAPI_PERF, "Send WM_PRIV_ONSETTHREADFOCUS message");
  689. PostMessage(pimx->_GetWorkerWnd(), WM_PRIV_ONSETTHREADFOCUS, 0, 0);
  690. }
  691. }
  692. */
  693. }
  694. return S_OK;
  695. }
  696. //
  697. //
  698. // ITfCreatePropertyStore implementation
  699. //
  700. //
  701. STDMETHODIMP
  702. CSapiIMX::CreatePropertyStore(
  703. REFGUID guidProp,
  704. ITfRange *pRange,
  705. ULONG cb,
  706. IStream *pStream,
  707. ITfPropertyStore **ppStore
  708. )
  709. {
  710. HRESULT hr = E_FAIL;
  711. //
  712. //
  713. //
  714. if (IsEqualGUID(guidProp, GUID_PROP_SAPIRESULTOBJECT))
  715. {
  716. CPropStoreRecoResultObject *pPropStore;
  717. CComPtr<ISpRecoContext> cpRecoCtxt;
  718. // ensure SAPI is initialized
  719. InitializeSAPI(TRUE);
  720. hr = m_pCSpTask->GetSAPIInterface(IID_ISpRecoContext, (void **)&cpRecoCtxt);
  721. pPropStore = new CPropStoreRecoResultObject(this, pRange);
  722. if (pPropStore)
  723. {
  724. hr = pPropStore->_InitFromIStream(pStream, cb, cpRecoCtxt);
  725. if (SUCCEEDED(hr))
  726. hr = pPropStore->QueryInterface(IID_ITfPropertyStore, (void **)ppStore);
  727. pPropStore->Release();
  728. }
  729. }
  730. return hr;
  731. }
  732. STDAPI CSapiIMX::IsStoreSerializable(REFGUID guidProp, ITfRange *pRange, ITfPropertyStore *pPropStore, BOOL *pfSerializable)
  733. {
  734. *pfSerializable = FALSE;
  735. if (IsEqualGUID(guidProp, GUID_PROP_SAPIRESULTOBJECT))
  736. {
  737. *pfSerializable = TRUE;
  738. }
  739. return S_OK;
  740. }
  741. STDMETHODIMP CSapiIMX::GetType(GUID *pguid)
  742. {
  743. HRESULT hr = E_INVALIDARG;
  744. if (pguid)
  745. {
  746. *pguid = CLSID_SapiLayr;
  747. hr = S_OK;
  748. }
  749. return hr;
  750. }
  751. STDMETHODIMP CSapiIMX::GetDescription(BSTR *pbstrDesc)
  752. {
  753. const WCHAR c_wszNameSapiLayer[] = L"Cicero Sapi function Layer";
  754. HRESULT hr = S_OK;
  755. BSTR pbstr;
  756. if (!(pbstr = SysAllocString(c_wszNameSapiLayer)))
  757. {
  758. hr = E_OUTOFMEMORY;
  759. }
  760. return hr;
  761. }
  762. STDMETHODIMP CSapiIMX::GetFunction(REFGUID rguid, REFIID riid, IUnknown **ppunk)
  763. {
  764. if (!ppunk)
  765. return E_INVALIDARG;
  766. *ppunk = NULL;
  767. HRESULT hr = E_NOTIMPL;
  768. if (!IsEqualGUID(rguid, GUID_NULL))
  769. return hr;
  770. if (IsEqualIID(riid, IID_ITfFnGetSAPIObject))
  771. {
  772. *ppunk = new CGetSAPIObject(this);
  773. }
  774. else
  775. {
  776. if (IsEqualGUID(riid, IID_ITfFnPlayBack))
  777. {
  778. *ppunk = new CSapiPlayBack(this);
  779. }
  780. else if (IsEqualGUID(riid, IID_ITfFnReconversion))
  781. {
  782. *ppunk = new CFnReconversion(this);
  783. }
  784. else if (IsEqualIID(riid, IID_ITfFnAbort))
  785. {
  786. *ppunk = new CFnAbort(this);
  787. }
  788. else if (IsEqualIID(riid, IID_ITfFnBalloon))
  789. {
  790. *ppunk = new CFnBalloon(this);
  791. }
  792. else if (IsEqualIID(riid, IID_ITfFnPropertyUIStatus))
  793. {
  794. *ppunk = new CFnPropertyUIStatus(this);
  795. }
  796. else
  797. {
  798. // This class decides if it's necessary to initialize
  799. // SAPI to retrieve the requested interface
  800. //
  801. CComPtr<CGetSAPIObject> cpGetSapi;
  802. cpGetSapi.Attach(new CGetSAPIObject(this));
  803. //
  804. //
  805. //
  806. if (cpGetSapi)
  807. {
  808. TfSapiObject tfSapiObj;
  809. // this returns S_FALSE if the iid does not match
  810. hr = cpGetSapi->IsSupported(riid, &tfSapiObj);
  811. if (S_OK == hr)
  812. {
  813. // *ppunk is initialized w/ NULL in GetSAPIInterface()
  814. // ppunk should get addref'd
  815. hr = cpGetSapi->Get(tfSapiObj, ppunk);
  816. }
  817. else
  818. hr = E_NOTIMPL;
  819. if (hr == E_NOTIMPL)
  820. {
  821. // should we care?
  822. // this indicates that the caller has requested an interface
  823. // that we are not dealing with.
  824. // The caller could just detect this failure and do their own stuff.
  825. TraceMsg(TF_GENERAL, "Caller requested SAPI interface Cicero doesn't handle");
  826. }
  827. }
  828. }
  829. }
  830. if (*ppunk)
  831. {
  832. hr = S_OK;
  833. }
  834. return hr;
  835. }
  836. //+---------------------------------------------------------------------------
  837. //
  838. // _TextEventSinkCallback
  839. //
  840. //----------------------------------------------------------------------------
  841. HRESULT CSapiIMX::_TextEventSinkCallback(UINT uCode, void *pv, void *pvData)
  842. {
  843. TESENDEDIT *pee = (TESENDEDIT *)pvData;
  844. HRESULT hr = E_FAIL;
  845. Assert(uCode == ICF_TEXTDELTA); // the only one we asked for
  846. CSapiIMX *pimx = (CSapiIMX *)pv;
  847. if (pimx->_fEditing)
  848. return S_OK;
  849. pimx->HandleTextEvent(pee->pic, pee);
  850. hr = S_OK;
  851. return hr;
  852. }
  853. void CSapiIMX::HandleTextEvent(ITfContext *pic, TESENDEDIT *pee)
  854. {
  855. ITfRange *pRange = NULL;
  856. Assert(pic);
  857. Assert(pee);
  858. if (!m_pCSpTask)
  859. {
  860. return;
  861. }
  862. // Get the selection/IP if it's updated
  863. BOOL fUpdated = FALSE;
  864. if (S_OK == _GetSelectionAndStatus(pic, pee, &pRange, &fUpdated))
  865. {
  866. // Handle context feed
  867. if (fUpdated)
  868. {
  869. _FeedIPContextToSR(pee->ecReadOnly, pic, pRange);
  870. BOOL fEmpty = FALSE;
  871. BOOL fSelection;
  872. // Get current selection status.
  873. if ( pRange != NULL )
  874. pRange->IsEmpty(pee->ecReadOnly, &fEmpty);
  875. fSelection = !fEmpty;
  876. if ( fSelection != m_pCSpTask->_GetSelectionStatus( ) )
  877. {
  878. m_pCSpTask->_SetSelectionStatus(fSelection);
  879. m_pCSpTask->_SetSpellingGrammarStatus(GetDICTATIONSTAT_DictOnOff());
  880. }
  881. }
  882. // Handle mode bias property data
  883. SyncWithCurrentModeBias(pee->ecReadOnly, pRange, pic);
  884. SafeRelease(pRange);
  885. }
  886. // m_fIPIsUpdated just keeps if there is IP change by other tips or keyboard typing
  887. // since last dictation. It doesn't care about the ip change caused by speech tip itself,
  888. // those ip changes would include feedback ui inject and final text inject.
  889. //
  890. // Every time when a dictation or spelling phrase is recognized, this value should be
  891. // reset to FALSE.
  892. //
  893. // Here m_fIPIsUpdated should not just keep the last value returned by _GetSelectionAndStatus,
  894. // There is a scenario like, user move the ip to other place and then speak a command ( with some
  895. // some hypothesis feedback before the command is recognized).
  896. // In this case we should treat it as ip changed since last dictation, but the last value returned
  897. // from _GetSelectionAndStatus could be FALSE, because _GetSelectionAndStatus treats Sptip-injected
  898. // feedback tex as non-ipchanged.
  899. //
  900. // So only when m_fIPIsUpdated is FALSE, we get new value from _GetSelectionAndStatus,
  901. // otherwise, keep it till the next dictation.
  902. if ( m_fIPIsUpdated == FALSE )
  903. m_fIPIsUpdated = fUpdated;
  904. }
  905. void CSapiIMX::_FeedIPContextToSR(TfEditCookie ecReadOnly, ITfContext *pic, ITfRange *pRange)
  906. {
  907. if (GetOnOff() == TRUE && _ContextFeedEnabled())
  908. {
  909. CDictContext *pdc = new CDictContext(pic, pRange);
  910. if (pdc)
  911. {
  912. if (GetDICTATIONSTAT_DictOnOff() == TRUE &&
  913. S_OK == pdc->InitializeContext(ecReadOnly))
  914. {
  915. Assert(m_pCSpTask);
  916. m_pCSpTask->FeedDictContext(pdc);
  917. }
  918. else
  919. delete pdc;
  920. }
  921. }
  922. }
  923. void CSapiIMX::_SetCurrentIPtoSR(void)
  924. {
  925. _RequestEditSession(ESCB_FEEDCURRENTIP, TF_ES_READ);
  926. }
  927. HRESULT CSapiIMX::_InternalFeedCurrentIPtoSR(TfEditCookie ecReadOnly, ITfContext *pic)
  928. {
  929. CComPtr<ITfRange> cpRange;
  930. HRESULT hr = GetSelectionSimple(ecReadOnly, pic, &cpRange);
  931. if (S_OK == hr)
  932. {
  933. _FeedIPContextToSR(ecReadOnly, pic, cpRange);
  934. }
  935. return hr;
  936. }
  937. BOOL CSapiIMX::HandleKey(WCHAR ch)
  938. {
  939. m_ulSimulatedKey = 1;
  940. keybd_event((BYTE)ch, 0, 0, 0);
  941. keybd_event((BYTE)ch, 0, KEYEVENTF_KEYUP, 0);
  942. return TRUE;
  943. }
  944. const TCHAR c_szcplsKey[] = TEXT("software\\microsoft\\windows\\currentversion\\control panel\\cpls");
  945. void CSapiIMX::GetSapiCplPath(TCHAR *szCplPath, int cchSizePath)
  946. {
  947. if (!m_szCplPath[0])
  948. {
  949. CMyRegKey regkey;
  950. if (S_OK == regkey.Open(HKEY_LOCAL_MACHINE, c_szcplsKey, KEY_READ))
  951. {
  952. LONG lret = regkey.QueryValueCch(m_szCplPath, TEXT("SapiCpl"), ARRAYSIZE(m_szCplPath));
  953. if (lret != ERROR_SUCCESS)
  954. lret = regkey.QueryValueCch(m_szCplPath, TEXT("Speech"), ARRAYSIZE(m_szCplPath));
  955. if (lret != ERROR_SUCCESS)
  956. m_szCplPath[0] = _T('\0'); // maybe we get lucky next time
  957. }
  958. }
  959. StringCchCopy(szCplPath, cchSizePath, m_szCplPath);
  960. }
  961. HRESULT CSapiIMX::_GetSelectionAndStatus(ITfContext *pic, TESENDEDIT *pee, ITfRange **ppRange, BOOL *pfUpdated)
  962. {
  963. BOOL fWriteSession;
  964. HRESULT hr = pic->InWriteSession(_tid, &fWriteSession);
  965. Assert(pfUpdated);
  966. *pfUpdated = FALSE;
  967. if (S_OK == hr)
  968. {
  969. // we don't want to pick up changes done by ourselves
  970. if (!fWriteSession)
  971. {
  972. hr = pee->pEditRecord->GetSelectionStatus(pfUpdated);
  973. }
  974. else
  975. {
  976. // returns S_FALSE when in write session
  977. hr = S_FALSE;
  978. }
  979. }
  980. if (S_OK == hr )
  981. {
  982. Assert(ppRange);
  983. hr = GetSelectionSimple(pee->ecReadOnly, pic, ppRange);
  984. }
  985. return hr;
  986. }
  987. void CSapiIMX::SyncWithCurrentModeBias(TfEditCookie ec, ITfRange *pRange, ITfContext *pic)
  988. {
  989. ITfReadOnlyProperty *pProp = NULL;
  990. VARIANT var;
  991. QuickVariantInit(&var);
  992. if (pic->GetAppProperty(GUID_PROP_MODEBIAS, &pProp) != S_OK)
  993. {
  994. pProp = NULL;
  995. goto Exit;
  996. }
  997. pProp->GetValue(ec, pRange, &var);
  998. if (_gaModebias != (TfGuidAtom)var.lVal)
  999. {
  1000. GUID guid;
  1001. _gaModebias = (TfGuidAtom)var.lVal;
  1002. GetGUIDFromGUIDATOM(&_libTLS, _gaModebias, &guid);
  1003. BOOL fActive;
  1004. if (!IsEqualGUID(guid, GUID_MODEBIAS_NONE))
  1005. {
  1006. fActive = TRUE;
  1007. }
  1008. else
  1009. {
  1010. fActive = FALSE;
  1011. }
  1012. // mode bias has to be remembered
  1013. if (m_pCSpTask)
  1014. m_pCSpTask->_SetModeBias(fActive, guid);
  1015. }
  1016. Exit:
  1017. VariantClear(&var);
  1018. SafeRelease(pProp);
  1019. }
  1020. HRESULT CSapiIMX::_HandleTrainingWiz()
  1021. {
  1022. WCHAR sz[64];
  1023. sz[0] = '\0';
  1024. CicLoadStringWrapW(g_hInst, IDS_UI_TRAINING, sz, ARRAYSIZE(sz));
  1025. CComPtr<ISpRecognizer> cpRecoEngine;
  1026. HRESULT hr = m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpRecoEngine);
  1027. if (S_OK == hr && cpRecoEngine)
  1028. {
  1029. DWORD dwDictStatBackup = GetDictationStatBackup();
  1030. DWORD dwBefore;
  1031. if (S_OK != GetCompartmentDWORD(_tim,
  1032. GUID_COMPARTMENT_SPEECH_DISABLED,
  1033. &dwBefore,
  1034. FALSE) )
  1035. {
  1036. dwBefore = 0;
  1037. }
  1038. SetCompartmentDWORD(0, _tim, GUID_COMPARTMENT_SPEECH_DISABLED, TF_DISABLE_DICTATION, FALSE);
  1039. cpRecoEngine->DisplayUI(_GetAppMainWnd(), sz, SPDUI_UserTraining, NULL, 0);
  1040. SetCompartmentDWORD(0, _tim, GUID_COMPARTMENT_SPEECH_DISABLED, dwBefore, FALSE);
  1041. SetDictationStatAll(dwDictStatBackup);
  1042. }
  1043. return hr;
  1044. }
  1045. HRESULT CSapiIMX::_RequestEditSession(UINT idEditSession, DWORD dwFlag, ESDATA *pesData, ITfContext *picCaller, LONG_PTR *pRetData, IUnknown **ppRetUnk)
  1046. {
  1047. CSapiEditSession *pes;
  1048. CComPtr<ITfContext> cpic;
  1049. HRESULT hr = E_FAIL;
  1050. // callers can intentionally give us a NULL pic
  1051. if (picCaller == NULL)
  1052. {
  1053. GetFocusIC(&cpic);
  1054. }
  1055. else
  1056. {
  1057. cpic = picCaller;
  1058. }
  1059. if (cpic)
  1060. {
  1061. if (pes = new CSapiEditSession(this, cpic))
  1062. {
  1063. if ( pesData )
  1064. {
  1065. pes->_SetRange(pesData->pRange);
  1066. pes->_SetUnk(pesData->pUnk);
  1067. pes->_SetEditSessionData(idEditSession, pesData->pData, pesData->uByte, pesData->lData1, pesData->lData2, pesData->fBool);
  1068. }
  1069. else
  1070. pes->_SetEditSessionData(idEditSession, NULL, 0);
  1071. cpic->RequestEditSession(_tid, pes, dwFlag, &hr);
  1072. // if caller wants to get the return value from the edit session, it has to set SYNC edit session.
  1073. if ( pRetData )
  1074. *pRetData = pes->_GetRetData( );
  1075. if ( ppRetUnk )
  1076. *ppRetUnk = pes->_GetRetUnknown( );
  1077. pes->Release();
  1078. }
  1079. }
  1080. return hr;
  1081. }
  1082. //+---------------------------------------------------------------------------
  1083. //
  1084. // _DIMCallback
  1085. //
  1086. //----------------------------------------------------------------------------
  1087. HRESULT CSapiIMX::_DIMCallback(UINT uCode, ITfDocumentMgr *dim, ITfDocumentMgr * pdimPrevFocus, void *pv)
  1088. {
  1089. CSapiIMX *_this = (CSapiIMX *)pv;
  1090. switch (uCode)
  1091. {
  1092. case TIM_CODE_INITDIM:
  1093. //
  1094. // Add this dim to the LearnFromDoc internal dim list with fFeed as FALSE.
  1095. TraceMsg(TF_GENERAL, "TIM_CODE_INITDIM callback is called, DIM is %x", (INT_PTR)dim);
  1096. _this->_AddDimToList(dim, FALSE);
  1097. break;
  1098. // clean up any reference to ranges
  1099. case TIM_CODE_UNINITDIM:
  1100. TraceMsg(TF_GENERAL, "TIM_CODE_UNINITDIM callback is called, DIM is %x", (INT_PTR)dim);
  1101. _this->_RemoveDimFromList(dim);
  1102. if (_this->m_pCSpTask)
  1103. _this->m_pCSpTask->CleanupDictContext();
  1104. //DIM is to destroyed, we want to stop speaking if TTS is playing
  1105. if ( _this->_IsInPlay( ) )
  1106. {
  1107. _this->_HandleEventOnPlayButton( );
  1108. }
  1109. break;
  1110. case TIM_CODE_SETFOCUS:
  1111. TraceMsg(TF_GENERAL, "TIM_CODE_SETFOCUS callback is called, DIM is %x", (INT_PTR)dim);
  1112. if ( !_this->m_fStageTip )
  1113. {
  1114. // The stage tip is a special instance of a RichEdit control that wants to stay active (i.e. enabled)
  1115. // regardless of focus elsewhere in the hosting application. This way dictaion always goes to the stage.
  1116. // This relies on a written-in-stone contract that only one Cicero enabled text input control exists in
  1117. // said application.
  1118. _this->SetDICTATIONSTAT_DictEnabled(dim ? _this->m_fDictationEnabled : FALSE);
  1119. }
  1120. // When TIM_CODE_SETFOCUS is called means there is a document focus change.
  1121. // No matter what DIM is getting focus now, we just need to close the existing
  1122. // candidate list menu.
  1123. // NOTE - for the TabletTip stage instance, do we want to close the candidate UI. This means when the user clicks in
  1124. // the area surrounding the stage RichEdit or on the titlebar, the correction menu (and widget) get dismissed.
  1125. // This means when TabletTip is dragged around with either of these visible, the correction widget or menu gets dismissed
  1126. // since the first step of the drag is a click in the titlebar. NOTE - if we disable this code here it still gets dismissed
  1127. // so there is another handler somewhere triggering that.
  1128. _this->CloseCandUI( );
  1129. if ( dim )
  1130. {
  1131. // A DIM is getting focus, when LearnFromDoc is set, we need to feed
  1132. // the existing document to the Dictation grammar.
  1133. // And we need to check if we already feed the document for this dim to the SREngine.
  1134. // if we did, we don't feed it again for the same document.
  1135. if ( _this->GetLearnFromDoc( ) == TRUE )
  1136. {
  1137. HRESULT hr = S_OK;
  1138. hr = _this->HandleLearnFromDoc( dim );
  1139. }
  1140. else
  1141. TraceMsg(TF_GENERAL, "Learn From DOC is set to FALSE");
  1142. // We want to check if this new DIM is AIMM aware or pure Cicero aware,
  1143. // so that we can determine if we want to disble TTS buttons.
  1144. ITfContext *pic;
  1145. dim->GetTop(&pic);
  1146. if ( pic )
  1147. {
  1148. _this->_SetTTSButtonStatus( pic );
  1149. // for the top ic, we hook the modebias change
  1150. // notification so that we can set up the corresponding
  1151. // grammar for the bias
  1152. //
  1153. if (_this->GetDICTATIONSTAT_DictOnOff())
  1154. _this->_SyncModeBiasWithSelection(pic);
  1155. SafeRelease(pic);
  1156. }
  1157. }
  1158. break;
  1159. }
  1160. return S_OK;
  1161. }
  1162. //+---------------------------------------------------------------------------
  1163. //
  1164. // _ICCallback
  1165. //
  1166. //----------------------------------------------------------------------------
  1167. /* static */
  1168. HRESULT CSapiIMX::_ICCallback(UINT uCode, ITfContext *pic, void *pv)
  1169. {
  1170. HRESULT hr = E_FAIL;
  1171. CSapiIMX *_this = (CSapiIMX *)pv;
  1172. CICPriv *priv;
  1173. ITfContext *picTest;
  1174. switch (uCode)
  1175. {
  1176. case TIM_CODE_INITIC:
  1177. if ((priv = EnsureInputContextPriv(_this, pic)) != NULL)
  1178. {
  1179. _this->_InitICPriv(priv, pic);
  1180. priv->Release();
  1181. // We want to check if this IC is under a foucs DIM, and if it is AIMM aware or pure Cicero aware,
  1182. // so that we can determine if we want to disble TTS buttons.
  1183. ITfDocumentMgr *pFocusDIM = NULL, *pThisDIM = NULL;
  1184. _this->GetFocusDIM(&pFocusDIM);
  1185. pic->GetDocumentMgr(&pThisDIM);
  1186. if ( pFocusDIM == pThisDIM )
  1187. {
  1188. _this->_SetTTSButtonStatus( pic );
  1189. }
  1190. SafeRelease(pFocusDIM);
  1191. SafeRelease(pThisDIM);
  1192. hr = S_OK;
  1193. }
  1194. break;
  1195. case TIM_CODE_UNINITIC:
  1196. // start setfocus code
  1197. if ((priv = GetInputContextPriv(_this->_tid, pic)) != NULL)
  1198. {
  1199. _this->_DeleteICPriv(priv, pic);
  1200. priv->Release();
  1201. hr = S_OK;
  1202. }
  1203. if (_this->m_cpRangeCurIP != NULL) // should m_cpRangeCurIP be per-ic and stored in icpriv?
  1204. {
  1205. // free up m_cpRangeCurIP if it belongs to this context
  1206. if (_this->m_cpRangeCurIP->GetContext(&picTest) == S_OK)
  1207. {
  1208. if (pic == picTest)
  1209. {
  1210. _this->m_cpRangeCurIP.Release();
  1211. }
  1212. picTest->Release();
  1213. }
  1214. }
  1215. // IC is getting popped. We need to reset cicero awareness
  1216. // status based on the bottom IC. This assumes IC stack to
  1217. // be 2 at maximum.
  1218. //
  1219. if (pic)
  1220. {
  1221. CComPtr<ITfContext> cpicTop;
  1222. CComPtr<ITfDocumentMgr> cpdim;
  1223. hr = pic->GetDocumentMgr(&cpdim);
  1224. if (S_OK == hr)
  1225. {
  1226. cpdim->GetBase(&cpicTop);
  1227. }
  1228. if ( cpicTop )
  1229. {
  1230. _this->_SetTTSButtonStatus( cpicTop );
  1231. }
  1232. }
  1233. break;
  1234. }
  1235. return hr;
  1236. }
  1237. //+---------------------------------------------------------------------------
  1238. //
  1239. // _DeleteICPriv
  1240. //
  1241. //----------------------------------------------------------------------------
  1242. void CSapiIMX::_DeleteICPriv(CICPriv *priv, ITfContext *pic)
  1243. {
  1244. if (!priv)
  1245. return;
  1246. if (priv->m_pTextEvent)
  1247. {
  1248. priv->m_pTextEvent->_Unadvise();
  1249. SafeReleaseClear(priv->m_pTextEvent);
  1250. }
  1251. // we MUST clear out the private data before cicero is free to release the ic
  1252. ClearCompartment(_tid, pic, GUID_IC_PRIVATE, FALSE);
  1253. // this is it, we won't need the private data any longer
  1254. }
  1255. //+---------------------------------------------------------------------------
  1256. //
  1257. // _InitICPriv
  1258. //
  1259. //----------------------------------------------------------------------------
  1260. void CSapiIMX::_InitICPriv(CICPriv *priv, ITfContext *pic)
  1261. {
  1262. if (!priv->m_pTextEvent)
  1263. {
  1264. if ((priv->m_pTextEvent = new CTextEventSink(CSapiIMX::_TextEventSinkCallback, this)) != NULL)
  1265. {
  1266. priv->m_pTextEvent->_Advise(pic, ICF_TEXTDELTA);
  1267. }
  1268. }
  1269. }
  1270. //+---------------------------------------------------------------------------
  1271. //
  1272. // _KillFocusRange
  1273. //
  1274. // get rid of the focusrange within the given range
  1275. //
  1276. //---------------------------------------------------------------------------+
  1277. HRESULT CSapiIMX::_KillFocusRange(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, TfClientId tid)
  1278. {
  1279. HRESULT hr = E_FAIL;
  1280. IEnumITfCompositionView *pEnumComp = NULL;
  1281. ITfContextComposition *picc = NULL;
  1282. ITfCompositionView *pCompositionView;
  1283. ITfComposition *pComposition;
  1284. CLSID clsid;
  1285. CICPriv *picp;
  1286. //
  1287. // clear any sptip compositions over the range
  1288. //
  1289. if (pic->QueryInterface(IID_ITfContextComposition, (void **)&picc) != S_OK)
  1290. goto Exit;
  1291. if (picc->FindComposition(ec, pRange, &pEnumComp) != S_OK)
  1292. goto Exit;
  1293. // tid will be TF_CLIENTID_NULL when we're deactivated, in which case we
  1294. // don't want to mess with the composition count
  1295. picp = (tid == TF_CLIENTID_NULL) ? NULL : GetInputContextPriv(tid, pic);
  1296. while (pEnumComp->Next(1, &pCompositionView, NULL) == S_OK)
  1297. {
  1298. if (pCompositionView->GetOwnerClsid(&clsid) != S_OK)
  1299. goto NextComp;
  1300. // make sure we ignore other TIPs' compositions!
  1301. if (!IsEqualCLSID(clsid, CLSID_SapiLayr))
  1302. goto NextComp;
  1303. if (pCompositionView->QueryInterface(IID_ITfComposition, (void **)&pComposition) != S_OK)
  1304. goto NextComp;
  1305. // found a composition, terminate it
  1306. pComposition->EndComposition(ec);
  1307. pComposition->Release();
  1308. if (picp != NULL)
  1309. {
  1310. picp->_ReleaseComposition();
  1311. }
  1312. NextComp:
  1313. pCompositionView->Release();
  1314. }
  1315. SafeRelease(picp);
  1316. hr = S_OK;
  1317. Exit:
  1318. SafeRelease(picc);
  1319. SafeRelease(pEnumComp);
  1320. return hr;
  1321. }
  1322. //+---------------------------------------------------------------------------
  1323. //
  1324. // _SetFocusToStageIfStage
  1325. //
  1326. // Many voice commands (particularly selection and correction) do not make
  1327. // sense unless the focus is in the stage. This adjusts focus so the commands
  1328. // work as the user will expect.
  1329. //
  1330. //---------------------------------------------------------------------------+
  1331. HRESULT CSapiIMX::_SetFocusToStageIfStage(void)
  1332. {
  1333. HRESULT hr = S_OK;
  1334. if (m_fStageTip)
  1335. {
  1336. ASSERT(m_hwndStage && L"Have null HWND for stage.");
  1337. if (m_hwndStage)
  1338. {
  1339. CComPtr<IAccessible> cpAccessible;
  1340. hr = AccessibleObjectFromWindow(m_hwndStage, OBJID_WINDOW, IID_IAccessible, (void **)&cpAccessible);
  1341. if (SUCCEEDED(hr) && cpAccessible)
  1342. {
  1343. CComVariant cpVar = CHILDID_SELF;
  1344. hr = cpAccessible->accSelect(SELFLAG_TAKEFOCUS, cpVar);
  1345. }
  1346. }
  1347. }
  1348. return hr;
  1349. }
  1350. //+---------------------------------------------------------------------------
  1351. //
  1352. // _SetFilteringString
  1353. //
  1354. // When non-matching event is notified, this function is called to inject
  1355. // previous filtering string to the parent document.
  1356. //
  1357. //---------------------------------------------------------------------------+
  1358. HRESULT CSapiIMX::_SetFilteringString(TfEditCookie ec, ITfCandidateUI *pCandUI, ITfContext *pic)
  1359. {
  1360. HRESULT hr = E_FAIL;
  1361. ITfRange *pRange;
  1362. BSTR bstr;
  1363. CDocStatus ds(pic);
  1364. if (ds.IsReadOnly())
  1365. return S_OK;
  1366. if (SUCCEEDED(GetSelectionSimple(ec, pic, &pRange )))
  1367. {
  1368. CComPtr<ITfProperty> cpProp;
  1369. LANGID langid = 0x0409;
  1370. // get langid from the given range
  1371. if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LANGID, &cpProp)))
  1372. {
  1373. GetLangIdPropertyData(ec, cpProp, pRange, &langid);
  1374. }
  1375. CComPtr<ITfCandUIFnAutoFilter> cpFnFilter;
  1376. hr = _pCandUIEx->GetFunction(IID_ITfCandUIFnAutoFilter, (IUnknown **)&cpFnFilter);
  1377. if (S_OK == hr && SUCCEEDED(cpFnFilter->GetFilteringString( CANDUIFST_DETERMINED, &bstr )))
  1378. {
  1379. hr = SetTextAndProperty(&_libTLS, ec, pic, pRange, bstr, SysStringLen(bstr), langid, NULL);
  1380. SysFreeString( bstr );
  1381. }
  1382. pRange->Collapse( ec, TF_ANCHOR_END );
  1383. SetSelectionSimple(ec, pic, pRange);
  1384. // we don't want to inject undetermined string to the document anymore.
  1385. // Cicero will inject the non-matching keyboard char
  1386. // to the document right after the determined filter string.
  1387. pRange->Release();
  1388. }
  1389. return hr;
  1390. }
  1391. //----------------------------------------------------------------------------
  1392. //
  1393. // _CompEventSinkCallback (static)
  1394. //
  1395. //----------------------------------------------------------------------------
  1396. HRESULT CSapiIMX::_CompEventSinkCallback(void *pv, REFGUID rguid)
  1397. {
  1398. CSapiIMX *_this = (CSapiIMX *)pv;
  1399. BOOL fOn;
  1400. if (IsEqualGUID(rguid, GUID_COMPARTMENT_SAPI_AUDIO))
  1401. {
  1402. fOn = _this->GetAudioOnOff();
  1403. if (_this->m_pCSpTask)
  1404. _this->m_pCSpTask->_SetAudioRetainStatus(fOn);
  1405. return S_OK;
  1406. }
  1407. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_OPENCLOSE))
  1408. {
  1409. HRESULT hr = S_OK;
  1410. #ifdef SAPI_PERF_DEBUG
  1411. DWORD dw;
  1412. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_OPENCLOSE, &dw, TRUE);
  1413. TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_OPENCLOSE event : %i. \n", dw);
  1414. #endif
  1415. // TABLETPC
  1416. if ( S_OK != _this->IsActiveThread() )
  1417. {
  1418. TraceMsg(TF_GENERAL, "SPEECH_OPENCLOSE, App doesn't get Focus!");
  1419. return hr;
  1420. }
  1421. TraceMsg(TF_GENERAL, "SPEECH_OPENCLOSE, App GETs Focus!");
  1422. DWORD dwLocal, dwGlobal;
  1423. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dwLocal, FALSE);
  1424. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_GLOBALSTATE, &dwGlobal, TRUE);
  1425. dwGlobal = dwGlobal & (TF_DICTATION_ON + TF_COMMANDING_ON);
  1426. if ( (dwLocal & (TF_DICTATION_ON + TF_COMMANDING_ON)) != dwGlobal)
  1427. {
  1428. dwLocal = (dwLocal & ~(TF_DICTATION_ON + TF_COMMANDING_ON)) + dwGlobal;
  1429. SetCompartmentDWORD(_this->_tid, _this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, dwLocal, FALSE);
  1430. }
  1431. // first time...
  1432. if (!_this->m_pCSpTask)
  1433. {
  1434. //
  1435. // put "Starting Speech..." in the balloon
  1436. //
  1437. if (_this->GetOnOff() && _this->GetBalloonStatus())
  1438. {
  1439. _this->SetBalloonStatus(TRUE, TRUE); // force update
  1440. // make sure balloon is shown
  1441. _this->GetSpeechUIServer()->ShowUI(TRUE);
  1442. // Ask the speech ui server to set the SAPI initializing
  1443. // flag to the balloon so it'll do it at the first callback
  1444. //
  1445. hr = _this->GetSpeechUIServer()->SetBalloonSAPIInitFlag(TRUE);
  1446. WCHAR sz[128];
  1447. sz[0] = '\0';
  1448. CicLoadStringWrapW(g_hInst, IDS_NUI_STARTINGSPEECH, sz, ARRAYSIZE(sz));
  1449. _this->GetSpeechUIServer()->UpdateBalloon(TF_LB_BALLOON_RECO, sz, -1);
  1450. TraceMsg(TF_SAPI_PERF, "Show Starting speech ...");
  1451. }
  1452. // TABLETPC
  1453. // BUGBUG - Do we need this now I have fixed the _HandleOpenCloseEvent to work in whichever sptip actually has focus?
  1454. if (_this->m_fStageTip)
  1455. {
  1456. // Since the stage may not have focus, the delayed mechanism above will not result
  1457. // in dictation activating in the stage since the delayed activation will happen in the
  1458. // cicero app with focus - except that the stage is visible hence the app with focus
  1459. // will simply ignore it. Not what we want when the stage is activating.
  1460. // Ignore above hresult in case of failure - this is the more important call.
  1461. hr = _this->_HandleOpenCloseEvent();
  1462. }
  1463. }
  1464. else
  1465. {
  1466. hr = _this->_HandleOpenCloseEvent();
  1467. }
  1468. // Office App uses its own global compartment GUID_OfficeSpeechMode to keep the current mode,
  1469. // so that next time the application starts, it checks this value to initalize SAPI objects
  1470. // even if Microphone is OFF.
  1471. // Since we have already used our own global compartment GUID_COMPARTMENT_SPEECH_GLOBALSTATE to
  1472. // keep the speech mode system wide, there is no need for Office to use that global compartment
  1473. // for its own usage that way.
  1474. //
  1475. // So when Microphone is OFF, we just reset the global compartment GUID_OfficeSpeechMode.
  1476. if ( !_this->GetOnOff( ) )
  1477. {
  1478. SetCompartmentDWORD(_this->_tid, _this->_tim, GUID_OfficeSpeechMode, 0, TRUE);
  1479. }
  1480. // when we have a temporary composistion such as
  1481. // CUAS level2 or AIMM level3, we don't want to
  1482. // finalize on going composition each time mic turns off
  1483. // because it also shutdown chance for correction
  1484. //
  1485. if (S_OK == hr && _this->IsFocusFullAware(_this->_tim))
  1486. {
  1487. hr = _this->_FinalizeComposition();
  1488. }
  1489. return hr;
  1490. }
  1491. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_STAGE))
  1492. {
  1493. DWORD dw = 0;
  1494. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_STAGE, &dw, FALSE);
  1495. Assert(dw && L"NULL HWND passed in GUID_COMPARTMENT_SPEECH_STAGE");
  1496. if (dw != 0)
  1497. {
  1498. _this->m_hwndStage = (HWND) dw;
  1499. _this->m_fStageTip = TRUE;
  1500. }
  1501. }
  1502. // TABLETPC
  1503. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_STAGECHANGE))
  1504. {
  1505. HRESULT hr = S_OK;
  1506. DWORD dw;
  1507. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, &dw, TRUE);
  1508. _this->m_fStageVisible = dw ? TRUE:FALSE;
  1509. if (S_OK == _this->IsActiveThread())
  1510. {
  1511. _this->OnSetThreadFocus();
  1512. }
  1513. else
  1514. {
  1515. _this->OnKillThreadFocus();
  1516. }
  1517. }
  1518. // TABLETPC
  1519. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT))
  1520. {
  1521. _this->m_fDictationEnabled = _this->GetDICTATIONSTAT_DictEnabled();
  1522. #ifdef SAPI_PERF_DEBUG
  1523. DWORD dw;
  1524. GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dw, FALSE);
  1525. TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT is set in SAPIIMX, dw=%x", dw);
  1526. #endif
  1527. HRESULT hr;
  1528. // TABLETPC
  1529. hr = _this->IsActiveThread();
  1530. if ( hr == S_OK )
  1531. {
  1532. BOOL fDictOn, fCmdOn;
  1533. BOOL fDisable;
  1534. fOn = _this->GetOnOff();
  1535. fDisable = _this->Get_SPEECH_DISABLED_Disabled();
  1536. fDictOn = fOn && _this->GetDICTATIONSTAT_DictOnOff() && _this->GetDICTATIONSTAT_DictEnabled( ) && !fDisable && !_this->Get_SPEECH_DISABLED_DictationDisabled();
  1537. fCmdOn = fOn && _this->GetDICTATIONSTAT_CommandingOnOff( ) && !fDisable && !_this->Get_SPEECH_DISABLED_CommandingDisabled();
  1538. if ( _this->m_pCSpTask )
  1539. {
  1540. hr = _this->m_pCSpTask->_SetDictRecoCtxtState(fDictOn);
  1541. if ( hr == S_OK )
  1542. hr = _this->m_pCSpTask->_SetCmdRecoCtxtState(fCmdOn);
  1543. if ((fDictOn || fCmdOn ) && _this->m_pSpeechUIServer)
  1544. {
  1545. WCHAR sz[128];
  1546. sz[0] = '\0';
  1547. if (fDictOn)
  1548. {
  1549. CicLoadStringWrapW(g_hInst, IDS_NUI_DICTATION_TEXT, sz, ARRAYSIZE(sz));
  1550. hr = _this->m_pSpeechUIServer->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
  1551. TraceMsg(TF_SAPI_PERF, "Show \"Dictation\"");
  1552. }
  1553. else if ( fCmdOn )
  1554. {
  1555. CicLoadStringWrapW(g_hInst, IDS_NUI_COMMANDING_TEXT, sz, ARRAYSIZE(sz));
  1556. hr = _this->m_pSpeechUIServer->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
  1557. TraceMsg(TF_SAPI_PERF, "Show \"Voice command\"");
  1558. }
  1559. }
  1560. if (fDictOn)
  1561. {
  1562. hr = _this->HandleLearnFromDoc( );
  1563. if ( S_OK == hr )
  1564. _this->_SetCurrentIPtoSR();
  1565. }
  1566. }
  1567. if (S_OK == hr)
  1568. {
  1569. hr = _this->EraseFeedbackUI();
  1570. if (S_OK == hr)
  1571. hr = _this->_FinalizeComposition();
  1572. }
  1573. TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT exit normally");
  1574. }
  1575. else
  1576. TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT exits when the app doesn't get focus!");
  1577. return hr;
  1578. }
  1579. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_LEARNDOC))
  1580. {
  1581. _this->UpdateLearnDocState( );
  1582. return S_OK;
  1583. }
  1584. else if (IsEqualGUID(rguid,GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE) )
  1585. {
  1586. TraceMsg(TF_GENERAL, "GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE is set!");
  1587. // Renew all the property values from the registry.
  1588. _this->_RenewAllPropDataFromReg( );
  1589. // Specially handle some of property changes.
  1590. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Support_LMA) )
  1591. SetCompartmentDWORD(_this->_GetId( ), _this->_tim, GUID_COMPARTMENT_SPEECH_LEARNDOC, _this->_LMASupportEnabled( ), FALSE);
  1592. // Specially handle mode button setting changes
  1593. BOOL fModeButtonChanged;
  1594. fModeButtonChanged = _this->_IsPropItemChangedSinceLastRenew(PropId_Mode_Button) ||
  1595. _this->_IsPropItemChangedSinceLastRenew(PropId_Dictation_Key) ||
  1596. _this->_IsPropItemChangedSinceLastRenew(PropId_Command_Key);
  1597. _this->HandleModeKeySettingChange( fModeButtonChanged );
  1598. // For command category items, it will update grammars's status.
  1599. // Update the grammar's status.
  1600. CSpTask *psp;
  1601. _this->GetSpeechTask(&psp);
  1602. if ( psp )
  1603. {
  1604. DWORD dwActiveMode = ACTIVE_IN_BOTH_MODES; // indicates which mode will change the grammar status.
  1605. BOOL bDictCmdChanged = _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_DictMode);
  1606. if ( _this->_AllDictCmdsDisabled( ) )
  1607. {
  1608. // All the commands are disabled in dication mode.
  1609. psp->_ActivateCmdInDictMode(FALSE);
  1610. //Needs to activate spelling grammar in dictation mode.
  1611. psp->_ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
  1612. // Needs to activate "Force Num" grammar in dication strong mode.
  1613. psp->_ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
  1614. // Needs to activate language bar grammar in dictation strong mode for mode switching commands.
  1615. psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1616. // Only need to change grammar status in command mode.
  1617. dwActiveMode = ACTIVE_IN_COMMAND_MODE;
  1618. }
  1619. else
  1620. {
  1621. // if this was changed since latst renew.
  1622. if ( bDictCmdChanged )
  1623. {
  1624. psp->_ActiveCategoryCmds(DC_CC_SelectCorrect, _this->_SelectCorrectCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
  1625. psp-> _ActiveCategoryCmds(DC_CC_Navigation, _this->_NavigationCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1626. psp->_ActiveCategoryCmds(DC_CC_Casing, _this->_CasingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1627. psp->_ActiveCategoryCmds(DC_CC_Editing, _this->_EditingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1628. psp->_ActiveCategoryCmds(DC_CC_Keyboard, _this->_KeyboardCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1629. psp->_ActiveCategoryCmds(DC_CC_TTS, _this->_TTSCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1630. psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
  1631. psp->_ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
  1632. psp->_ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
  1633. if ( _this->_SelectCorrectCmdEnabled( ) || _this->_NavigationCmdEnabled( ) )
  1634. {
  1635. psp->_UpdateSelectGramTextBufWhenStatusChanged( );
  1636. }
  1637. dwActiveMode = ACTIVE_IN_COMMAND_MODE;
  1638. }
  1639. }
  1640. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Select_Correct) )
  1641. psp->_ActiveCategoryCmds(DC_CC_SelectCorrect, _this->_SelectCorrectCmdEnabled( ), dwActiveMode);
  1642. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Navigation) )
  1643. psp-> _ActiveCategoryCmds(DC_CC_Navigation, _this->_NavigationCmdEnabled( ), dwActiveMode );
  1644. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Casing) )
  1645. psp->_ActiveCategoryCmds(DC_CC_Casing, _this->_CasingCmdEnabled( ), dwActiveMode );
  1646. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Editing) )
  1647. psp->_ActiveCategoryCmds(DC_CC_Editing, _this->_EditingCmdEnabled( ), dwActiveMode );
  1648. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Keyboard) )
  1649. psp->_ActiveCategoryCmds(DC_CC_Keyboard, _this->_KeyboardCmdEnabled( ), dwActiveMode );
  1650. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_TTS) )
  1651. psp->_ActiveCategoryCmds(DC_CC_TTS, _this->_TTSCmdEnabled( ), dwActiveMode );
  1652. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Language_Bar) )
  1653. psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), dwActiveMode );
  1654. // Check to see if we need to fill text to selection grammar.
  1655. if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Select_Correct) ||
  1656. _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Navigation) )
  1657. {
  1658. BOOL bUpdateText;
  1659. bUpdateText = _this->_SelectCorrectCmdEnabled( ) || _this->_NavigationCmdEnabled( );
  1660. if ( bUpdateText )
  1661. {
  1662. psp->_UpdateSelectGramTextBufWhenStatusChanged( );
  1663. }
  1664. }
  1665. psp->Release( );
  1666. }
  1667. return S_OK;
  1668. }
  1669. #ifdef TF_DISABLE_SPEECH
  1670. else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_DISABLED))
  1671. {
  1672. BOOL fDictationDisabled = _this->Get_SPEECH_DISABLED_DictationDisabled() ? TRUE : FALSE;
  1673. BOOL fCommandingDisabled = _this->Get_SPEECH_DISABLED_CommandingDisabled() ? TRUE : FALSE;
  1674. if (fDictationDisabled)
  1675. _this->SetDICTATIONSTAT_DictOnOff(FALSE);
  1676. if (fCommandingDisabled)
  1677. _this->SetDICTATIONSTAT_CommandingOnOff(FALSE);
  1678. return S_OK;
  1679. }
  1680. #endif
  1681. return S_FALSE;
  1682. }
  1683. HRESULT CSapiIMX::_HandleOpenCloseEvent(MIC_STATUS ms)
  1684. {
  1685. HRESULT hr = S_OK;
  1686. BOOL fOn;
  1687. TraceMsg(TF_SAPI_PERF, "_HandleOpenCloseEvent is called, ms=%d", (int)ms);
  1688. if (ms == MICSTAT_NA)
  1689. {
  1690. fOn = GetOnOff();
  1691. }
  1692. else
  1693. {
  1694. fOn = (ms == MICSTAT_ON) ? TRUE : FALSE;
  1695. }
  1696. if (fOn)
  1697. {
  1698. // if no one so far set dictation status, we assume
  1699. // there's no C&C button so we can synchronize dictation
  1700. // status with mic
  1701. //
  1702. InitializeSAPI(TRUE);
  1703. if (m_fDictationEnabled == TRUE)
  1704. {
  1705. //
  1706. // if the caller wants to set the mic status (!= NA)
  1707. // we also want to make sure dictation status follow that
  1708. //
  1709. _SetCurrentIPtoSR();
  1710. // whenever dictation is turned on, we need to sync
  1711. // with the current modebias
  1712. //
  1713. CComPtr<ITfContext> cpic;
  1714. if (GetFocusIC(&cpic))
  1715. {
  1716. _gaModebias = 0;
  1717. _SyncModeBiasWithSelection(cpic);
  1718. }
  1719. }
  1720. }
  1721. if (m_pCSpTask)
  1722. {
  1723. m_pCSpTask->_SetInputOnOffState(fOn);
  1724. }
  1725. return hr;
  1726. }
  1727. //+---------------------------------------------------------------------------
  1728. //
  1729. // _SysLBarCallback
  1730. //
  1731. //----------------------------------------------------------------------------
  1732. HRESULT CSapiIMX::_SysLBarCallback(UINT uCode, void *pv, ITfMenu *pMenu, UINT wID)
  1733. {
  1734. CSapiIMX *pew = (CSapiIMX *)pv;
  1735. HRESULT hr = S_OK;
  1736. if (uCode == IDSLB_INITMENU)
  1737. {
  1738. WCHAR sz[128];
  1739. BOOL fOn = pew->GetOnOff();
  1740. sz[0] = '\0';
  1741. CicLoadStringWrapW(g_hInst, IDS_MIC_OPTIONS, sz, ARRAYSIZE(sz));
  1742. LangBarInsertMenu(pMenu, IDM_MIC_OPTIONS, sz);
  1743. #ifdef TEST_SHARED_ENGINE
  1744. LangBarInsertMenu(pMenu, IDM_MIC_SHAREDENGINE, L"Use shared engine", pew->m_fSharedReco);
  1745. LangBarInsertMenu(pMenu, IDM_MIC_INPROCENGINE, L"Use inproc engine", !pew->m_fSharedReco);
  1746. #endif
  1747. sz[0] = '\0';
  1748. CicLoadStringWrapW(g_hInst, IDS_MIC_TRAINING, sz, ARRAYSIZE(sz));
  1749. LangBarInsertMenu(pMenu, IDM_MIC_TRAINING, sz);
  1750. sz[0] = '\0';
  1751. CicLoadStringWrapW(g_hInst, IDS_MIC_ADDDELETE, sz, ARRAYSIZE(sz));
  1752. LangBarInsertMenu(pMenu, IDM_MIC_ADDDELETE, sz);
  1753. // insert sub menu for user profile stuff
  1754. ITfMenu *pSubMenu = NULL;
  1755. sz[0] = '\0';
  1756. CicLoadStringWrapW(g_hInst, IDS_MIC_CURRENTUSER, sz, ARRAYSIZE(sz));
  1757. hr = LangBarInsertSubMenu(pMenu, sz, &pSubMenu);
  1758. if (S_OK == hr)
  1759. {
  1760. CComPtr<IEnumSpObjectTokens> cpEnum;
  1761. CComPtr<ISpRecognizer> cpEngine;
  1762. ISpObjectToken *pUserProfile = NULL;
  1763. CSpDynamicString dstrDefaultUser;
  1764. // ensure SAPI is initialized
  1765. hr = pew->InitializeSAPI(TRUE);
  1766. if (S_OK == hr)
  1767. {
  1768. // get the current default user
  1769. hr = pew->m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpEngine);
  1770. }
  1771. if (S_OK == hr)
  1772. {
  1773. hr = cpEngine->GetRecoProfile(&pUserProfile);
  1774. }
  1775. if (S_OK == hr)
  1776. {
  1777. hr = SpGetDescription(pUserProfile, &dstrDefaultUser);
  1778. SafeRelease(pUserProfile);
  1779. }
  1780. if (S_OK == hr)
  1781. {
  1782. hr = SpEnumTokens (SPCAT_RECOPROFILES, NULL, NULL, &cpEnum);
  1783. }
  1784. if (S_OK == hr)
  1785. {
  1786. int idUser = 0;
  1787. while (cpEnum->Next(1, &pUserProfile, NULL) == S_OK)
  1788. {
  1789. // dstr frees itself
  1790. CSpDynamicString dstrUser;
  1791. hr = SpGetDescription(pUserProfile, &dstrUser);
  1792. if (S_OK == hr)
  1793. {
  1794. BOOL fDefaultUser = (wcscmp(dstrUser, dstrDefaultUser) == 0);
  1795. Assert(idUser < IDM_MIC_USEREND);
  1796. LangBarInsertMenu(pSubMenu, IDM_MIC_USERSTART + idUser++, dstrUser, fDefaultUser);
  1797. }
  1798. SafeRelease(pUserProfile);
  1799. }
  1800. }
  1801. pSubMenu->Release();
  1802. }
  1803. }
  1804. else if (uCode == IDSLB_ONMENUSELECT)
  1805. {
  1806. if ( wID == IDM_MIC_ONOFF )
  1807. {
  1808. // toggle mic
  1809. pew->SetOnOff(!pew->GetOnOff());
  1810. }
  1811. // Invoke SAPI UI stuff...
  1812. else if (wID == IDM_MIC_TRAINING)
  1813. {
  1814. hr = pew->_HandleTrainingWiz();
  1815. }
  1816. else if (wID == IDM_MIC_ADDDELETE)
  1817. {
  1818. // A editsession callback will handle this requirement first
  1819. // if the edit session fails, we will just display the UI
  1820. // without any initial words.
  1821. hr = pew->_RequestEditSession(ESCB_HANDLE_ADDDELETE_WORD, TF_ES_READ);
  1822. if ( FAILED(hr) )
  1823. hr = pew->DisplayAddDeleteUI( NULL, 0 );
  1824. }
  1825. else if (wID == IDM_MIC_OPTIONS)
  1826. {
  1827. PostMessage(pew->_GetWorkerWnd(), WM_PRIV_OPTIONS, 0, 0);
  1828. }
  1829. else if (wID >= IDM_MIC_USERSTART && wID < IDM_MIC_USEREND)
  1830. {
  1831. CComPtr<IEnumSpObjectTokens> cpEnum;
  1832. CComPtr<ISpObjectToken> cpProfile;
  1833. // change the current user
  1834. //
  1835. // this is still a hack, until we get an OnEndMenu event for LangBarItemSink
  1836. // for now I just assume SAPI enumerates profiles in same order always
  1837. //
  1838. // what we should really do is to set up an arry to associate IDs with
  1839. // user profiles and clean them up when OnEndMenu comes to us
  1840. //
  1841. if (S_OK == hr)
  1842. {
  1843. hr = SpEnumTokens (SPCAT_RECOPROFILES, NULL, NULL, &cpEnum);
  1844. }
  1845. if (S_OK == hr)
  1846. {
  1847. ULONG ulidUser = wID - IDM_MIC_USERSTART;
  1848. ULONG ulFetched;
  1849. CPtrArray<ISpObjectToken> rgpProfile;
  1850. rgpProfile.Append(ulidUser+1);
  1851. // trasform 0 base index to num of profile
  1852. hr = cpEnum->Next(ulidUser+1, rgpProfile.GetPtr(0), &ulFetched);
  1853. if (S_OK == hr && ulFetched == ulidUser+1)
  1854. {
  1855. // get the profile which is selected
  1856. cpProfile = rgpProfile.Get(ulidUser);
  1857. // clean up
  1858. for(ULONG i = 0; i <= ulidUser ; i++)
  1859. {
  1860. rgpProfile.Get(i)->Release();
  1861. }
  1862. }
  1863. }
  1864. if (S_OK == hr && cpProfile)
  1865. {
  1866. hr = SpSetDefaultTokenForCategoryId(SPCAT_RECOPROFILES, cpProfile);
  1867. if ( S_OK == hr )
  1868. {
  1869. CComPtr<ISpRecognizer> cpEngine;
  1870. hr = pew->m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpEngine);
  1871. if (S_OK == hr)
  1872. {
  1873. SPRECOSTATE State;
  1874. if (S_OK == cpEngine->GetRecoState(&State))
  1875. {
  1876. cpEngine->SetRecoState(SPRST_INACTIVE);
  1877. hr = cpEngine->SetRecoProfile(cpProfile);
  1878. cpEngine->SetRecoState(State);
  1879. }
  1880. }
  1881. }
  1882. }
  1883. }
  1884. #ifdef TEST_SHARED_ENGINE
  1885. else if (wID == IDM_MIC_SHAREDENGINE || wID == IDM_MIC_INPROCENGINE)
  1886. {
  1887. pew->m_fSharedReco = wID == IDM_MIC_SHAREDENGINE ? TRUE : FALSE;
  1888. pew->_ReinitializeSAPI();
  1889. }
  1890. #endif
  1891. }
  1892. return hr;
  1893. }
  1894. void CSapiIMX::_ReinitializeSAPI(void)
  1895. {
  1896. TraceMsg(TF_SAPI_PERF, "_ReinitializeSAPI is called");
  1897. DeinitializeSAPI();
  1898. InitializeSAPI(TRUE);
  1899. }
  1900. //+---------------------------------------------------------------------------
  1901. //
  1902. // OnCompositionTerminated
  1903. //
  1904. // Cicero calls this method when one of our compositions is terminated.
  1905. //----------------------------------------------------------------------------
  1906. STDAPI CSapiIMX::OnCompositionTerminated(TfEditCookie ec, ITfComposition *pComposition)
  1907. {
  1908. ITfRange *pRange = NULL;
  1909. ITfContext *pic = NULL;
  1910. ITfContext *picTest;
  1911. CICPriv *picp;
  1912. HRESULT hr;
  1913. TraceMsg(TF_GENERAL, "OnCompositionTerminated is Called");
  1914. hr = E_FAIL;
  1915. if (pComposition->GetRange(&pRange) != S_OK)
  1916. goto Exit;
  1917. if (pRange->GetContext(&pic) != S_OK)
  1918. goto Exit;
  1919. if (_fDeactivated)
  1920. {
  1921. // CleanupConsider: benwest: I don't think this can happen anymore...
  1922. hr = MakeResultString(ec, pic, pRange, TF_CLIENTID_NULL, NULL);
  1923. }
  1924. else
  1925. {
  1926. // Close candidate ui if it is up.
  1927. CloseCandUI( );
  1928. // take note we're done composing
  1929. if (picp = GetInputContextPriv(_tid, pic))
  1930. {
  1931. picp->_ReleaseComposition();
  1932. picp->Release();
  1933. }
  1934. if (!m_fStartingComposition)
  1935. {
  1936. hr = MakeResultString(ec, pic, pRange, _tid, m_pCSpTask);
  1937. }
  1938. else
  1939. {
  1940. // just avoid terminating recognition when we are just about
  1941. // to start composition.
  1942. hr = S_OK;
  1943. }
  1944. }
  1945. // free up m_cpRangeCurIP if it belongs to this context
  1946. if (m_cpRangeCurIP != NULL &&
  1947. m_cpRangeCurIP->GetContext(&picTest) == S_OK)
  1948. {
  1949. if (pic == picTest)
  1950. {
  1951. m_cpRangeCurIP.Release();
  1952. }
  1953. picTest->Release();
  1954. }
  1955. // unadvise mouse sink
  1956. if (m_pMouseSink)
  1957. {
  1958. m_pMouseSink->_Unadvise();
  1959. SafeReleaseClear(m_pMouseSink);
  1960. }
  1961. Exit:
  1962. SafeRelease(pRange);
  1963. SafeRelease(pic);
  1964. return hr;
  1965. }
  1966. //+---------------------------------------------------------------------------
  1967. //
  1968. // _FindComposition
  1969. //
  1970. //----------------------------------------------------------------------------
  1971. /* static */
  1972. BOOL CSapiIMX::_FindComposition(TfEditCookie ec, ITfContextComposition *picc, ITfRange *pRange, ITfCompositionView **ppCompositionView)
  1973. {
  1974. ITfCompositionView *pCompositionView;
  1975. IEnumITfCompositionView *pEnum;
  1976. ITfRange *pRangeView = NULL;
  1977. BOOL fFoundComposition;
  1978. LONG l;
  1979. CLSID clsid;
  1980. HRESULT hr;
  1981. if (picc->FindComposition(ec, pRange, &pEnum) != S_OK)
  1982. {
  1983. Assert(0);
  1984. return FALSE;
  1985. }
  1986. fFoundComposition = FALSE;
  1987. while (!fFoundComposition && pEnum->Next(1, &pCompositionView, NULL) == S_OK)
  1988. {
  1989. hr = pCompositionView->GetOwnerClsid(&clsid);
  1990. Assert(hr == S_OK);
  1991. // make sure we ignore other TIPs' compositions!
  1992. if (!IsEqualCLSID(clsid, CLSID_SapiLayr))
  1993. goto NextRange;
  1994. hr = pCompositionView->GetRange(&pRangeView);
  1995. Assert(hr == S_OK);
  1996. if (pRange->CompareStart(ec, pRangeView, TF_ANCHOR_START, &l) == S_OK &&
  1997. l >= 0 &&
  1998. pRange->CompareEnd(ec, pRangeView, TF_ANCHOR_END, &l) == S_OK &&
  1999. l <= 0)
  2000. {
  2001. // our test range is within this composition range
  2002. fFoundComposition = TRUE;
  2003. }
  2004. NextRange:
  2005. SafeRelease(pRangeView);
  2006. if (fFoundComposition && ppCompositionView != NULL)
  2007. {
  2008. *ppCompositionView = pCompositionView;
  2009. }
  2010. else
  2011. {
  2012. pCompositionView->Release();
  2013. }
  2014. }
  2015. pEnum->Release();
  2016. return fFoundComposition;
  2017. }
  2018. //+---------------------------------------------------------------------------
  2019. //
  2020. // _CheckStartComposition
  2021. //
  2022. //----------------------------------------------------------------------------
  2023. void CSapiIMX::_CheckStartComposition(TfEditCookie ec, ITfRange *pRange)
  2024. {
  2025. ITfContext *pic;
  2026. ITfContextComposition *picc;
  2027. ITfComposition *pComposition;
  2028. CICPriv *picp;
  2029. HRESULT hr;
  2030. if (pRange->GetContext(&pic) != S_OK)
  2031. return;
  2032. hr = pic->QueryInterface(IID_ITfContextComposition, (void **)&picc);
  2033. Assert(hr == S_OK);
  2034. // is pRange already included in a composition range?
  2035. if (_FindComposition(ec, picc, pRange, NULL))
  2036. goto Exit; // there's already a composition, we're golden
  2037. // need to create a new composition, or at least try
  2038. m_fStartingComposition = TRUE;
  2039. if (picc->StartComposition(ec, pRange, this, &pComposition) == S_OK)
  2040. {
  2041. if (pComposition != NULL) // NULL if the app rejects the composition
  2042. {
  2043. // take note we're composing
  2044. if (picp = GetInputContextPriv(_tid, pic))
  2045. {
  2046. picp->_AddRefComposition();
  2047. picp->Release();
  2048. }
  2049. // create mouse sink here for unfinalized composition buffer
  2050. if (!IsFocusFullAware(_tim) && !m_pMouseSink)
  2051. {
  2052. m_pMouseSink = new CMouseSink(_MouseSinkCallback, this);
  2053. if (m_pMouseSink)
  2054. {
  2055. CComPtr<ITfRange> cpRange;
  2056. hr = pComposition->GetRange(&cpRange);
  2057. if (S_OK == hr)
  2058. {
  2059. hr = m_pMouseSink->_Advise(cpRange, pic);
  2060. }
  2061. // mouse not pressed, no selection first
  2062. m_fMouseDown = FALSE;
  2063. m_ichMouseSel = 0;
  2064. }
  2065. }
  2066. // we already set up the sink, so we'll use ITfContextComposition::FindComposition
  2067. // to get this guy back when we want to terminate it
  2068. // Cicero will hold a ref to the object until someone terminates it
  2069. pComposition->Release();
  2070. }
  2071. }
  2072. m_fStartingComposition = FALSE;
  2073. Exit:
  2074. pic->Release();
  2075. picc->Release();
  2076. }
  2077. //+---------------------------------------------------------------------------
  2078. //
  2079. // IsInterestedInContext
  2080. //
  2081. //----------------------------------------------------------------------------
  2082. HRESULT CSapiIMX::IsInterestedInContext(ITfContext *pic, BOOL *pfInterested)
  2083. {
  2084. CICPriv *picp;
  2085. *pfInterested = FALSE;
  2086. if (picp = GetInputContextPriv(_tid, pic))
  2087. {
  2088. // we only need access to ic's with active compositions
  2089. *pfInterested = (picp->_GetCompositionCount() > 0);
  2090. picp->Release();
  2091. }
  2092. return S_OK;
  2093. }
  2094. //+---------------------------------------------------------------------------
  2095. //
  2096. // CleanupContext
  2097. //
  2098. // This method is a callback for the library helper CleanupAllContexts.
  2099. // We have to be very careful here because we may be called _after_ this tip
  2100. // has been deactivated, if the app couldn't grant a lock right away.
  2101. //----------------------------------------------------------------------------
  2102. HRESULT CSapiIMX::CleanupContext(TfEditCookie ecWrite, ITfContext *pic)
  2103. {
  2104. // all sptip cares about is finalizing compositions
  2105. CleanupAllCompositions(ecWrite, pic, CLSID_SapiLayr, _CleanupCompositionsCallback, this);
  2106. return S_OK;
  2107. }
  2108. //+---------------------------------------------------------------------------
  2109. //
  2110. // _CleanupCompositionsCallback
  2111. //
  2112. //----------------------------------------------------------------------------
  2113. /* static */
  2114. void CSapiIMX::_CleanupCompositionsCallback(TfEditCookie ecWrite, ITfRange *rangeComposition, void *pvPrivate)
  2115. {
  2116. ITfContext *pic;
  2117. CICPriv *picp;
  2118. CSapiIMX *_this = (CSapiIMX *)pvPrivate;
  2119. if (rangeComposition->GetContext(&pic) != S_OK)
  2120. return;
  2121. if (_this->_fDeactivated)
  2122. {
  2123. // this is a cleanup callback. _tid, m_pCSpTask should already have been cleaned up
  2124. _this->MakeResultString(ecWrite, pic, rangeComposition, TF_CLIENTID_NULL, NULL);
  2125. }
  2126. else
  2127. {
  2128. // during a profile switch we will still be active and need to clear the composition count on this ic
  2129. if (picp = GetInputContextPriv(_this->_tid, pic))
  2130. {
  2131. // clear the composition count for this ic
  2132. picp->_ReleaseComposition();
  2133. picp->Release();
  2134. }
  2135. _this->MakeResultString(ecWrite, pic, rangeComposition, _this->_tid, _this->m_pCSpTask);
  2136. }
  2137. pic->Release();
  2138. }
  2139. //+---------------------------------------------------------------------------
  2140. //
  2141. // _IsDoubleClick
  2142. //
  2143. // returns TRUE only if the last position is same and lbutton down happens
  2144. // within the time defined for double click
  2145. //
  2146. //----------------------------------------------------------------------------
  2147. BOOL CSapiIMX::_IsDoubleClick(ULONG uEdge, ULONG uQuadrant, DWORD dwBtnStatus)
  2148. {
  2149. if (dwBtnStatus & MK_LBUTTON)
  2150. {
  2151. LONG lTime=GetMessageTime();
  2152. if (!m_fMouseDown && m_uLastEdge == uEdge && m_uLastQuadrant == uQuadrant)
  2153. {
  2154. if (lTime > m_lTimeLastClk && lTime < m_lTimeLastClk + 500) // use 500 ms for now
  2155. {
  2156. return TRUE;
  2157. }
  2158. }
  2159. m_uLastEdge = uEdge;
  2160. m_uLastQuadrant = uQuadrant;
  2161. m_lTimeLastClk = lTime;
  2162. }
  2163. return FALSE;
  2164. }
  2165. //+---------------------------------------------------------------------------
  2166. //
  2167. // _MouseSinkCallback
  2168. //
  2169. // synopsis: set the current IP on the composition range based on
  2170. // uEdge parameter. We don't probably care too much about
  2171. // eQuadrant for speech composition
  2172. //
  2173. //----------------------------------------------------------------------------
  2174. /* static */
  2175. HRESULT CSapiIMX::_MouseSinkCallback(ULONG uEdge, ULONG uQuadrant, DWORD dwBtnStatus, BOOL *pfEaten, void *pv)
  2176. {
  2177. CSapiIMX *_this = (CSapiIMX *)pv;
  2178. Assert(pv);
  2179. BOOL fDoubleClick = _this->_IsDoubleClick(uEdge, uQuadrant, dwBtnStatus);
  2180. ESDATA esData = {0};
  2181. esData.lData1 = (LONG_PTR)uEdge;
  2182. esData.lData2 = (LONG_PTR)dwBtnStatus;
  2183. esData.fBool = fDoubleClick;
  2184. if (pfEaten)
  2185. *pfEaten = TRUE;
  2186. return _this->_RequestEditSession(ESCB_HANDLE_MOUSESINK, TF_ES_READWRITE, &esData);
  2187. }
  2188. HRESULT CSapiIMX::_HandleMouseSink(TfEditCookie ec, ULONG uEdge, ULONG uBtnStatus, BOOL fDblClick)
  2189. {
  2190. CComPtr<ITfDocumentMgr> cpDim;
  2191. CComPtr<ITfContext> cpic;
  2192. CComPtr<ITfContextComposition> cpicc;
  2193. CComPtr<IEnumITfCompositionView> cpEnumComp;
  2194. CComPtr<ITfCompositionView> cpCompositionView;
  2195. CComPtr<ITfRange> cpRangeComp;
  2196. CComPtr<ITfRange> cpRangeSel;
  2197. // if the btn up comes, the next time we'll destroy the selection
  2198. // nothing we need to do in this turn
  2199. BOOL fLeftBtn = (uBtnStatus & MK_LBUTTON) > 0 ? TRUE : FALSE;
  2200. if (!fLeftBtn)
  2201. {
  2202. m_ichMouseSel = 0;
  2203. m_fMouseDown = FALSE;
  2204. return S_OK;
  2205. }
  2206. // Close candidate ui if it is up.
  2207. CloseCandUI( );
  2208. HRESULT hr = GetFocusDIM(&cpDim);
  2209. if(S_OK == hr)
  2210. {
  2211. hr= cpDim->GetBase(&cpic);
  2212. }
  2213. if (S_OK == hr)
  2214. {
  2215. hr = cpic->QueryInterface(IID_ITfContextComposition, (void **)&cpicc);
  2216. }
  2217. if (S_OK == hr)
  2218. {
  2219. hr = cpicc->EnumCompositions(&cpEnumComp);
  2220. }
  2221. if (S_OK == hr)
  2222. {
  2223. while ((hr = cpEnumComp->Next(1, &cpCompositionView, NULL)) == S_OK)
  2224. {
  2225. hr = cpCompositionView->GetRange(&cpRangeComp);
  2226. if (S_OK == hr)
  2227. break;
  2228. // prepare for the next turn
  2229. cpCompositionView.Release();
  2230. }
  2231. }
  2232. if (S_OK == hr)
  2233. {
  2234. if (fDblClick)
  2235. {
  2236. WCHAR wsz[256] = {0}; // the buffer is 256 chars max
  2237. ULONG cch = 0;
  2238. CComPtr<ITfRange> cpRangeWord;
  2239. // obtain the text within the entire composition
  2240. hr = cpRangeComp->Clone(&cpRangeWord);
  2241. if (S_OK == hr)
  2242. {
  2243. hr = cpRangeWord->GetText(ec, 0, wsz, 255, &cch);
  2244. }
  2245. // get the left side edge char position, looking at delimiters
  2246. if (S_OK == hr)
  2247. {
  2248. WCHAR *psz = &wsz[uEdge];
  2249. while (psz > wsz)
  2250. {
  2251. if (!iswalpha(*psz))
  2252. {
  2253. psz++;
  2254. break;
  2255. }
  2256. psz--;
  2257. }
  2258. // re-posisition ich
  2259. m_ichMouseSel = psz - wsz;
  2260. // get the right side word boundary, also based on delimiters
  2261. psz = &wsz[uEdge];
  2262. while( psz < &wsz[cch] )
  2263. {
  2264. if (!iswalpha(*psz))
  2265. {
  2266. break;
  2267. }
  2268. psz++;
  2269. }
  2270. // reposition uEdge
  2271. uEdge = psz - wsz;
  2272. }
  2273. // pretend lbutton was previously down to get the same effect of
  2274. // dragging selection
  2275. //
  2276. m_fMouseDown = TRUE;
  2277. }
  2278. }
  2279. if (S_OK == hr)
  2280. {
  2281. hr = cpRangeComp->Clone(&cpRangeSel);
  2282. }
  2283. if (S_OK == hr)
  2284. {
  2285. if(m_fMouseDown)
  2286. {
  2287. // if the mouse is down the last time and still down this time
  2288. // it means it was dragged like this _v_>>>>_v_ or _v_<<<<_v_
  2289. // we'll have to make a selection accordingly
  2290. // 1) place the IP to the previous position
  2291. long cch;
  2292. hr = cpRangeSel->ShiftStart(ec, m_ichMouseSel, &cch, NULL);
  2293. if (S_OK == hr)
  2294. {
  2295. // 2) prepare for extension
  2296. hr = cpRangeSel->Collapse( ec, TF_ANCHOR_START);
  2297. }
  2298. }
  2299. }
  2300. if (S_OK == hr)
  2301. {
  2302. long ich = (long) (uEdge);
  2303. long cch;
  2304. // 3) see if there's a prev selection and if there is,
  2305. // calculate the dir and width of selection
  2306. // note that we've already had ich set to the pos at above 1) & 2)
  2307. long iich = 0;
  2308. if (m_fMouseDown)
  2309. {
  2310. iich = ich - m_ichMouseSel;
  2311. }
  2312. if (iich > 0) // sel towards the end
  2313. {
  2314. hr = cpRangeSel->ShiftEnd(ec, iich, &cch, NULL);
  2315. }
  2316. else if (iich < 0) // sel towards the start
  2317. {
  2318. hr = cpRangeSel->ShiftStart(ec, iich, &cch, NULL);
  2319. }
  2320. else // no width sel == an IP
  2321. {
  2322. hr = cpRangeSel->ShiftStart(ec, ich, &cch, NULL);
  2323. if (S_OK == hr) // collapse it only when there's no selection
  2324. {
  2325. hr = cpRangeSel->Collapse( ec, TF_ANCHOR_START);
  2326. }
  2327. }
  2328. // preserve the ip position so we can make a selection later
  2329. // a tricky thing is you have to remember the pos where you
  2330. // have started to "drag" not the pos you just updated
  2331. // so we need this only for the first time we started selection
  2332. if (!m_fMouseDown)
  2333. m_ichMouseSel = ich;
  2334. }
  2335. if (S_OK == hr)
  2336. {
  2337. BOOL fSetSelection = TRUE;
  2338. CComPtr<ITfRange> cpRangeCur;
  2339. HRESULT tmpHr = S_OK;
  2340. tmpHr = GetSelectionSimple(ec, cpic, &cpRangeCur);
  2341. if (SUCCEEDED(tmpHr) && cpRangeCur)
  2342. {
  2343. LONG l = 0;
  2344. if (cpRangeCur->CompareStart(ec, cpRangeSel, TF_ANCHOR_START, &l) == S_OK && l == 0 &&
  2345. cpRangeCur->CompareEnd(ec, cpRangeSel, TF_ANCHOR_END, &l) == S_OK && l == 0)
  2346. {
  2347. fSetSelection = FALSE;
  2348. }
  2349. }
  2350. if (fSetSelection)
  2351. {
  2352. CComPtr<ITfProperty> cpProp;
  2353. hr = cpic->GetProperty(GUID_PROP_ATTRIBUTE, &cpProp);
  2354. if (S_OK == hr)
  2355. {
  2356. SetGUIDPropertyData(&_libTLS, ec, cpProp, cpRangeCur, GUID_NULL);
  2357. SetGUIDPropertyData(&_libTLS, ec, cpProp, cpRangeSel, GUID_ATTR_SAPI_SELECTION);
  2358. }
  2359. hr = SetSelectionSimple(ec, cpic, cpRangeSel);
  2360. }
  2361. }
  2362. m_fMouseDown = fLeftBtn;
  2363. return hr;
  2364. }