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.

2236 lines
70 KiB

  1. //
  2. // Dictation.cpp
  3. //
  4. // This file contains functions related to dictation mode handling.
  5. //
  6. //
  7. // They are moved from sapilayr.cpp
  8. #include "private.h"
  9. #include "globals.h"
  10. #include "sapilayr.h"
  11. const GUID *s_KnownModeBias[] =
  12. {
  13. &GUID_MODEBIAS_NUMERIC
  14. };
  15. //+---------------------------------------------------------------------------
  16. //
  17. // CSapiIMX::InjectText
  18. //
  19. // synopsis - recieve text from ISpTask and insert it to the current selection
  20. //
  21. //
  22. //----------------------------------------------------------------------------
  23. HRESULT CSapiIMX::InjectText(const WCHAR *pwszRecognized, LANGID langid, ITfContext *pic)
  24. {
  25. if ( pwszRecognized == NULL )
  26. return E_INVALIDARG;
  27. ESDATA esData;
  28. memset(&esData, 0, sizeof(ESDATA));
  29. esData.pData = (void *)pwszRecognized;
  30. esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR);
  31. esData.lData1 = (LONG_PTR)langid;
  32. return _RequestEditSession(ESCB_PROCESSTEXT, TF_ES_READWRITE, &esData, pic);
  33. }
  34. //+---------------------------------------------------------------------------
  35. //
  36. // CSapiIMX::InjectTextWithoutOwnerID
  37. //
  38. // synopsis - inject text to the clients doc same way InjectText does but
  39. // with GUID_PROP_TEXTOWNER cleared out
  40. //
  41. //
  42. //----------------------------------------------------------------------------
  43. HRESULT
  44. CSapiIMX::InjectTextWithoutOwnerID(const WCHAR *pwszRecognized, LANGID langid)
  45. {
  46. if ( pwszRecognized == NULL )
  47. return E_INVALIDARG;
  48. ESDATA esData;
  49. memset(&esData, 0, sizeof(ESDATA));
  50. esData.pData = (void *)pwszRecognized;
  51. esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR);
  52. esData.lData1 = (LONG_PTR)langid;
  53. return _RequestEditSession(ESCB_PROCESSTEXT_NO_OWNERID, TF_ES_READWRITE, &esData);
  54. }
  55. //+---------------------------------------------------------------------------
  56. //
  57. // CSapiIMX::HRESULT InjectSpelledText
  58. //
  59. // synopsis - inject spelled text to the clients doc
  60. //
  61. //
  62. //----------------------------------------------------------------------------
  63. HRESULT CSapiIMX::InjectSpelledText(WCHAR *pwszText, LANGID langid, BOOL fOwnerId)
  64. {
  65. if ( pwszText == NULL )
  66. return E_INVALIDARG;
  67. ESDATA esData;
  68. memset(&esData, 0, sizeof(ESDATA));
  69. esData.pData = (void *)pwszText;
  70. esData.uByte = (wcslen(pwszText)+1) * sizeof(WCHAR);
  71. esData.lData1 = (LONG_PTR)langid;
  72. esData.fBool = fOwnerId;
  73. return _RequestEditSession(ESCB_INJECT_SPELL_TEXT, TF_ES_READWRITE, &esData);
  74. }
  75. //+---------------------------------------------------------------------------
  76. //
  77. // CSapiIMX::InjectModebiasText
  78. //
  79. // synopsis - recieve ModeBias text from ISpTask and insert it to the current
  80. // selection
  81. //
  82. //----------------------------------------------------------------------------
  83. HRESULT CSapiIMX::InjectModebiasText(const WCHAR *pwszRecognized, LANGID langid)
  84. {
  85. if ( pwszRecognized == NULL )
  86. return E_INVALIDARG;
  87. ESDATA esData;
  88. memset(&esData, 0, sizeof(ESDATA));
  89. esData.pData = (void *)pwszRecognized;
  90. esData.uByte = (wcslen(pwszRecognized)+1) * sizeof(WCHAR);
  91. esData.lData1 = (LONG_PTR)langid;
  92. return _RequestEditSession(ESCB_PROCESS_MODEBIAS_TEXT, TF_ES_READWRITE, &esData);
  93. }
  94. //+--------------------------------------------------------------------------+
  95. //
  96. // CSapiIMX::_ProcessModebiasText
  97. //
  98. //+--------------------------------------------------------------------------+
  99. HRESULT CSapiIMX::_ProcessModebiasText(TfEditCookie ec, WCHAR *pwszText, LANGID langid, ITfContext *picCaller)
  100. {
  101. HRESULT hr = E_FAIL;
  102. ITfContext *pic = NULL;
  103. if (!picCaller)
  104. {
  105. GetFocusIC(&pic);
  106. }
  107. else
  108. {
  109. pic = picCaller;
  110. }
  111. hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE);
  112. if (picCaller == NULL)
  113. {
  114. SafeRelease(pic);
  115. }
  116. // Before we clear the saved ip range, we need to treat this current ip as last
  117. // saved ip range if current ip is selected by end user
  118. SaveLastUsedIPRange( );
  119. SaveIPRange(NULL);
  120. return hr;
  121. }
  122. //+---------------------------------------------------------------------------
  123. //
  124. // CSapiIMX::InjectFeedbackUI
  125. //
  126. // synopsis - insert dotted bar to a doc for the length of cch
  127. //
  128. //
  129. //---------------------------------------------------------------------------+
  130. HRESULT CSapiIMX::InjectFeedbackUI(const GUID attr, LONG cch)
  131. {
  132. ESDATA esData;
  133. memset(&esData, 0, sizeof(ESDATA));
  134. esData.pData = (void *)&attr;
  135. esData.uByte = sizeof(GUID);
  136. esData.lData1 = (LONG_PTR)cch;
  137. return _RequestEditSession(ESCB_FEEDBACKUI, TF_ES_READWRITE, &esData);
  138. }
  139. //+---------------------------------------------------------------------------
  140. //
  141. // CSapiIMX::EraseFeedbackUI
  142. //
  143. // synopsis - cleans up the feedback UI
  144. // GUID - specifies which feedback UI bar to erase
  145. //
  146. //---------------------------------------------------------------------------+
  147. HRESULT CSapiIMX::EraseFeedbackUI()
  148. {
  149. if ( S_OK == IsActiveThread())
  150. {
  151. return _RequestEditSession(ESCB_KILLFEEDBACKUI, TF_ES_ASYNC|TF_ES_READWRITE, NULL);
  152. }
  153. return S_OK;
  154. }
  155. //+--------------------------------------------------------------------------+
  156. //
  157. // CSapiIMX::__AddFeedbackUI
  158. //
  159. //+--------------------------------------------------------------------------+
  160. HRESULT CSapiIMX::_AddFeedbackUI(TfEditCookie ec, ColorType ct, LONG cch)
  161. {
  162. HRESULT hr = E_FAIL;
  163. ITfContext *pic;
  164. //
  165. // distinguish unaware applications from cicero aware apps
  166. //
  167. GUID attr = ((ct == DA_COLOR_AWARE) ? GUID_ATTR_SAPI_GREENBAR : GUID_ATTR_SAPI_GREENBAR2);
  168. if (cch > 0)
  169. {
  170. WCHAR *pwsz = (WCHAR *)cicMemAlloc((cch + 1)*sizeof(WCHAR));
  171. if (pwsz)
  172. {
  173. for (int i = 0; i < cch; i++ )
  174. pwsz[i] = L'.';
  175. pwsz[i] = L'\0';
  176. if (GetFocusIC(&pic))
  177. {
  178. // When add feedback UI, we should not change current doc's reco result property store
  179. // so set fPreserveResult as TRUE.
  180. // Only when the final text is injected to the document, the property store will be
  181. // updated.
  182. hr = _ProcessTextInternal(ec, pwsz, attr, GetUserDefaultLangID(), pic, TRUE);
  183. SafeRelease(pic);
  184. }
  185. cicMemFree(pwsz);
  186. }
  187. }
  188. return hr;
  189. }
  190. //+--------------------------------------------------------------------------+
  191. //
  192. // CSapiIMX::_ProcessText
  193. //
  194. //+--------------------------------------------------------------------------+
  195. HRESULT CSapiIMX::_ProcessText(TfEditCookie ec, WCHAR *pwszText, LANGID langid, ITfContext *picCaller)
  196. {
  197. HRESULT hr = E_FAIL;
  198. ITfContext *pic = NULL;
  199. if (!picCaller)
  200. {
  201. GetFocusIC(&pic);
  202. }
  203. else
  204. {
  205. pic = picCaller;
  206. }
  207. hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE);
  208. if (picCaller == NULL)
  209. {
  210. SafeRelease(pic);
  211. }
  212. return hr;
  213. }
  214. //+--------------------------------------------------------------------------+
  215. //
  216. // CSapiIMX::_ProcessTextInternal
  217. //
  218. // common lower level routine for injecting text to docuement
  219. //
  220. //+--------------------------------------------------------------------------+
  221. HRESULT CSapiIMX::_ProcessTextInternal(TfEditCookie ec, WCHAR *pwszText, GUID input_attr, LANGID langid, ITfContext *pic, BOOL fPreserveResult, BOOL fSpelling)
  222. {
  223. HRESULT hr = E_FAIL;
  224. if (pic)
  225. {
  226. BOOL fPureCiceroIC;
  227. fPureCiceroIC = _IsPureCiceroIC(pic);
  228. CDocStatus ds(pic);
  229. if (ds.IsReadOnly())
  230. return S_OK;
  231. _fEditing = TRUE;
  232. // here we compare if the current selection is
  233. // equal to the saved IP range. If they are equal,
  234. // it means that the user has not moved the IP since
  235. // the first hypothesis arrived. In this case we'll
  236. // update the selection after injecting text, and
  237. // invalidate the saved IP.
  238. //
  239. BOOL fIPIsSelection = FALSE; // by default
  240. CComPtr<ITfRange> cpInsertionPoint;
  241. if ( cpInsertionPoint = GetSavedIP( ) )
  242. {
  243. // this is trying to determine
  244. // if the saved IP was on this context.
  245. // if not we just ignore that
  246. CComPtr<ITfContext> cpic;
  247. hr = cpInsertionPoint->GetContext(&cpic);
  248. if (S_OK != hr || cpic != pic)
  249. {
  250. cpInsertionPoint.Release();
  251. }
  252. }
  253. if (cpInsertionPoint)
  254. {
  255. CComPtr<ITfRange> cpSelection;
  256. hr = GetSelectionSimple(ec, pic, &cpSelection);
  257. if (SUCCEEDED(hr))
  258. {
  259. hr = cpSelection->IsEqualStart(ec, cpInsertionPoint, TF_ANCHOR_START, &fIPIsSelection);
  260. }
  261. }
  262. else
  263. {
  264. // if there is no saved IP, selection is an IP
  265. fIPIsSelection = TRUE;
  266. hr = GetSelectionSimple(ec, pic, &cpInsertionPoint);
  267. }
  268. if (hr == S_OK)
  269. {
  270. // finalize the previous input for now
  271. // if this is either feedback UI or alternate selection
  272. // no need to finalize
  273. //
  274. // Only for AIMM app or CUAS app,
  275. // finalize the previous dictated phrase here.
  276. //
  277. // For full Cicero aware app, it is better to finalize the composition
  278. // after this dictated text is injected to the document.
  279. //
  280. if (!fPureCiceroIC && !fPreserveResult
  281. && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT))
  282. {
  283. _FinalizePrevComp(ec, pic, cpInsertionPoint);
  284. }
  285. ITfProperty *pProp = NULL;
  286. // now inject text
  287. if (SUCCEEDED(hr))
  288. {
  289. // Just check with the app in case it wants to modify
  290. // the range.
  291. //
  292. BOOL fInsertOk;
  293. hr = cpInsertionPoint->AdjustForInsert(ec, wcslen(pwszText), &fInsertOk);
  294. if (S_OK == hr && fInsertOk)
  295. {
  296. // start a composition here if we haven't already
  297. _CheckStartComposition(ec, cpInsertionPoint);
  298. // protect the reco property while we modify the text
  299. // memo: we might want to preserve the original property instead
  300. // of the current property for LM lattice info We'll check
  301. // back this later (RTM)
  302. //
  303. m_fAcceptRecoResultTextUpdates = fPreserveResult;
  304. CRecoResultWrap *pRecoWrapOrg = NULL;
  305. ITfRange *pPropRange = NULL;
  306. ITfProperty *pProp_SAPIRESULT = NULL;
  307. if (fPreserveResult == TRUE)
  308. {
  309. if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &pProp_SAPIRESULT)))
  310. {
  311. // save out the result data
  312. //
  313. hr = _PreserveResult(ec, cpInsertionPoint, pProp_SAPIRESULT, &pRecoWrapOrg, &pPropRange);
  314. }
  315. if (S_OK != hr)
  316. pRecoWrapOrg = NULL;
  317. }
  318. if ( SUCCEEDED(hr) )
  319. {
  320. // set the text
  321. hr = cpInsertionPoint->SetText(ec, 0, pwszText, -1);
  322. // prwOrg holds a prop data if not NULL
  323. if (S_OK == hr && fPreserveResult == TRUE && pPropRange)
  324. {
  325. hr = _RestoreResult(ec, pPropRange, pProp_SAPIRESULT, pRecoWrapOrg);
  326. }
  327. }
  328. SafeReleaseClear(pPropRange);
  329. SafeReleaseClear(pProp_SAPIRESULT);
  330. SafeRelease(pRecoWrapOrg);
  331. m_fAcceptRecoResultTextUpdates = FALSE;
  332. }
  333. }
  334. //
  335. // set attribute range, use custom prop to mark speech text
  336. //
  337. if (SUCCEEDED(hr))
  338. {
  339. if (IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT))
  340. {
  341. hr = pic->GetProperty(GUID_PROP_SAPI_DISPATTR, &pProp);
  342. }
  343. else
  344. {
  345. hr = pic->GetProperty(GUID_PROP_ATTRIBUTE, &pProp);
  346. }
  347. }
  348. ITfRange *pAttrRange = NULL;
  349. if (S_OK == hr)
  350. {
  351. // when we insert feedback UI text, we expect the prop
  352. // range (attribute range) to be extended.
  353. // if we are attaching our custom GUID_PROP_SAPI_DISPATTR
  354. // property, we'll attach it phrase by phrase
  355. //
  356. if (!IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT))
  357. {
  358. hr = _FindPropRange(ec, pProp, cpInsertionPoint,
  359. &pAttrRange, input_attr, TRUE);
  360. }
  361. //
  362. // findproprange can return S_FALSE when there's no property yet
  363. //
  364. if (SUCCEEDED(hr) && !pAttrRange)
  365. {
  366. hr = cpInsertionPoint->Clone(&pAttrRange);
  367. }
  368. }
  369. if (S_OK == hr && pAttrRange)
  370. {
  371. SetGUIDPropertyData(&_libTLS, ec, pProp, pAttrRange, input_attr);
  372. }
  373. //
  374. // one more prop stuff for text owner id to fix
  375. // problem with Japanese spelling
  376. //
  377. if (S_OK == hr && fSpelling && !_MasterLMEnabled())
  378. {
  379. CComPtr<ITfProperty> cpPropTextOwner;
  380. hr = pic->GetProperty(GUID_PROP_TEXTOWNER, &cpPropTextOwner);
  381. if (S_OK == hr)
  382. {
  383. SetGUIDPropertyData(&_libTLS, ec, cpPropTextOwner, pAttrRange, GUID_NULL);
  384. }
  385. }
  386. SafeRelease(pAttrRange);
  387. SafeRelease(pProp);
  388. //
  389. // setup langid property
  390. //
  391. _SetLangID(ec, pic, cpInsertionPoint, langid);
  392. // move the caret
  393. if (fIPIsSelection)
  394. {
  395. cpInsertionPoint->Collapse(ec, TF_ANCHOR_END);
  396. SetSelectionSimple(ec, pic, cpInsertionPoint);
  397. }
  398. // Finalize the composition object here for Cicero aware app.
  399. if ((hr == S_OK) && fPureCiceroIC
  400. && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT))
  401. {
  402. _KillFocusRange(ec, pic, NULL, _tid);
  403. }
  404. }
  405. // If candidate UI is open, we need to close it now.
  406. CloseCandUI( );
  407. _fEditing = FALSE;
  408. }
  409. // Finally, notify stage process if we are the stage speech tip instance.
  410. if (m_fStageTip && IsEqualGUID(input_attr, GUID_ATTR_SAPI_INPUT) && fPreserveResult == FALSE)
  411. {
  412. SetCompartmentDWORD(_tid, _tim, GUID_COMPARTMENT_SPEECH_STAGEDICTATION, 1, FALSE);
  413. }
  414. return hr;
  415. }
  416. //
  417. // _ProcessSpelledText
  418. //
  419. // Call back function for edit session ESCB_INJECT_SPELL_TEXT
  420. //
  421. //
  422. HRESULT CSapiIMX::_ProcessSpelledText(TfEditCookie ec, ITfContext *pic, WCHAR *pwszText, LANGID langid, BOOL fOwnerId)
  423. {
  424. HRESULT hr = S_OK;
  425. CComPtr<ITfRange> cpTextRange;
  426. CComPtr<ITfRange> cpCurIP;
  427. if ( !pic || !pwszText )
  428. return E_INVALIDARG;
  429. // Keep the current range.
  430. cpCurIP = GetSavedIP( );
  431. if ( !cpCurIP )
  432. GetSelectionSimple(ec, pic, &cpCurIP);
  433. // We want to clone current ip so that it would not be changed
  434. // after _ProcessTextInternal( ) is called.
  435. //
  436. if ( cpCurIP )
  437. cpCurIP->Clone(&cpTextRange);
  438. if ( !cpTextRange ) return E_FAIL;
  439. // check if the current selection or empty ip is inside a middle of English word. for English only.
  440. BOOL fStartAnchorInMidWord = FALSE; // Check if the start anchor of pTextRange is in a middle of word
  441. BOOL fEndAnchorInMidWord = FALSE; // Check if the end anchor of pTextRange is in a middle of word.
  442. // When fStartAnchorInMidWord is TRUE, we don't add extra space between this text range and the previous range.
  443. // When fEndAnchoInMidWord is TRUE, we remove the trailing space in this text range.
  444. if ( langid == 0x0409 )
  445. {
  446. WCHAR szSurrounding[3]=L" ";
  447. LONG cch;
  448. CComPtr<ITfRange> cpClonedRange;
  449. hr = cpTextRange->Clone(&cpClonedRange);
  450. if ( hr == S_OK )
  451. hr = cpClonedRange->Collapse(ec, TF_ANCHOR_START);
  452. if ( hr == S_OK )
  453. hr = cpClonedRange->ShiftStart(ec, -1, &cch, NULL);
  454. if (hr == S_OK && cch != 0)
  455. hr = cpClonedRange->ShiftEnd(ec, 1, &cch, NULL);
  456. if ( hr == S_OK && cch != 0 )
  457. hr = cpClonedRange->GetText(ec, 0, szSurrounding, 2, (ULONG *)&cch);
  458. if ( hr == S_OK && cch > 0 )
  459. {
  460. if ( iswalnum(szSurrounding[0]) && iswalnum(szSurrounding[1]))
  461. fStartAnchorInMidWord = TRUE;
  462. }
  463. szSurrounding[0] = szSurrounding[1]=L' ';
  464. cpClonedRange.Release( );
  465. hr = cpTextRange->Clone(&cpClonedRange);
  466. if ( hr == S_OK )
  467. hr = cpClonedRange->Collapse(ec, TF_ANCHOR_END);
  468. if ( hr == S_OK )
  469. hr = cpClonedRange->ShiftStart(ec, -1, &cch, NULL);
  470. if (hr == S_OK && cch != 0)
  471. hr = cpClonedRange->ShiftEnd(ec, 1, &cch, NULL);
  472. if ( hr == S_OK && cch != 0 )
  473. hr = cpClonedRange->GetText(ec, 0, szSurrounding, 2, (ULONG *)&cch);
  474. if ( hr == S_OK && cch == 2 )
  475. {
  476. if ( iswalnum(szSurrounding[0]) && iswalnum(szSurrounding[1]) )
  477. fEndAnchorInMidWord = TRUE;
  478. }
  479. }
  480. // Inject text with or without owner id according to fOwnerId parameter
  481. // this is a final text injection, we don't want to preserve the speech property data
  482. // possible divided or shrinked by this text injection.
  483. //
  484. hr = _ProcessTextInternal(ec, pwszText, GUID_ATTR_SAPI_INPUT, langid, pic, FALSE, !fOwnerId);
  485. if ( hr == S_OK && !fOwnerId)
  486. {
  487. BOOL fConsumeLeadSpaces = FALSE;
  488. ULONG ulNumTrailSpace = 0;
  489. if ( iswcntrl(pwszText[0]) || iswpunct(pwszText[0]) )
  490. fConsumeLeadSpaces = TRUE;
  491. for ( LONG i=wcslen(pwszText)-1; i > 0; i-- )
  492. {
  493. if ( pwszText[i] == L' ' )
  494. ulNumTrailSpace++;
  495. else
  496. break;
  497. }
  498. hr = _ProcessSpaces(ec, pic, cpTextRange, fConsumeLeadSpaces, ulNumTrailSpace, langid, fStartAnchorInMidWord, fEndAnchorInMidWord);
  499. }
  500. return hr;
  501. }
  502. //
  503. // Handle the spaces after the recogized text is injected to the document.
  504. //
  505. // The handling includes below cases:
  506. //
  507. // Consume the leading spaces.
  508. // Remove the possible spaces after the injected text. English Only
  509. // Add a space before the injected text if necessary. English Only.
  510. //
  511. HRESULT CSapiIMX::HandleSpaces(ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements, ITfRange *pTextRange, LANGID langid)
  512. {
  513. HRESULT hr = E_FAIL;
  514. if (pResult && pTextRange)
  515. {
  516. BOOL fConsumeLeadSpaces = FALSE;
  517. ULONG ulNumTrailSpace = 0;
  518. SPPHRASE *pPhrase = NULL;
  519. // Check if the first element of the pResult wants to consume the leading spaces
  520. // (the trailing spaces in the last phrase) in the documenet.
  521. //
  522. // If the disp attrib bit is set as SPAF_CONSUME_LEADING_SPACES, means it wants to consume
  523. // all the leading spaces in the document.
  524. //
  525. hr = S_OK;
  526. if ( _NeedRemovingSpaceForPunctation( ) )
  527. {
  528. hr = pResult->GetPhrase(&pPhrase);
  529. if ( hr == S_OK )
  530. {
  531. ULONG cElements = 0;
  532. BYTE bDispAttr;
  533. cElements = pPhrase->Rule.ulCountOfElements;
  534. if ( cElements >= (ulStartElement + ulNumElements) )
  535. {
  536. bDispAttr = pPhrase->pElements[ulStartElement].bDisplayAttributes;
  537. if ( bDispAttr & SPAF_CONSUME_LEADING_SPACES )
  538. fConsumeLeadSpaces = TRUE;
  539. bDispAttr = pPhrase->pElements[ulStartElement + ulNumElements - 1].bDisplayAttributes;
  540. if ( bDispAttr & SPAF_ONE_TRAILING_SPACE )
  541. ulNumTrailSpace = 1;
  542. else if ( bDispAttr & SPAF_TWO_TRAILING_SPACES )
  543. ulNumTrailSpace = 2;
  544. }
  545. }
  546. if (pPhrase)
  547. CoTaskMemFree(pPhrase);
  548. }
  549. if ( hr == S_OK )
  550. {
  551. ESDATA esData;
  552. memset(&esData, 0, sizeof(ESDATA));
  553. esData.lData1 = (LONG_PTR)langid;
  554. esData.lData2 = (LONG_PTR)ulNumTrailSpace;
  555. esData.fBool = fConsumeLeadSpaces;
  556. esData.pRange = pTextRange;
  557. hr = _RequestEditSession(ESCB_HANDLESPACES, TF_ES_READWRITE, &esData);
  558. }
  559. }
  560. return hr;
  561. }
  562. //
  563. // CSapiIMX::AttachResult
  564. //
  565. // attaches the result object and keep it *alive*
  566. // until the property is discarded
  567. //
  568. HRESULT CSapiIMX::AttachResult(ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements)
  569. {
  570. HRESULT hr = E_FAIL;
  571. if (pResult)
  572. {
  573. ESDATA esData;
  574. memset(&esData, 0, sizeof(ESDATA));
  575. esData.lData1 = (LONG_PTR)ulStartElement;
  576. esData.lData2 = (LONG_PTR)ulNumElements;
  577. esData.pUnk = (IUnknown *)pResult;
  578. hr = _RequestEditSession(ESCB_ATTACHRECORESULTOBJ, TF_ES_READWRITE, &esData);
  579. }
  580. return hr;
  581. }
  582. //
  583. // CSapiIMX::_GetSpaceRangeBeyondText
  584. //
  585. // Get space range beyond the injected text in the document.
  586. // fBefore is TRUE, Get the space range to contain spaces between
  587. // the previous word and start anchor of the TextRange.
  588. // fBefore is FALSE,Get space range to contains spaces between
  589. // the end anchor of the TextRange and next word.
  590. //
  591. // pulNum receives the real space number.
  592. // pfRealTextBeyond indicates if there is real text before or after the text range.
  593. //
  594. // Caller is responsible to release *ppSpaceRange.
  595. //
  596. HRESULT CSapiIMX::_GetSpaceRangeBeyondText(TfEditCookie ec, ITfRange *pTextRange, BOOL fBefore, ITfRange **ppSpaceRange, BOOL *pfRealTextBeyond)
  597. {
  598. HRESULT hr = S_OK;
  599. CComPtr<ITfRange> cpSpaceRange;
  600. LONG cch;
  601. if ( !pTextRange || !ppSpaceRange )
  602. return E_INVALIDARG;
  603. *ppSpaceRange = NULL;
  604. if ( pfRealTextBeyond )
  605. *pfRealTextBeyond = FALSE;
  606. hr = pTextRange->Clone(&cpSpaceRange);
  607. if ( hr == S_OK )
  608. {
  609. hr = cpSpaceRange->Collapse(ec, fBefore ? TF_ANCHOR_START : TF_ANCHOR_END);
  610. }
  611. if ( hr == S_OK )
  612. {
  613. if ( fBefore )
  614. hr = cpSpaceRange->ShiftStart(ec, MAX_CHARS_FOR_BEYONDSPACE * (-1), &cch, NULL);
  615. else
  616. hr = cpSpaceRange->ShiftEnd(ec, MAX_CHARS_FOR_BEYONDSPACE, &cch, NULL);
  617. }
  618. if ( (hr == S_OK) && (cch != 0 ) )
  619. {
  620. // There are more characters beyond the text range.
  621. // Determine the real number of spaces in the guessed range.
  622. //
  623. // if fBefore TRUE, search the number from end to start anchor.
  624. // if fBefore FASLE, search the number from start to end anchor.
  625. WCHAR *pwsz = NULL;
  626. LONG lSize = cch;
  627. LONG lNumSpaces = 0;
  628. if (cch < 0)
  629. lSize = cch * (-1);
  630. pwsz = new WCHAR[lSize + 1];
  631. if ( pwsz )
  632. {
  633. hr = cpSpaceRange->GetText(ec, 0, pwsz, lSize, (ULONG *)&cch);
  634. if ( hr == S_OK)
  635. {
  636. pwsz[cch] = L'\0';
  637. // calculate the number of trailing or prefix spaces in this range.
  638. BOOL bSearchDone = FALSE;
  639. ULONG iStart;
  640. if ( fBefore )
  641. iStart = cch - 1; // Start from the end anchor to Start Anchor.
  642. else
  643. iStart = 0; // Start from Start Anchor to End Anchor.
  644. while ( !bSearchDone )
  645. {
  646. if ((pwsz[iStart] != L' ') && (pwsz[iStart] != L'\t'))
  647. {
  648. bSearchDone = TRUE;
  649. if ( pwsz[iStart] > 0x20 && pfRealTextBeyond)
  650. *pfRealTextBeyond = TRUE;
  651. break;
  652. }
  653. else
  654. lNumSpaces ++;
  655. if ( fBefore )
  656. {
  657. if ( (long)iStart <= 0 )
  658. bSearchDone = TRUE;
  659. else
  660. iStart --;
  661. }
  662. else
  663. {
  664. if ( iStart >= (ULONG)cch - 1 )
  665. bSearchDone = TRUE;
  666. else
  667. iStart ++;
  668. }
  669. }
  670. }
  671. delete[] pwsz;
  672. if ( (hr == S_OK) && (lNumSpaces > 0))
  673. {
  674. // Shift the range to cover only spaces.
  675. LONG NonSpaceNum;
  676. NonSpaceNum = cch - lNumSpaces;
  677. if ( fBefore )
  678. hr = cpSpaceRange->ShiftStart(ec, NonSpaceNum, &cch, NULL);
  679. else
  680. hr = cpSpaceRange->ShiftEnd(ec, NonSpaceNum * (-1), &cch, NULL);
  681. // Return this cpSpaceRange to the caller.
  682. // Caller is responsible for releasing this object.
  683. if ( hr == S_OK )
  684. hr = cpSpaceRange->Clone(ppSpaceRange);
  685. }
  686. }
  687. else
  688. hr = E_OUTOFMEMORY;
  689. }
  690. return hr;
  691. }
  692. //
  693. // CSapiIMX::_ProcessTrailingSpace
  694. //
  695. // If the next phrase wants to consume leading space,
  696. // we want to remove all the trailing spaces in this text range and the spaces
  697. // between this range and next text range.
  698. // This is for all languages.
  699. //
  700. HRESULT CSapiIMX::_ProcessTrailingSpace(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, ULONG ulNumTrailSpace)
  701. {
  702. HRESULT hr = S_OK;
  703. CComPtr<ITfRange> cpNextRange;
  704. CComPtr<ITfRange> cpSpaceRange; // Space Range between this range and next text range.
  705. BOOL fHasNextText = FALSE;
  706. CComPtr<ITfRange> cpTrailSpaceRange;
  707. LONG cch;
  708. if ( !pTextRange )
  709. return E_INVALIDARG;
  710. hr = pTextRange->Clone(&cpTrailSpaceRange);
  711. if (hr == S_OK)
  712. hr = cpTrailSpaceRange->Collapse(ec, TF_ANCHOR_END);
  713. // Generate the real Trailing Space Range
  714. if (hr == S_OK && ulNumTrailSpace > 0)
  715. hr = cpTrailSpaceRange->ShiftStart(ec, (LONG)ulNumTrailSpace * (-1), &cch, NULL);
  716. // Get the spaces between this range and possible next text range.
  717. if ( hr == S_OK )
  718. hr = _GetSpaceRangeBeyondText(ec, pTextRange, FALSE, &cpSpaceRange);
  719. // if we found the space range, the trailing space range should also include this range.
  720. if ( hr == S_OK && cpSpaceRange )
  721. hr = cpTrailSpaceRange->ShiftEndToRange(ec, cpSpaceRange, TF_ANCHOR_END);
  722. // Determine if there is Next Text range after this cpTrailSpaceRange.
  723. if (hr == S_OK)
  724. {
  725. hr = cpTrailSpaceRange->Clone(&cpNextRange);
  726. if ( hr == S_OK )
  727. hr = cpNextRange->Collapse(ec, TF_ANCHOR_END);
  728. cch = 0;
  729. if ( hr == S_OK )
  730. hr = cpNextRange->ShiftEnd(ec, 1, &cch, NULL);
  731. if ( hr == S_OK && cch != 0 )
  732. fHasNextText = TRUE;
  733. }
  734. if (hr == S_OK && fHasNextText && cpNextRange)
  735. {
  736. BOOL fNextRangeConsumeSpace = FALSE;
  737. BOOL fAddOneSpace = FALSE; // this is only for Hyphen handling,
  738. // if it is TRUE, a trailing space is required
  739. // to append.
  740. // so that new text could be like A - B.
  741. WCHAR wszText[4];
  742. hr = cpNextRange->GetText(ec, 0, wszText, 1, (ULONG *)&cch);
  743. if ((hr == S_OK) && ( iswcntrl(wszText[0]) || iswpunct(wszText[0]) ))
  744. {
  745. // if the character is a control character or punctuation character,
  746. // it means it want to consume the previous spaces.
  747. fNextRangeConsumeSpace = TRUE;
  748. if ((wszText[0] == L'-') || (wszText[0] == 0x2013)) // Specially handle Hyphen character.
  749. {
  750. // If the next text is "-xxx", there should be no space between
  751. // this range and next range.
  752. // If the next text is "- xxx", there should be a space between
  753. // this range and next range, the text would be: "nnn - xxx"
  754. HRESULT hret;
  755. hret = cpNextRange->ShiftEnd(ec, 1, &cch, NULL);
  756. if ( hret == S_OK && cch > 0 )
  757. {
  758. hret = cpNextRange->GetText(ec, 0, wszText, 2, (ULONG *)&cch);
  759. if ( hret == S_OK && cch == 2 && wszText[1] == L' ')
  760. fAddOneSpace = TRUE;
  761. }
  762. }
  763. }
  764. if ( fNextRangeConsumeSpace )
  765. {
  766. _CheckStartComposition(ec, cpTrailSpaceRange);
  767. if ( !fAddOneSpace )
  768. hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0);
  769. else
  770. hr = cpTrailSpaceRange->SetText(ec, 0, L" ", 1);
  771. }
  772. }
  773. return hr;
  774. }
  775. //
  776. // CSapiIMX::_ProcessLeadingSpaces
  777. //
  778. // If this phrase wants to consume leading spaces, all the spaces before this phrase
  779. // must be removed.
  780. // if the phrase doesn't want to consume leading spaces, and there is no space between
  781. // this phrase and previous phrase for English case, leading space is required to add
  782. // between these two phrases.
  783. //
  784. HRESULT CSapiIMX::_ProcessLeadingSpaces(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, BOOL fConsumeLeadSpaces, LANGID langid, BOOL fStartInMidWord)
  785. {
  786. HRESULT hr = S_OK;
  787. if (!pTextRange || !pic)
  788. return E_INVALIDARG;
  789. // Handle Consuming leading Spaces.
  790. if (fConsumeLeadSpaces )
  791. {
  792. CComPtr<ITfRange> cpLeadSpaceRange;
  793. hr = _GetSpaceRangeBeyondText(ec, pTextRange, TRUE, &cpLeadSpaceRange);
  794. if ( hr == S_OK && cpLeadSpaceRange )
  795. {
  796. // Kill all the trailing spaces in the range.
  797. // start a composition here if we haven't already
  798. _CheckStartComposition(ec, cpLeadSpaceRange);
  799. hr = cpLeadSpaceRange->SetText(ec, 0, NULL, 0);
  800. }
  801. }
  802. // Specially handle some other space cases for English
  803. if ((hr == S_OK) && (langid == 0x0409))
  804. {
  805. // If this phrase doesn't consume the leading space, and
  806. // there is no any spaces between this text range and a real previous text word.
  807. // we need to add one space here.
  808. // if this is a spelled text, and the start anchor of selection or ip is inside
  809. // of a word, don't add extra leading space.
  810. //
  811. if ( hr == S_OK && !fConsumeLeadSpaces && !fStartInMidWord)
  812. {
  813. CComPtr<ITfRange> cpLeadSpaceRange;
  814. BOOL fRealTextInPreviousWord = FALSE;
  815. hr = _GetSpaceRangeBeyondText(ec, pTextRange, TRUE, &cpLeadSpaceRange,&fRealTextInPreviousWord);
  816. if ( hr == S_OK && !cpLeadSpaceRange && fRealTextInPreviousWord )
  817. {
  818. //
  819. // Specially handle the hyphen case for bug 468907
  820. //
  821. // if the previous text is "x-", this text is "y",
  822. // the final text should be like "x-y".
  823. // we should not add one space in this case.
  824. //
  825. // if the previous text is "x -", the final text would be "x - y"
  826. // the extra space is necessary.
  827. BOOL fAddExtraSpace = TRUE;
  828. CComPtr<ITfRange> cpPrevTextRange;
  829. WCHAR wszTrailTextInPrevRange[3];
  830. LONG cch;
  831. // Since previous text range does exist, ( fRealTextInPreviousWord is TRUE).
  832. // and there is no space between this range and previous range.
  833. // we can just rely on pTextRange to shift to previous range and get
  834. // its trail characters. ( last two characters, saved in wszTrailTextInPrevRange).
  835. hr = pTextRange->Clone(&cpPrevTextRange);
  836. if ( hr == S_OK )
  837. hr = cpPrevTextRange->Collapse(ec, TF_ANCHOR_START);
  838. if ( hr == S_OK )
  839. hr = cpPrevTextRange->ShiftStart(ec, -2, &cch, NULL);
  840. if ( hr == S_OK )
  841. hr = cpPrevTextRange->GetText(ec, 0, wszTrailTextInPrevRange, 2, (ULONG *)&cch);
  842. if ( hr == S_OK && cch == 2 )
  843. {
  844. if ( (wszTrailTextInPrevRange[0] != L' ') &&
  845. ((wszTrailTextInPrevRange[1] == L'-') || (wszTrailTextInPrevRange[1] == 0x2013)) )
  846. fAddExtraSpace = FALSE;
  847. }
  848. if ( fAddExtraSpace )
  849. {
  850. hr = pTextRange->Clone(&cpLeadSpaceRange);
  851. if ( hr == S_OK )
  852. hr = cpLeadSpaceRange->Collapse(ec, TF_ANCHOR_START);
  853. if ( hr == S_OK )
  854. {
  855. // Insert one Space to this new empty range.
  856. _CheckStartComposition(ec, cpLeadSpaceRange);
  857. hr = cpLeadSpaceRange->SetText(ec, 0, L" ", 1);
  858. }
  859. }
  860. }
  861. }
  862. }
  863. return hr;
  864. }
  865. //
  866. // CSapiIMX::_ProcessSpaces
  867. //
  868. // Edit session callback function for ESCB_HANDLESPACES.
  869. //
  870. //
  871. HRESULT CSapiIMX::_ProcessSpaces(TfEditCookie ec, ITfContext *pic, ITfRange *pTextRange, BOOL fConsumeLeadSpaces, ULONG ulNumTrailSpace, LANGID langid, BOOL fStartInMidWord, BOOL fEndInMidWord )
  872. {
  873. HRESULT hr = S_OK;
  874. if (!pTextRange || !pic)
  875. return E_INVALIDARG;
  876. hr = _ProcessLeadingSpaces(ec, pic, pTextRange,fConsumeLeadSpaces, langid, fStartInMidWord);
  877. // Specially handle some other space cases for English
  878. if ((hr == S_OK) && (langid == 0x0409))
  879. {
  880. // Remove all the unnecessary spaces between this text range and next word.
  881. CComPtr<ITfRange> cpTrailSpaceRange;
  882. hr = _GetSpaceRangeBeyondText(ec, pTextRange, FALSE, &cpTrailSpaceRange);
  883. if ( hr == S_OK && cpTrailSpaceRange )
  884. {
  885. _CheckStartComposition(ec, cpTrailSpaceRange);
  886. hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0);
  887. }
  888. }
  889. if ( (hr == S_OK) && fEndInMidWord )
  890. {
  891. // This is spelled text.
  892. // EndAnchor is in middle of a word.
  893. // we just want to remove the trail spaces injected in this text range.
  894. if ( ulNumTrailSpace )
  895. {
  896. CComPtr<ITfRange> cpTrailSpaceRange;
  897. LONG cch;
  898. hr = pTextRange->Clone(&cpTrailSpaceRange);
  899. if (hr == S_OK)
  900. hr = cpTrailSpaceRange->Collapse(ec, TF_ANCHOR_END);
  901. // Generate the real Trailing Space Range
  902. if (hr == S_OK)
  903. hr = cpTrailSpaceRange->ShiftStart(ec, (LONG)ulNumTrailSpace * (-1), &cch, NULL);
  904. if ( hr == S_OK && cch != 0 )
  905. {
  906. // Remove the spaces.
  907. _CheckStartComposition(ec, cpTrailSpaceRange);
  908. hr = cpTrailSpaceRange->SetText(ec, 0, NULL, 0);
  909. }
  910. if ( hr == S_OK )
  911. ulNumTrailSpace = 0;
  912. }
  913. }
  914. // If the next phrase wants to consume leading space,
  915. // we want to remove all the trailing spaces in this text range.
  916. // This is for all languages.
  917. if ( hr == S_OK )
  918. hr = _ProcessTrailingSpace(ec, pic, pTextRange, ulNumTrailSpace);
  919. return hr;
  920. }
  921. //
  922. // CSapiIMX::_ProcessRecoObject
  923. //
  924. //
  925. HRESULT CSapiIMX::_ProcessRecoObject(TfEditCookie ec, ISpRecoResult *pResult, ULONG ulStartElement, ULONG ulNumElements)
  926. {
  927. HRESULT hr;
  928. ITfContext *pic;
  929. CComPtr<ITfRange> cpInsertionPoint;
  930. if (!GetFocusIC(&pic))
  931. {
  932. return E_OUTOFMEMORY;
  933. }
  934. _fEditing = TRUE;
  935. if (cpInsertionPoint = GetSavedIP())
  936. {
  937. // this is trying to determine
  938. // if the saved IP was on this context.
  939. // if not we just ignore that
  940. CComPtr<ITfContext> cpic;
  941. hr = cpInsertionPoint->GetContext(&cpic);
  942. if (S_OK != hr || cpic != pic)
  943. {
  944. cpInsertionPoint.Release();
  945. }
  946. }
  947. // find range to attach property
  948. if (!cpInsertionPoint)
  949. {
  950. CComPtr<ITfRange> cpSelection;
  951. if (GetSelectionSimple(ec, pic, &cpSelection) == S_OK)
  952. {
  953. cpInsertionPoint = cpSelection; // comptr addrefs
  954. }
  955. }
  956. if (cpInsertionPoint)
  957. {
  958. CComPtr<ITfRange> cpRange;
  959. BOOL fPrSize = _FindPrevComp(ec, pic, cpInsertionPoint, &cpRange, GUID_ATTR_SAPI_INPUT);
  960. if (!fPrSize)
  961. {
  962. hr = E_FAIL; // we may need to assert here?
  963. goto pr_exit;
  964. }
  965. CComPtr<ITfProperty> cpProp;
  966. if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_SAPIRESULTOBJECT, &cpProp)))
  967. {
  968. CComPtr<ITfPropertyStore> cpResultStore;
  969. CPropStoreRecoResultObject *prps = new CPropStoreRecoResultObject(this, cpRange);
  970. if (!prps)
  971. {
  972. hr = E_OUTOFMEMORY;
  973. goto pr_exit;
  974. }
  975. // determine whether this partial result has an ITN
  976. SPPHRASE *pPhrase;
  977. ULONG ulNumOfITN = 0;
  978. hr = pResult->GetPhrase(&pPhrase);
  979. if (S_OK == hr)
  980. {
  981. const SPPHRASEREPLACEMENT *pRep = pPhrase->pReplacements;
  982. for (ULONG ul = 0; ul < pPhrase->cReplacements; ul++)
  983. {
  984. // review: we need to verify if this is really a correct way to determine
  985. // whether the ITN fits in the partial result
  986. //
  987. if (pRep->ulFirstElement >= ulStartElement
  988. && (pRep->ulFirstElement + pRep->ulCountOfElements) <= (ulStartElement + ulNumElements))
  989. {
  990. ulNumOfITN ++;
  991. }
  992. pRep++;
  993. }
  994. ::CoTaskMemFree(pPhrase);
  995. }
  996. CRecoResultWrap *prw = new CRecoResultWrap(this, ulStartElement, ulNumElements, ulNumOfITN);
  997. if (prw)
  998. {
  999. hr = prw->Init(pResult);
  1000. }
  1001. else
  1002. hr = E_OUTOFMEMORY;
  1003. // set up the result data
  1004. if (S_OK == hr)
  1005. {
  1006. // save text
  1007. CComPtr<ITfRange> cpRangeTemp;
  1008. hr = cpRange->Clone(&cpRangeTemp);
  1009. if (S_OK == hr)
  1010. {
  1011. long cch;
  1012. TF_HALTCOND hc;
  1013. WCHAR *psz;
  1014. hc.pHaltRange = cpRange;
  1015. hc.aHaltPos = TF_ANCHOR_END;
  1016. hc.dwFlags = 0;
  1017. cpRangeTemp->ShiftStart(ec, LONG_MAX, &cch, &hc);
  1018. psz = new WCHAR[cch+1];
  1019. if (psz)
  1020. {
  1021. if ( S_OK == cpRange->GetText(ec, 0, psz, cch, (ULONG *)&cch))
  1022. {
  1023. prw->m_bstrCurrentText = SysAllocString(psz);
  1024. delete[] psz;
  1025. }
  1026. }
  1027. }
  1028. // Init the ITN Show State list in reco wrapper.
  1029. prw->_InitITNShowState(TRUE, 0, 0);
  1030. hr = prps->_InitFromResultWrap(prw); // this addref's
  1031. }
  1032. // get ITfPropertyStore interface
  1033. if (SUCCEEDED(hr))
  1034. {
  1035. hr = prps->QueryInterface(IID_ITfPropertyStore, (void **)&cpResultStore);
  1036. SafeRelease(prps);
  1037. }
  1038. // set the property store for this range property
  1039. if (hr == S_OK)
  1040. {
  1041. hr = cpProp->SetValueStore(ec, cpRange, cpResultStore);
  1042. }
  1043. if (_MasterLMEnabled())
  1044. {
  1045. // set up the LM lattice store, only if reco result is given
  1046. //
  1047. CComPtr<ITfProperty> cpLMProp;
  1048. if ( S_OK == hr &&
  1049. SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LMLATTICE, &cpLMProp)))
  1050. {
  1051. CPropStoreLMLattice *prpsLMLattice = new CPropStoreLMLattice(this);
  1052. CComPtr<ITfPropertyStore> cpLatticeStore;
  1053. if (prpsLMLattice && prw)
  1054. {
  1055. hr = prpsLMLattice->_InitFromResultWrap(prw);
  1056. }
  1057. else
  1058. hr = E_OUTOFMEMORY;
  1059. if (S_OK == hr)
  1060. {
  1061. hr = prpsLMLattice->QueryInterface(IID_ITfPropertyStore, (void **)&cpLatticeStore);
  1062. }
  1063. if (S_OK == hr)
  1064. {
  1065. hr = cpLMProp->SetValueStore(ec, cpRange, cpLatticeStore);
  1066. }
  1067. SafeRelease(prpsLMLattice);
  1068. }
  1069. }
  1070. SafeRelease(prw);
  1071. }
  1072. }
  1073. pr_exit:
  1074. _fEditing = FALSE;
  1075. pic->Release();
  1076. return hr;
  1077. }
  1078. HRESULT CSapiIMX::_PreserveResult(TfEditCookie ec, ITfRange *pRange, ITfProperty *pProp, CRecoResultWrap **ppRecoWrap, ITfRange **ppPropRange)
  1079. {
  1080. HRESULT hr;
  1081. ITfRange *pPropRange;
  1082. CComPtr<IUnknown> cpunk;
  1083. Assert(ppPropRange);
  1084. hr = pProp->FindRange(ec, pRange, &pPropRange, TF_ANCHOR_START);
  1085. // retrieve the result data and addref it
  1086. //
  1087. if (SUCCEEDED(hr) && pPropRange)
  1088. {
  1089. hr = GetUnknownPropertyData(ec, pProp, pPropRange, &cpunk);
  1090. *ppPropRange = pPropRange;
  1091. // would be released at the caller
  1092. // pPropRange->Release();
  1093. // get the result object, cpunk points to our wrapper object
  1094. CComPtr<IServiceProvider> cpServicePrv;
  1095. CComPtr<ISpRecoResult> cpResult;
  1096. CRecoResultWrap *pRecoWrapOrg = NULL;
  1097. if (S_OK == hr)
  1098. {
  1099. hr = cpunk->QueryInterface(IID_IServiceProvider, (void **)&cpServicePrv);
  1100. }
  1101. // get result object
  1102. if (S_OK == hr)
  1103. {
  1104. hr = cpServicePrv->QueryService(GUID_NULL, IID_ISpRecoResult, (void **)&cpResult);
  1105. }
  1106. if (S_OK == hr)
  1107. {
  1108. hr = cpunk->QueryInterface(IID_PRIV_RESULTWRAP, (void **)&pRecoWrapOrg);
  1109. }
  1110. // Now Create a new RecoResult Wrapper based on the org wrapper's data.
  1111. // Clone a new RecoWrapper.
  1112. if ( S_OK == hr )
  1113. {
  1114. CRecoResultWrap *pRecoWrapNew = NULL;
  1115. ULONG ulStartElement, ulNumElements, ulNumOfITN;
  1116. ulStartElement = pRecoWrapOrg->GetStart( );
  1117. ulNumElements = pRecoWrapOrg->GetNumElements( );
  1118. ulNumOfITN = pRecoWrapOrg->m_ulNumOfITN;
  1119. pRecoWrapNew = new CRecoResultWrap(this, ulStartElement, ulNumElements, ulNumOfITN);
  1120. if ( pRecoWrapNew )
  1121. {
  1122. // Init from RecoResult SR object
  1123. hr = pRecoWrapNew->Init(cpResult);
  1124. if ( S_OK == hr )
  1125. {
  1126. pRecoWrapNew->SetOffsetDelta( pRecoWrapOrg->_GetOffsetDelta( ) );
  1127. pRecoWrapNew->SetCharsInTrail( pRecoWrapOrg->GetCharsInTrail( ) );
  1128. pRecoWrapNew->SetTrailSpaceRemoved( pRecoWrapOrg->GetTrailSpaceRemoved( ) );
  1129. pRecoWrapNew->m_bstrCurrentText = SysAllocString((WCHAR *)pRecoWrapOrg->m_bstrCurrentText);
  1130. // Update ITN show-state list .
  1131. if ( ulNumOfITN > 0 )
  1132. {
  1133. SPITNSHOWSTATE *pITNShowStateOrg;
  1134. for ( ULONG iIndex=0; iIndex<ulNumOfITN; iIndex ++ )
  1135. {
  1136. pITNShowStateOrg = pRecoWrapOrg->m_rgITNShowState.GetPtr(iIndex);
  1137. if ( pITNShowStateOrg)
  1138. {
  1139. ULONG ulITNStart;
  1140. ULONG ulITNNumElem;
  1141. BOOL fITNShown;
  1142. ulITNStart = pITNShowStateOrg->ulITNStart;
  1143. ulITNNumElem = pITNShowStateOrg->ulITNNumElem;
  1144. fITNShown = pITNShowStateOrg->fITNShown;
  1145. pRecoWrapNew->_InitITNShowState(fITNShown, ulITNStart, ulITNNumElem );
  1146. }
  1147. } // for
  1148. } // if
  1149. // Update Offset List
  1150. if ( pRecoWrapOrg->IsElementOffsetIntialized( ) )
  1151. {
  1152. ULONG ulOffsetNum;
  1153. ULONG i;
  1154. ULONG ulOffset;
  1155. ulOffsetNum = pRecoWrapOrg->GetNumElements( ) + 1;
  1156. for ( i=0; i < ulOffsetNum; i ++ )
  1157. {
  1158. ulOffset = pRecoWrapOrg->_GetElementOffsetCch(ulStartElement + i );
  1159. pRecoWrapNew->_SetElementNewOffset(ulStartElement + i, ulOffset);
  1160. }
  1161. }
  1162. }
  1163. SafeRelease(pRecoWrapOrg);
  1164. if ( ppRecoWrap )
  1165. *ppRecoWrap = pRecoWrapNew;
  1166. }
  1167. else
  1168. hr = E_OUTOFMEMORY;
  1169. }
  1170. }
  1171. return hr;
  1172. }
  1173. HRESULT CSapiIMX::_RestoreResult(TfEditCookie ec, ITfRange *pPropRange, ITfProperty *pProp, CRecoResultWrap *pRecoWrap)
  1174. {
  1175. Assert(m_pCSpTask);
  1176. CPropStoreRecoResultObject *prps = new CPropStoreRecoResultObject(this, pPropRange);
  1177. HRESULT hr;
  1178. if (prps)
  1179. {
  1180. ITfPropertyStore *pps;
  1181. // restore the result object
  1182. prps->_InitFromResultWrap(pRecoWrap);
  1183. // get ITfPropertyStore interface
  1184. hr = prps->QueryInterface(IID_ITfPropertyStore, (void **)&pps);
  1185. prps->Release();
  1186. // re-set the property store for this range property
  1187. if (hr == S_OK)
  1188. {
  1189. hr = pProp->SetValueStore(ec, pPropRange, pps);
  1190. pps->Release();
  1191. }
  1192. }
  1193. else
  1194. hr = E_OUTOFMEMORY;
  1195. return hr;
  1196. }
  1197. HRESULT CSapiIMX::_FinalizePrevComp(TfEditCookie ec, ITfContext *pic, ITfRange *pRange)
  1198. //
  1199. // the following code assumes single IP with no simaltanious SR going on
  1200. // we always remove the feedback UI and focus range everytime we receive
  1201. // SR result - mainly for demonstration purpose
  1202. //
  1203. {
  1204. // kill the Feedback UI for the entire document
  1205. HRESULT hr = _KillFeedbackUI(ec, pic, NULL);
  1206. // also clear the focus range and its display attribute
  1207. if (SUCCEEDED(hr))
  1208. {
  1209. hr = _KillFocusRange(ec, pic, NULL, _tid);
  1210. }
  1211. return hr;
  1212. }
  1213. //
  1214. // bogus: very similar to Finalize prev comp. Consolidate this!
  1215. //
  1216. //
  1217. BOOL CSapiIMX::_FindPrevComp(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, ITfRange **ppRangeOut, GUID input_attr)
  1218. {
  1219. HRESULT hr = E_FAIL;
  1220. ITfRange *pRangeTmp;
  1221. LONG l;
  1222. BOOL fEmpty;
  1223. BOOL fRet = FALSE;
  1224. // usual stuff
  1225. pRange->Clone(&pRangeTmp);
  1226. // set size to 0
  1227. pRangeTmp->Collapse(ec, TF_ANCHOR_START);
  1228. // shift to the previous position
  1229. pRangeTmp->ShiftStart(ec, -1, &l, NULL);
  1230. ITfRange *pAttrRange;
  1231. ITfProperty *pProp = NULL;
  1232. if (SUCCEEDED(pic->GetProperty(GUID_PROP_SAPI_DISPATTR, &pProp)))
  1233. {
  1234. hr = _FindPropRange(ec, pProp, pRangeTmp, &pAttrRange, input_attr);
  1235. if (S_OK == hr && pAttrRange)
  1236. {
  1237. TfGuidAtom attr;
  1238. if (SUCCEEDED(GetGUIDPropertyData(ec, pProp, pAttrRange, &attr)))
  1239. {
  1240. if (IsEqualTFGUIDATOM(&_libTLS, attr, input_attr))
  1241. {
  1242. hr = pAttrRange->Clone(ppRangeOut);
  1243. }
  1244. }
  1245. pAttrRange->Release();
  1246. }
  1247. pProp->Release();
  1248. }
  1249. pRangeTmp->Release();
  1250. if (SUCCEEDED(hr) && *ppRangeOut)
  1251. {
  1252. (*ppRangeOut)->IsEmpty(ec, &fEmpty);
  1253. fRet = !fEmpty;
  1254. }
  1255. return fRet;
  1256. }
  1257. //
  1258. // CSapiIMX::_SetLangID
  1259. //
  1260. // synopsis - set langid for the given text range
  1261. //
  1262. HRESULT CSapiIMX::_SetLangID(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, LANGID langid)
  1263. {
  1264. BOOL fEmpty;
  1265. HRESULT hr = E_FAIL;
  1266. pRange->IsEmpty(ec, &fEmpty);
  1267. if (!fEmpty)
  1268. {
  1269. //
  1270. // make langid prop
  1271. //
  1272. ITfProperty *pProp = NULL;
  1273. // set the attrib info
  1274. if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LANGID, &pProp)))
  1275. {
  1276. hr = SetLangIdPropertyData(ec, pProp, pRange, langid);
  1277. pProp->Release();
  1278. }
  1279. }
  1280. return hr;
  1281. }
  1282. //
  1283. // CSapiIMX::_FindPropRange
  1284. //
  1285. //
  1286. HRESULT CSapiIMX::_FindPropRange(TfEditCookie ec, ITfProperty *pProp, ITfRange *pRange, ITfRange **ppAttrRange, GUID input_attr, BOOL fExtend)
  1287. {
  1288. // set the attrib info
  1289. ITfRange *pAttrRange = NULL;
  1290. ITfRange *pRangeTmp;
  1291. TfGuidAtom guidAttr = TF_INVALID_GUIDATOM;
  1292. HRESULT hr;
  1293. // LONG l;
  1294. // set the attrib info
  1295. pRange->Clone(&pRangeTmp);
  1296. // There is no need to shiftstart to the left again when this function is called.
  1297. // This function is called in two different places, one in FindPrevComp( ) and the
  1298. // other is in ProcessTextInternal( ). FindPrevComp( ) has already shift the start
  1299. // anchor to left by 1 already, we don't want to shift again, otherwise, if the phrase
  1300. // contains only one char, it will not find the right prev-composition string.
  1301. // In function ProcessTextInternal( ), shift start anchor to left is not really required.
  1302. //
  1303. // Remove the below two lines will fix cicero bug 3646 & 3649
  1304. //
  1305. // pRangeTmp->Collapse(ec, TF_ANCHOR_START);
  1306. // pRangeTmp->ShiftStart(ec, -1, &l, NULL);
  1307. hr = pProp->FindRange(ec, pRangeTmp, &pAttrRange, TF_ANCHOR_START);
  1308. if (S_OK == hr && pAttrRange)
  1309. {
  1310. hr = GetGUIDPropertyData(ec, pProp, pAttrRange, &guidAttr);
  1311. }
  1312. if (SUCCEEDED(hr))
  1313. {
  1314. if (!IsEqualTFGUIDATOM(&_libTLS, guidAttr, input_attr))
  1315. {
  1316. SafeReleaseClear(pAttrRange);
  1317. }
  1318. }
  1319. if (fExtend)
  1320. {
  1321. if (pAttrRange)
  1322. {
  1323. pAttrRange->ShiftEndToRange(ec, pRange, TF_ANCHOR_END);
  1324. }
  1325. }
  1326. *ppAttrRange = pAttrRange;
  1327. SafeRelease(pRangeTmp);
  1328. return hr;
  1329. }
  1330. HRESULT CSapiIMX::_DetectFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange)
  1331. {
  1332. BOOL fDetected;
  1333. HRESULT hr = _KillOrDetectFeedbackUI(ec, pic, pRange, &fDetected);
  1334. if (S_OK == hr)
  1335. {
  1336. if (fDetected)
  1337. {
  1338. hr = _RequestEditSession(ESCB_KILLFEEDBACKUI, TF_ES_ASYNC|TF_ES_READWRITE, NULL);
  1339. }
  1340. }
  1341. return hr;
  1342. }
  1343. //+---------------------------------------------------------------------------
  1344. //
  1345. // _KillFeedbackUI
  1346. //
  1347. // get rid of the green/red bar thing within the given range
  1348. //
  1349. //----------------------------------------------------------------------------+
  1350. HRESULT CSapiIMX::_KillFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange)
  1351. {
  1352. return _KillOrDetectFeedbackUI(ec, pic, pRange, NULL);
  1353. }
  1354. HRESULT CSapiIMX::_KillOrDetectFeedbackUI(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, BOOL * pfDetection)
  1355. {
  1356. HRESULT hr;
  1357. ITfProperty *pProp = NULL;
  1358. ITfRange *pAttrRange = NULL;
  1359. IEnumTfRanges *pEnumPr;
  1360. if (pfDetection)
  1361. *pfDetection = FALSE;
  1362. CDocStatus ds(pic);
  1363. if (ds.IsReadOnly())
  1364. return S_OK;
  1365. if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_ATTRIBUTE, &pProp)))
  1366. {
  1367. hr = pProp->EnumRanges(ec, &pEnumPr, pRange);
  1368. if (SUCCEEDED(hr))
  1369. {
  1370. TfGuidAtom guidAttr;
  1371. while( pEnumPr->Next(1, &pAttrRange, NULL) == S_OK )
  1372. {
  1373. if (SUCCEEDED(GetAttrPropertyData(ec, pProp, pAttrRange, &guidAttr)))
  1374. {
  1375. if ( IsEqualTFGUIDATOM(&_libTLS, guidAttr, GUID_ATTR_SAPI_GREENBAR) || IsEqualTFGUIDATOM(&_libTLS, guidAttr, GUID_ATTR_SAPI_GREENBAR2)
  1376. )
  1377. {
  1378. if (pfDetection == NULL)
  1379. {
  1380. // we're not detecting the feedback UI
  1381. // kill this guy
  1382. ITfRange *pSel;
  1383. if (SUCCEEDED(pAttrRange->Clone(&pSel)))
  1384. {
  1385. // Because we didn't change the speech property data while
  1386. // feedback text were injected.
  1387. //
  1388. // Now, when the feedback is killed, we don't want to affect
  1389. // the original speech property data either.
  1390. //
  1391. // set the below flag to prevent the speech property data updated
  1392. // similar way as in feedback UI injection handling
  1393. //
  1394. m_fAcceptRecoResultTextUpdates = TRUE;
  1395. pSel->SetText(ec, 0, NULL, 0);
  1396. // CUAS application will not update the composition
  1397. // while the feedback text is removed based on msctfime
  1398. // current text update checking logic.
  1399. //
  1400. // Call SetSection( ) to forcelly update the edit record
  1401. // of the selection status, and then make sure CUAS
  1402. // update composition string successfully.
  1403. //
  1404. if ( !_IsPureCiceroIC(pic) )
  1405. SetSelectionSimple(ec, pic, pSel);
  1406. pSel->Release();
  1407. m_fAcceptRecoResultTextUpdates = FALSE;
  1408. }
  1409. }
  1410. else
  1411. {
  1412. *pfDetection = TRUE;
  1413. }
  1414. }
  1415. }
  1416. pAttrRange->Release();
  1417. }
  1418. pEnumPr->Release();
  1419. }
  1420. pProp->Release();
  1421. }
  1422. return hr;
  1423. }
  1424. //+---------------------------------------------------------------------------
  1425. //
  1426. // MakeResultString
  1427. //
  1428. //----------------------------------------------------------------------------
  1429. HRESULT CSapiIMX::MakeResultString(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, TfClientId tid, CSpTask *pCSpTask)
  1430. {
  1431. TraceMsg(TF_GENERAL, "MakeResultString is Called");
  1432. HRESULT hr = S_OK;
  1433. if (pCSpTask != NULL)
  1434. {
  1435. AbortString(ec, pic, pCSpTask);
  1436. }
  1437. _KillFocusRange(ec, pic, NULL, tid);
  1438. return hr;
  1439. }
  1440. //+---------------------------------------------------------------------------
  1441. //
  1442. // AbortString
  1443. //
  1444. //----------------------------------------------------------------------------
  1445. HRESULT CSapiIMX::AbortString(TfEditCookie ec, ITfContext *pic, CSpTask *pCSpTask)
  1446. {
  1447. // We may consider not to kill the entire feedback UI
  1448. // because SR happens at background and there can be multi-
  1449. // range dictation going at once but for now, we just kill
  1450. // them all for safety. Later on, we'll re-visit this
  1451. // CSpTask::_StopInput kill the feedback UI.
  1452. //
  1453. Assert(pCSpTask);
  1454. pCSpTask->_StopInput();
  1455. _KillFeedbackUI(ec, pic, NULL);
  1456. return S_OK;
  1457. }
  1458. HRESULT CSapiIMX::_FinalizeComposition()
  1459. {
  1460. return _RequestEditSession(ESCB_FINALIZECOMP, TF_ES_READWRITE);
  1461. }
  1462. HRESULT CSapiIMX::FinalizeAllCompositions( )
  1463. {
  1464. return _RequestEditSession(ESCB_FINALIZE_ALL_COMPS, TF_ES_READWRITE);
  1465. }
  1466. HRESULT CSapiIMX::_FinalizeAllCompositions(TfEditCookie ec, ITfContext *pic )
  1467. {
  1468. HRESULT hr = E_FAIL;
  1469. IEnumITfCompositionView *pEnumComp = NULL;
  1470. ITfContextComposition *picc = NULL;
  1471. ITfCompositionView *pCompositionView;
  1472. ITfComposition *pComposition;
  1473. CLSID clsid;
  1474. CICPriv *picp;
  1475. BOOL fHasOtherComp = FALSE; // When there is composition which is initialized and started
  1476. // by other tips, ( especially by Keyboard tips), this variable
  1477. // set to TRUE
  1478. //
  1479. // clear any sptip compositions over the range
  1480. //
  1481. if (pic->QueryInterface(IID_ITfContextComposition, (void **)&picc) != S_OK)
  1482. goto Exit;
  1483. if (picc->FindComposition(ec, NULL, &pEnumComp) != S_OK)
  1484. goto Exit;
  1485. picp = GetInputContextPriv(_tid, pic);
  1486. while (pEnumComp->Next(1, &pCompositionView, NULL) == S_OK)
  1487. {
  1488. if (pCompositionView->GetOwnerClsid(&clsid) != S_OK)
  1489. goto NextComp;
  1490. if (!IsEqualCLSID(clsid, CLSID_SapiLayr))
  1491. {
  1492. fHasOtherComp = TRUE;
  1493. goto NextComp;
  1494. }
  1495. if (pCompositionView->QueryInterface(IID_ITfComposition, (void **)&pComposition) != S_OK)
  1496. goto NextComp;
  1497. // found a composition, terminate it
  1498. pComposition->EndComposition(ec);
  1499. pComposition->Release();
  1500. if (picp != NULL)
  1501. {
  1502. picp->_ReleaseComposition();
  1503. }
  1504. NextComp:
  1505. pCompositionView->Release();
  1506. }
  1507. SafeRelease(picp);
  1508. if ( fHasOtherComp )
  1509. {
  1510. // Simulate VK_RETURN to terminate composition started by other tips.
  1511. HandleKey( VK_RETURN );
  1512. }
  1513. hr = S_OK;
  1514. Exit:
  1515. SafeRelease(picc);
  1516. SafeRelease(pEnumComp);
  1517. SaveLastUsedIPRange( );
  1518. SaveIPRange(NULL);
  1519. return hr;
  1520. }
  1521. //+---------------------------------------------------------------------------
  1522. //
  1523. // SaveCurrentIP
  1524. //
  1525. // synopsis: this is for recognition handler CSpTask to call when
  1526. // The first hypothesis arrives
  1527. //
  1528. //+---------------------------------------------------------------------------
  1529. void CSapiIMX::SaveCurrentIP(TfEditCookie ec, ITfContext *pic)
  1530. {
  1531. CComPtr<ITfRange> cpSel;
  1532. HRESULT hr = GetSelectionSimple(ec, pic, (ITfRange **)&cpSel);
  1533. if (SUCCEEDED(hr))
  1534. {
  1535. SaveIPRange(cpSel);
  1536. }
  1537. }
  1538. //+---------------------------------------------------------------------------
  1539. //
  1540. // _SyncModeBiasWithSelection
  1541. //
  1542. // synopsis: obtain a read cookie to process selection API
  1543. //
  1544. //---------------------------------------------------------------------------+
  1545. HRESULT CSapiIMX::_SyncModeBiasWithSelection(ITfContext *pic)
  1546. {
  1547. return _RequestEditSession(ESCB_SYNCMBWITHSEL, TF_ES_READ|TF_ES_ASYNC, NULL, pic);
  1548. }
  1549. HRESULT CSapiIMX::_SyncModeBiasWithSelectionCallback(TfEditCookie ec, ITfContext *pic)
  1550. {
  1551. ITfRange *sel;
  1552. if (S_OK == GetSelectionSimple(ec, pic, &sel))
  1553. {
  1554. SyncWithCurrentModeBias(ec, sel, pic);
  1555. sel->Release();
  1556. }
  1557. return S_OK;
  1558. }
  1559. //+---------------------------------------------------------------------------
  1560. //
  1561. // _GetRangeText
  1562. //
  1563. // synopsis: obtain a read cookie to process selection API
  1564. //
  1565. //---------------------------------------------------------------------------+
  1566. HRESULT CSapiIMX::_GetRangeText(ITfRange *pRange, DWORD dwFlgs, WCHAR *psz, ULONG *pulcch)
  1567. {
  1568. HRESULT hr = E_FAIL;
  1569. Assert(pulcch);
  1570. Assert(psz);
  1571. Assert(pRange);
  1572. CComPtr<ITfContext> cpic;
  1573. hr = pRange->GetContext(&cpic);
  1574. if (S_OK == hr)
  1575. {
  1576. CSapiEditSession *pes = new CSapiEditSession(this, cpic);
  1577. if (pes)
  1578. {
  1579. ULONG ulInitSize;
  1580. ulInitSize = *pulcch;
  1581. pes->_SetEditSessionData(ESCB_GETRANGETEXT, NULL, (UINT)(ulInitSize+1) * sizeof(WCHAR), (LONG_PTR)dwFlgs, (LONG_PTR)*pulcch);
  1582. pes->_SetRange(pRange);
  1583. cpic->RequestEditSession(_tid, pes, TF_ES_READ | TF_ES_SYNC, &hr);
  1584. if ( SUCCEEDED(hr) )
  1585. {
  1586. ULONG ulNewSize;
  1587. ulNewSize = (ULONG)pes->_GetRetData( );
  1588. if ( ulNewSize > 0 && ulNewSize <= ulInitSize && pes->_GetPtrData( ) != NULL)
  1589. {
  1590. wcsncpy(psz, (WCHAR *)pes->_GetPtrData( ), ulNewSize);
  1591. psz[ulNewSize] = L'\0';
  1592. }
  1593. *pulcch = ulNewSize;
  1594. }
  1595. pes->Release( );
  1596. }
  1597. }
  1598. return hr;
  1599. }
  1600. //+---------------------------------------------------------------------------
  1601. //
  1602. // _IsRangeEmpty
  1603. //
  1604. // synopsis:
  1605. //
  1606. //---------------------------------------------------------------------------+
  1607. BOOL CSapiIMX::_IsRangeEmpty(ITfRange *pRange)
  1608. {
  1609. CComPtr<ITfContext> cpic;
  1610. BOOL fEmpty = FALSE;
  1611. pRange->GetContext(&cpic);
  1612. if ( cpic )
  1613. {
  1614. _RequestEditSession(ESCB_ISRANGEEMPTY, TF_ES_READ|TF_ES_SYNC, NULL, cpic, (LONG_PTR *)&fEmpty);
  1615. }
  1616. return fEmpty;
  1617. }
  1618. HRESULT CSapiIMX::_HandleHypothesis(CSpEvent &event)
  1619. {
  1620. HRESULT hr = E_FAIL;
  1621. m_ulHypothesisNum ++;
  1622. if ( (m_ulHypothesisNum % 3) != 1 )
  1623. {
  1624. TraceMsg(TF_SAPI_PERF, "Discarded hypothesis %i.", m_ulHypothesisNum % 3);
  1625. return S_OK;
  1626. }
  1627. // if it is under hypothesis processing, don't start a new edit session.
  1628. if ( m_IsInHypoProcessing )
  1629. {
  1630. TraceMsg(TF_SAPI_PERF, "It is under process for previous hypothesis");
  1631. return S_OK;
  1632. }
  1633. m_IsInHypoProcessing = TRUE;
  1634. ISpRecoResult *pResult = event.RecoResult();
  1635. if (pResult)
  1636. {
  1637. ESDATA esData;
  1638. memset(&esData, 0, sizeof(ESDATA));
  1639. esData.pUnk = (IUnknown *)pResult;
  1640. // Require it to be asynchronous to guarantee we don't get called before we have had change
  1641. // to process any final recognition events from SAPI. Otherwise the hypothesis gets injected
  1642. // immediately and then the final recognition tries to remove it which fails.
  1643. hr = _RequestEditSession(ESCB_HANDLEHYPOTHESIS, TF_ES_ASYNC | TF_ES_READWRITE, &esData);
  1644. }
  1645. if ( FAILED(hr) )
  1646. {
  1647. // Set flag to indicate hypothesis processing is finished.
  1648. m_IsInHypoProcessing = FALSE;
  1649. }
  1650. // When hr is succeeded, including TF_S_ASYNC, the edit session function will be called, and
  1651. // it will set the flag when the edit session function exits.
  1652. return hr;
  1653. }
  1654. void CSapiIMX::_HandleHypothesis(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec)
  1655. {
  1656. // if there is a selection, do not inject
  1657. // feedback UI
  1658. //
  1659. // save the current IP if we haven't done so
  1660. if (m_pCSpTask->_GotReco())
  1661. {
  1662. // Optimization and bugfix. We already have a reco and hence have no need to update
  1663. // the feedback bar. AND if we do, it gets left in the document at dictation off and
  1664. // voice commands since it gets altered immediately before an attempt to remove it which
  1665. // then silently fails.
  1666. // Set flag to indicate hypothesis processing is finished.
  1667. m_IsInHypoProcessing = FALSE;
  1668. return;
  1669. }
  1670. Assert(pic);
  1671. CComPtr<ITfRange> cpRange = GetSavedIP();
  1672. if (cpRange)
  1673. {
  1674. CComPtr<ITfContext> cpic;
  1675. if (S_OK == cpRange->GetContext(&cpic))
  1676. {
  1677. if (cpic != pic)
  1678. cpRange.Release(); // this will set NULL to cpRange
  1679. }
  1680. }
  1681. if ( !cpRange )
  1682. {
  1683. SaveCurrentIP(ec, pic);
  1684. cpRange = GetSavedIP();
  1685. }
  1686. SPPHRASE *pPhrase = NULL;
  1687. HRESULT hr = pResult->GetPhrase(&pPhrase);
  1688. if (SUCCEEDED(hr) && pPhrase )
  1689. {
  1690. BOOL fEmpty = FALSE;
  1691. if ( cpRange )
  1692. cpRange->IsEmpty(ec, &fEmpty);
  1693. if (cpRange && fEmpty && pPhrase->ullGrammarID == GRAM_ID_DICT)
  1694. {
  1695. CSpDynamicString dstr;
  1696. hr = pResult->GetText( SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstr, NULL );
  1697. if (S_OK == hr)
  1698. {
  1699. int cch = wcslen(dstr);
  1700. BOOL fAware = IsFocusFullAware(_tim);
  1701. if ( cch > (int)m_ulHypothesisLen )
  1702. {
  1703. _AddFeedbackUI(ec,
  1704. fAware ? DA_COLOR_AWARE : DA_COLOR_UNAWARE,
  1705. 5);
  1706. m_ulHypothesisLen = cch;
  1707. }
  1708. }
  1709. }
  1710. CoTaskMemFree(pPhrase);
  1711. }
  1712. // Set flag to indicate hypothesis processing is finished.
  1713. m_IsInHypoProcessing = FALSE;
  1714. }
  1715. HRESULT CSapiIMX::_HandleFalseRecognition(void)
  1716. {
  1717. m_ulHypothesisLen = 0;
  1718. m_ulHypothesisNum = 0;
  1719. return S_OK;
  1720. }
  1721. HRESULT CSapiIMX::_HandleRecognition(CSpEvent &event, ULONGLONG *pullGramID)
  1722. {
  1723. HRESULT hr = S_OK;
  1724. m_ulHypothesisLen = 0;
  1725. m_ulHypothesisNum = 0;
  1726. ISpRecoResult *pResult = event.RecoResult();
  1727. if (pResult)
  1728. {
  1729. SPPHRASE *pPhrase = NULL;
  1730. hr = pResult->GetPhrase(&pPhrase);
  1731. if (S_OK == hr)
  1732. {
  1733. BOOL fCommand = FALSE;
  1734. ULONGLONG ullGramId;
  1735. BOOL fInjectToDoc = TRUE;
  1736. ullGramId = pPhrase->ullGrammarID;
  1737. if ( ullGramId != GRAM_ID_DICT && ullGramId != GRAM_ID_SPELLING )
  1738. {
  1739. // This is a C&C grammar.
  1740. fCommand = TRUE;
  1741. }
  1742. else if ( ullGramId == GRAM_ID_SPELLING )
  1743. {
  1744. const WCHAR *pwszName;
  1745. pwszName = pPhrase->Rule.pszName;
  1746. if ( pwszName )
  1747. {
  1748. if (0 == wcscmp(pwszName, c_szSpelling))
  1749. fCommand = TRUE;
  1750. else if ( 0 == wcscmp(pwszName, c_szSpellMode) )
  1751. {
  1752. fCommand = TRUE;
  1753. ullGramId = 0; // return 0 to fool the handler for SPEI_RECOGNITION
  1754. // so that it will not call _SetSpellingGrammarStatus(FALSE);
  1755. }
  1756. }
  1757. }
  1758. if (pullGramID)
  1759. *pullGramID = ullGramId;
  1760. if ( fCommand == TRUE)
  1761. {
  1762. // If we got the final recognition before a SOUND_END event we should remove the
  1763. // feedback here otherwise it can and is left in the document.
  1764. EraseFeedbackUI(); // Ignore HRESULT for better failure behavior.
  1765. // If candidate UI is open, we need to close it now. This means a voice command (such as scratch that)
  1766. // will cause the candidate UI to close if open.
  1767. CloseCandUI( );
  1768. // we process this reco synchronously
  1769. // _DoCommand internal will start edit session if necessary
  1770. hr = m_pCSpTask->_DoCommand(pPhrase->ullGrammarID, pPhrase, pPhrase->LangID);
  1771. if ( SUCCEEDED(hr) )
  1772. {
  1773. // if the Command hanlder handles the command successfully, we don't
  1774. // inject the result to the document.
  1775. // otherwise, we just inject the text to the document.
  1776. fInjectToDoc = FALSE;
  1777. }
  1778. }
  1779. if ( fInjectToDoc )
  1780. {
  1781. ESDATA esData;
  1782. memset(&esData, 0, sizeof(ESDATA));
  1783. esData.pUnk = (IUnknown *)pResult;
  1784. hr = _RequestEditSession(ESCB_HANDLERECOGNITION, TF_ES_READWRITE, &esData);
  1785. }
  1786. CoTaskMemFree(pPhrase);
  1787. }
  1788. }
  1789. else
  1790. {
  1791. return E_OUTOFMEMORY;
  1792. }
  1793. return hr;
  1794. }
  1795. void CSapiIMX::_HandleRecognition(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec)
  1796. {
  1797. _KillFeedbackUI(ec, pic, NULL);
  1798. m_pCSpTask->_OnSpEventRecognition(pResult, pic, ec);
  1799. // Before we clear the saved ip range, we need to treat this current ip as last
  1800. // saved ip range if current ip is selected by end user
  1801. SaveLastUsedIPRange( );
  1802. // clear the saved IP range
  1803. SaveIPRange(NULL);
  1804. }