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.

883 lines
29 KiB

  1. #include "stdafx.h"
  2. #include "resource.h"
  3. #include <sapi.h>
  4. #include <string.h>
  5. #include "TTSDlg.h"
  6. #include "audiodlg.h"
  7. #include <spddkhlp.h>
  8. #include "helpresource.h"
  9. #include "srdlg.h"
  10. #include "richedit.h"
  11. #include <SPCollec.h>
  12. #include "SAPIINT.h"
  13. #include "SpATL.h"
  14. #include "SpAutoHandle.h"
  15. #include "SpAutoMutex.h"
  16. #include "SpAutoEvent.h"
  17. #include "spvoice.h"
  18. #include <richedit.h>
  19. #include <richole.h>
  20. #include "tom.h"
  21. static DWORD aKeywordIds[] = {
  22. // Control ID // Help Context ID
  23. IDC_COMBO_VOICES, IDH_LIST_TTS,
  24. IDC_TTS_ADV, IDH_TTS_ADV,
  25. IDC_OUTPUT_SETTINGS, IDH_OUTPUT_SETTINGS,
  26. IDC_SLIDER_SPEED, IDH_SLIDER_SPEED,
  27. IDC_EDIT_SPEAK, IDH_EDIT_SPEAK,
  28. IDC_SPEAK, IDH_SPEAK,
  29. IDC_TTS_ICON, IDH_NOHELP,
  30. IDC_DIRECTIONS, IDH_NOHELP,
  31. IDC_TTS_CAP, IDH_NOHELP,
  32. IDC_SLOW, IDH_NOHELP,
  33. IDC_NORMAL, IDH_NOHELP,
  34. IDC_FAST, IDH_NOHELP,
  35. IDC_GROUP_VOICESPEED, IDH_NOHELP,
  36. IDC_GROUP_PREVIEWVOICE, IDH_NOHELP,
  37. 0, 0
  38. };
  39. // Address of the TrackBar's WNDPROC
  40. WNDPROC g_TrackBarWindowProc;
  41. // Our own internal TrackBar WNDPROC used to intercept and process VK_UP and VK_DOWN messages
  42. LRESULT CALLBACK MyTrackBarWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
  43. /*****************************************************************************
  44. * TTSDlgProc *
  45. *------------*
  46. * Description:
  47. * DLGPROC for the TTS
  48. ****************************************************************** MIKEAR ***/
  49. INT_PTR CALLBACK TTSDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  50. {
  51. SPDBG_FUNC( "TTSDlgProc" );
  52. USES_CONVERSION;
  53. switch (uMsg)
  54. {
  55. case WM_INITDIALOG:
  56. {
  57. g_pTTSDlg->OnInitDialog(hWnd);
  58. break;
  59. }
  60. case WM_DESTROY:
  61. {
  62. g_pTTSDlg->OnDestroy();
  63. break;
  64. }
  65. // Handle the context sensitive help
  66. case WM_CONTEXTMENU:
  67. {
  68. WinHelp((HWND) wParam, CPL_HELPFILE, HELP_CONTEXTMENU, (DWORD_PTR)(LPSTR) aKeywordIds);
  69. break;
  70. }
  71. case WM_HELP:
  72. {
  73. WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, CPL_HELPFILE, HELP_WM_HELP,(DWORD_PTR)(LPSTR) aKeywordIds);
  74. break;
  75. }
  76. case WM_HSCROLL:
  77. {
  78. g_pTTSDlg->ChangeSpeed();
  79. break;
  80. }
  81. case WM_NOTIFY:
  82. switch (((NMHDR*)lParam)->code)
  83. {
  84. case PSN_APPLY:
  85. {
  86. g_pTTSDlg->OnApply();
  87. break;
  88. }
  89. case PSN_KILLACTIVE:
  90. {
  91. // if the voice is speaking, stop it before switching tabs
  92. if (g_pTTSDlg->m_bIsSpeaking) {
  93. g_pTTSDlg->Speak();
  94. }
  95. break;
  96. }
  97. case PSN_QUERYCANCEL: // user clicks the Cancel button
  98. {
  99. if ( g_pSRDlg )
  100. {
  101. g_pSRDlg->OnCancel();
  102. }
  103. break;
  104. }
  105. }
  106. break;
  107. case WM_COMMAND:
  108. switch ( LOWORD(wParam) )
  109. {
  110. case IDC_COMBO_VOICES:
  111. {
  112. if ( CBN_SELCHANGE == HIWORD(wParam) )
  113. {
  114. HRESULT hr = g_pTTSDlg->DefaultVoiceChange(false);
  115. if ( SUCCEEDED( hr ) )
  116. {
  117. g_pTTSDlg->Speak();
  118. }
  119. }
  120. break;
  121. }
  122. case IDC_OUTPUT_SETTINGS:
  123. {
  124. // if it's speaking make it stop
  125. g_pTTSDlg->StopSpeak();
  126. ::SetFocus(GetDlgItem(g_pTTSDlg->m_hDlg, IDC_OUTPUT_SETTINGS));
  127. // The m_pAudioDlg will be non-NULL only if the audio dialog
  128. // has been previously brough up.
  129. // Otherwise, we need a newly-initialized one
  130. if ( !g_pTTSDlg->m_pAudioDlg )
  131. {
  132. g_pTTSDlg->m_pAudioDlg = new CAudioDlg(eOUTPUT );
  133. }
  134. if (g_pTTSDlg->m_pAudioDlg != NULL)
  135. {
  136. ::DialogBoxParam( _Module.GetResourceInstance(),
  137. MAKEINTRESOURCE( IDD_AUDIO_DEFAULT ),
  138. hWnd,
  139. AudioDlgProc,
  140. (LPARAM) g_pTTSDlg->m_pAudioDlg );
  141. if ( g_pTTSDlg->m_pAudioDlg->IsAudioDeviceChangedSinceLastTime() )
  142. {
  143. // Warn the user that he needs to apply the changes
  144. WCHAR szWarning[MAX_LOADSTRING];
  145. szWarning[0] = 0;
  146. LoadString( _Module.GetResourceInstance(), IDS_AUDIOOUT_CHANGE_WARNING, szWarning, MAX_LOADSTRING);
  147. MessageBox( g_pTTSDlg->GetHDlg(), szWarning, g_pTTSDlg->m_szCaption, MB_ICONWARNING | g_dwIsRTLLayout );
  148. }
  149. }
  150. g_pTTSDlg->KickCPLUI();
  151. break;
  152. }
  153. case IDC_EDIT_SPEAK:
  154. {
  155. if (HIWORD(wParam) == EN_CHANGE) // user is changing text
  156. {
  157. g_pTTSDlg->SetEditModified(true);
  158. }
  159. break;
  160. }
  161. case IDC_SPEAK:
  162. {
  163. g_pTTSDlg->Speak();
  164. break;
  165. }
  166. case IDC_TTS_ADV:
  167. {
  168. // convert the title of the window to wide chars
  169. CSpDynamicString dstrTitle;
  170. WCHAR szTitle[256];
  171. szTitle[0] = '\0';
  172. LoadString(_Module.GetResourceInstance(), IDS_ENGINE_SETTINGS, szTitle, sp_countof(szTitle));
  173. dstrTitle = szTitle;
  174. HRESULT hr = g_pTTSDlg->m_cpCurVoiceToken->DisplayUI(
  175. hWnd, dstrTitle, SPDUI_EngineProperties, NULL, 0, NULL );
  176. if ( FAILED( hr ) )
  177. {
  178. WCHAR szError[ MAX_LOADSTRING ];
  179. ::LoadString( _Module.GetResourceInstance(), IDS_TTSUI_ERROR, szError, sp_countof( szError ) );
  180. ::MessageBox( hWnd, szError, g_pTTSDlg->m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout );
  181. ::EnableWindow( ::GetDlgItem( hWnd, IDC_TTS_ADV ), FALSE );
  182. }
  183. break;
  184. }
  185. }
  186. break;
  187. }
  188. return FALSE;
  189. } /* TTSDlgProc */
  190. /*****************************************************************************
  191. * MyTrackBarWindowProc *
  192. *------------*
  193. * Description:
  194. * This is our own privately sub-classed WNDPROC for the rate TrackBar. We
  195. * tell the TTS dialog to use this one so we can pre-process the VK_UP and
  196. * VK_DOWN messages before the TrackBar's WNDPROC "incorrectly" handles them
  197. * on it's own. All other messages we just pass through to the TrackBar's
  198. * WNDPROC.
  199. ****************************************************************** Leonro ***/
  200. LRESULT CALLBACK MyTrackBarWindowProc(
  201. HWND hwnd, // handle to window
  202. UINT uMsg, // message identifier
  203. WPARAM wParam, // first message parameter
  204. LPARAM lParam // second message parameter
  205. )
  206. {
  207. switch( uMsg )
  208. {
  209. case WM_KEYDOWN:
  210. case WM_KEYUP:
  211. if( wParam == VK_UP )
  212. {
  213. wParam = VK_RIGHT;
  214. }
  215. else if( wParam == VK_DOWN )
  216. {
  217. wParam = VK_LEFT;
  218. }
  219. break;
  220. }
  221. return CallWindowProc( g_TrackBarWindowProc, hwnd, uMsg, wParam, lParam );
  222. }
  223. /*****************************************************************************
  224. * CTTSDlg::SetEditModified( bool fModify ) *
  225. *-----------------------*
  226. * Description:
  227. * Access method for m_fTextModified
  228. ****************************************************************** BRENTMID ***/
  229. void CTTSDlg::SetEditModified( bool fModify )
  230. {
  231. m_fTextModified = fModify;
  232. }
  233. /*****************************************************************************
  234. * CTTSDlg::OnInitDialog *
  235. *-----------------------*
  236. * Description:
  237. * Dialog Initialization
  238. ****************************************************************** BECKYW ***/
  239. void CTTSDlg::OnInitDialog(HWND hWnd)
  240. {
  241. USES_CONVERSION;
  242. SPDBG_FUNC( "CTTSDlg::OnInitDialog" );
  243. SPDBG_ASSERT(IsWindow(hWnd));
  244. m_hDlg = hWnd;
  245. // Put text on the speak button
  246. ChangeSpeakButton();
  247. // This is to be the caption for error messages
  248. m_szCaption[0] = 0;
  249. ::LoadString( _Module.GetResourceInstance(), IDS_CAPTION, m_szCaption, sp_countof( m_szCaption ) );
  250. // Initialize the TTS personality list
  251. InitTTSList( hWnd );
  252. // Set the range on the slider
  253. HWND hSlider = ::GetDlgItem( hWnd, IDC_SLIDER_SPEED );
  254. ::SendMessage( hSlider, TBM_SETRANGE, true, MAKELONG( VOICE_MIN_SPEED, VOICE_MAX_SPEED ) );
  255. // Retrieve address of the TrackBar's WNDPROC so we can sub-class it and intercept
  256. // and process the VK_UP and VK_DOWN messages before it handle's them on it's
  257. // own "incorrectly"
  258. g_TrackBarWindowProc = (WNDPROC)GetWindowLongPtr( hSlider, GWLP_WNDPROC );
  259. // Set the WNDPROC of the TrackBar to MyTrackBarWindowProc
  260. SetWindowLongPtr( hSlider, GWLP_WNDPROC, (LONG_PTR)MyTrackBarWindowProc );
  261. // Limit the text in the preview pane
  262. ::SendDlgItemMessage( hWnd, IDC_EDIT_SPEAK, EM_LIMITTEXT, MAX_EDIT_TEXT - 1, 0 );
  263. // Find the original default token
  264. SpGetDefaultTokenFromCategoryId( SPCAT_VOICES, &m_cpOriginalDefaultVoiceToken );
  265. // Set the appropriate voice
  266. DefaultVoiceChange(true);
  267. } /* CTTSDlg::OnInitDialog */
  268. /*****************************************************************************
  269. * CTTSDlg::InitTTSList *
  270. *----------------------*
  271. * Description:
  272. * Initializes the list control for the TTS dialog box.
  273. *******************************************************************BECKYW****/
  274. void CTTSDlg::InitTTSList( HWND hWnd )
  275. {
  276. m_hTTSCombo = ::GetDlgItem( hWnd, IDC_COMBO_VOICES );
  277. SpInitTokenComboBox( m_hTTSCombo, SPCAT_VOICES );
  278. }
  279. /*****************************************************************************
  280. * CTTSDlg::OnDestroy *
  281. *--------------------*
  282. * Description:
  283. * Destruction
  284. ****************************************************************** MIKEAR ***/
  285. void CTTSDlg::OnDestroy()
  286. {
  287. SPDBG_FUNC( "CTTSDlg::OnDestroy" );
  288. if (m_cpVoice)
  289. {
  290. m_cpVoice->SetNotifySink(NULL);
  291. m_cpVoice.Release();
  292. }
  293. // Let go of the tokens in the combo box
  294. SpDestroyTokenComboBox( m_hTTSCombo );
  295. } /* CTTSDlg::OnDestroy */
  296. /*****************************************************************************
  297. * CTTSDlg::OnApply *
  298. *------------------*
  299. * Description:
  300. * Set user specified options
  301. ****************************************************************** BECKYW ***/
  302. void CTTSDlg::OnApply()
  303. {
  304. // SOFTWARE ENGINEERING OPPORTUNITY (BeckyW 7/28/00): This needs to
  305. // return an error code
  306. SPDBG_FUNC( "CTTSDlg::OnApply" );
  307. m_bApplied = true;
  308. // Store the current voice
  309. HRESULT hr = E_FAIL;
  310. if (m_cpCurVoiceToken)
  311. {
  312. hr = SpSetDefaultTokenForCategoryId(SPCAT_VOICES, m_cpCurVoiceToken );
  313. }
  314. if ( SUCCEEDED( hr ) )
  315. {
  316. m_cpOriginalDefaultVoiceToken = m_cpCurVoiceToken;
  317. }
  318. // Store the current audio out
  319. hr = S_OK;
  320. if ( m_pAudioDlg )
  321. {
  322. hr = m_pAudioDlg->OnApply();
  323. if ( FAILED( hr ) )
  324. {
  325. WCHAR szError[256];
  326. szError[0] = '\0';
  327. LoadString(_Module.GetResourceInstance(), IDS_AUDIO_CHANGE_FAILED, szError, sp_countof(szError));
  328. MessageBox(m_hDlg, szError, m_szCaption, MB_ICONWARNING | g_dwIsRTLLayout);
  329. }
  330. // Kill the audio dialog, as we are done with it.
  331. delete m_pAudioDlg;
  332. m_pAudioDlg = NULL;
  333. // Re-create the voice, since we have audio changes to pick up
  334. DefaultVoiceChange(false);
  335. }
  336. // Store the voice rate in the registry
  337. int iCurRate = 0;
  338. HWND hSlider = ::GetDlgItem( m_hDlg, IDC_SLIDER_SPEED );
  339. iCurRate = (int)::SendMessage( hSlider, TBM_GETPOS, 0, 0 );
  340. CComPtr<ISpObjectTokenCategory> cpCategory;
  341. if (SUCCEEDED(SpGetCategoryFromId(SPCAT_VOICES, &cpCategory)))
  342. {
  343. CComPtr<ISpDataKey> cpDataKey;
  344. if (SUCCEEDED(cpCategory->GetDataKey(SPDKL_CurrentUser, &cpDataKey)))
  345. {
  346. cpDataKey->SetDWORD(SPVOICECATEGORY_TTSRATE, iCurRate);
  347. }
  348. }
  349. // Keep around the slider position for determining UI state later
  350. m_iOriginalRateSliderPos = iCurRate;
  351. KickCPLUI();
  352. } /* CTTSDlg::OnApply */
  353. /*****************************************************************************
  354. * CTTSDlg::PopulateEditCtrl *
  355. *---------------------------*
  356. * Description:
  357. * Populates the edit control with the name of the default voice.
  358. ****************************************************************** MIKEAR ***/
  359. void CTTSDlg::PopulateEditCtrl( ISpObjectToken * pToken )
  360. {
  361. SPDBG_FUNC( "CTTSDlg::PopulateEditCtrl" );
  362. HRESULT hr = S_OK;
  363. // Richedit/TOM
  364. CComPtr<IRichEditOle> cpRichEdit; // OLE interface to the rich edit control
  365. CComPtr<ITextDocument> cpTextDoc;
  366. CComPtr<ITextRange> cpTextRange;
  367. LANGID langId;
  368. WCHAR text[128], editText[MAX_PATH];
  369. HWND hWndEdit = ::GetDlgItem(m_hDlg, IDC_EDIT_SPEAK);
  370. CSpDynamicString dstrDescription;
  371. if ((SUCCEEDED(SpGetLanguageFromVoiceToken(pToken, &langId))) && (!m_fTextModified))
  372. {
  373. CComPtr<ISpObjectTokenCategory> cpCategory;
  374. CComPtr<ISpDataKey> cpAttributesKey;
  375. CComPtr<ISpDataKey> cpPreviewKey;
  376. CComPtr<ISpDataKey> cpVoicesKey;
  377. int len;
  378. // First get language of voice from token.
  379. hr = SpGetDescription(pToken, &dstrDescription, langId);
  380. // Now get hold of preview key to try and find the appropriate text.
  381. if (SUCCEEDED(hr))
  382. {
  383. hr = SpGetCategoryFromId(SPCAT_VOICES, &cpCategory);
  384. }
  385. if (SUCCEEDED(hr))
  386. {
  387. hr = cpCategory->GetDataKey(SPDKL_LocalMachine, &cpVoicesKey);
  388. }
  389. if (SUCCEEDED(hr))
  390. {
  391. hr = cpVoicesKey->OpenKey(L"Preview", &cpPreviewKey);
  392. }
  393. if (SUCCEEDED(hr))
  394. {
  395. CSpDynamicString dstrText;
  396. swprintf(text, L"%x", langId);
  397. hr = cpPreviewKey->GetStringValue(text, &dstrText);
  398. if (SUCCEEDED(hr))
  399. {
  400. wcsncpy(text, dstrText, 127);
  401. text[127] = 0;
  402. }
  403. }
  404. // If preview key did not contain appropriate text, fall back to the hard-coded (and
  405. // potentially localized) text in the cpl resources.
  406. if (FAILED(hr))
  407. {
  408. len = LoadString( _Module.GetResourceInstance(), IDS_DEF_VOICE_TEXT, text, 128);
  409. if (len != 0)
  410. {
  411. hr = S_OK;
  412. }
  413. }
  414. if(SUCCEEDED(hr))
  415. {
  416. WCHAR *pFirstP = wcschr(text, L'%');
  417. WCHAR *pLastP = wcsrchr(text, L'%');
  418. // Preview string with no %s or one %s is valid. Other kinds are not so just use description string
  419. if(!pFirstP || (pFirstP == pLastP && ((*(pFirstP + 1) == L's' || *(pFirstP + 1) == L'S'))))
  420. {
  421. _snwprintf( editText, MAX_PATH, text, dstrDescription );
  422. }
  423. else
  424. {
  425. _snwprintf( editText, MAX_PATH, L"%s", dstrDescription );
  426. }
  427. // truncate string if too long
  428. editText[MAX_PATH - 1] = L'\0';
  429. ::SendMessage( hWndEdit, EM_GETOLEINTERFACE, 0, (LPARAM)(LPVOID FAR *)&cpRichEdit );
  430. if ( !cpRichEdit )
  431. {
  432. hr = E_FAIL;
  433. }
  434. if (SUCCEEDED(hr))
  435. {
  436. hr = cpRichEdit->QueryInterface( IID_ITextDocument, (void**)&cpTextDoc );
  437. }
  438. if (SUCCEEDED(hr))
  439. {
  440. hr = cpTextDoc->Range(0, MAX_EDIT_TEXT-1, &cpTextRange);
  441. }
  442. if (SUCCEEDED(hr))
  443. {
  444. BSTR bstrText = SysAllocString(editText);
  445. hr = cpTextRange->SetText(bstrText);
  446. SysFreeString(bstrText);
  447. }
  448. if (FAILED(hr))
  449. {
  450. // Do best we can with this API - unicode languages not equal to the OS language will be replaced with ???'s.
  451. SetWindowText(hWndEdit, editText );
  452. }
  453. }
  454. }
  455. } /* CTTSDlg::PopulateEditCtrl */
  456. /*****************************************************************************
  457. * CTTSDlg::DefaultVoiceChange *
  458. *-----------------------------*
  459. * Description:
  460. * Changes the current default voice.
  461. * If there is already a default voice in effect (i.e., if this is
  462. * not the first time we are calling this function), removes the
  463. * checkmark from the appropriate item in the list.
  464. * Sets the checkmark to the appropriate item in the list.
  465. * Sets the voice with the appropriate token.
  466. * Return:
  467. * S_OK
  468. * E_UNEXPECTED if there is no token currently selected in the voice list
  469. * failure codes of SAPI voice initialization functions
  470. ******************************************************************* BECKYW ***/
  471. HRESULT CTTSDlg::DefaultVoiceChange(bool fUsePersistentRate)
  472. {
  473. SPDBG_FUNC( "CTTSDlg::DefaultVoiceChange" );
  474. if ( m_bIsSpeaking )
  475. {
  476. // This forces the voice to stop speaking
  477. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  478. m_bIsSpeaking = false;
  479. ChangeSpeakButton();
  480. }
  481. if (m_cpVoice)
  482. {
  483. m_cpVoice->SetNotifySink(NULL);
  484. m_cpVoice.Release();
  485. }
  486. // Find out what is selected
  487. int iSelIndex = (int) ::SendMessage( m_hTTSCombo, CB_GETCURSEL, 0, 0 );
  488. m_cpCurVoiceToken = (ISpObjectToken *) ::SendMessage( m_hTTSCombo, CB_GETITEMDATA, iSelIndex, 0 );
  489. if ( CB_ERR == (LRESULT) m_cpCurVoiceToken.p )
  490. {
  491. m_cpCurVoiceToken = NULL;
  492. }
  493. HRESULT hr = E_UNEXPECTED;
  494. if (m_cpCurVoiceToken)
  495. {
  496. BOOL fSupported = FALSE;
  497. hr = m_cpCurVoiceToken->IsUISupported(SPDUI_EngineProperties, NULL, 0, NULL, &fSupported);
  498. if ( FAILED( hr ) )
  499. {
  500. fSupported = FALSE;
  501. }
  502. ::EnableWindow(::GetDlgItem(m_hDlg, IDC_TTS_ADV), fSupported);
  503. // Get the voice from the SR Dlg's recognizer, if possible.
  504. // Otherwise just CoCreate the voice
  505. ISpRecoContext *pRecoContext =
  506. g_pSRDlg ? g_pSRDlg->GetRecoContext() : NULL;
  507. if ( pRecoContext )
  508. {
  509. hr = pRecoContext->GetVoice( &m_cpVoice );
  510. // Since the recocontext might not have changed, but we might have
  511. // changed the default audio object, just to be sure, we
  512. // go and get the default audio out token and SetOutput
  513. CComPtr<ISpObjectToken> cpAudioToken;
  514. if ( SUCCEEDED( hr ) )
  515. {
  516. hr = SpGetDefaultTokenFromCategoryId( SPCAT_AUDIOOUT, &cpAudioToken );
  517. }
  518. else
  519. {
  520. hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice);
  521. }
  522. if ( SUCCEEDED( hr ) )
  523. {
  524. hr = m_cpVoice->SetOutput( cpAudioToken, TRUE );
  525. }
  526. }
  527. else
  528. {
  529. hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice);
  530. }
  531. if( SUCCEEDED( hr ) )
  532. {
  533. hr = m_cpVoice->SetVoice(m_cpCurVoiceToken);
  534. }
  535. if (SUCCEEDED(hr))
  536. {
  537. CComPtr<ISpNotifyTranslator> cpNotify;
  538. cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
  539. cpNotify->InitSpNotifyCallback(this, 0, 0);
  540. m_cpVoice->SetInterest(SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_END_INPUT_STREAM), 0);
  541. m_cpVoice->SetNotifySink(cpNotify);
  542. // Set the appropriate speed on the slider
  543. if (fUsePersistentRate)
  544. {
  545. CComPtr<ISpObjectTokenCategory> cpCategory;
  546. ULONG ulCurRate=0;
  547. if (SUCCEEDED(SpGetCategoryFromId(SPCAT_VOICES, &cpCategory)))
  548. {
  549. CComPtr<ISpDataKey> cpDataKey;
  550. if (SUCCEEDED(cpCategory->GetDataKey(SPDKL_CurrentUser, &cpDataKey)))
  551. {
  552. cpDataKey->GetDWORD(SPVOICECATEGORY_TTSRATE, (ULONG*)&ulCurRate);
  553. }
  554. }
  555. m_iOriginalRateSliderPos = ulCurRate;
  556. }
  557. else
  558. {
  559. m_iOriginalRateSliderPos = m_iSpeed;
  560. }
  561. HWND hSlider = ::GetDlgItem( m_hDlg, IDC_SLIDER_SPEED );
  562. ::SendMessage( hSlider, TBM_SETPOS, true, m_iOriginalRateSliderPos );
  563. // Enable the Preview Voice button
  564. ::EnableWindow( ::GetDlgItem( g_pTTSDlg->GetHDlg(), IDC_SPEAK ), TRUE );
  565. }
  566. else
  567. {
  568. // Warn the user of failure
  569. WCHAR szError[MAX_LOADSTRING];
  570. szError[0] = 0;
  571. LoadString( _Module.GetResourceInstance(), IDS_TTS_ERROR, szError, MAX_LOADSTRING);
  572. MessageBox( GetHDlg(), szError, m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout );
  573. // Disable the Preview Voice button
  574. ::EnableWindow( ::GetDlgItem( GetHDlg(), IDC_SPEAK ), FALSE );
  575. }
  576. // Put text corresponding to this voice into the edit control
  577. PopulateEditCtrl(m_cpCurVoiceToken);
  578. // Kick the UI
  579. KickCPLUI();
  580. }
  581. return hr;
  582. } /* CTTSDlg::DefaultVoiceChange */
  583. /*****************************************************************************
  584. * CTTSDlg::SetCheckmark *
  585. *-----------------------*
  586. * Description:
  587. * Sets the specified item in the list control to be either checked
  588. * or unchecked (as the default voice)
  589. ******************************************************************************/
  590. void CTTSDlg::SetCheckmark( HWND hList, int iIndex, bool bCheck )
  591. {
  592. m_fForceCheckStateChange = true;
  593. ListView_SetCheckState( hList, iIndex, bCheck );
  594. m_fForceCheckStateChange = false;
  595. } /* CTTSDlg::SetCheckmark */
  596. /*****************************************************************************
  597. * CTTSDlg::KickCPLUI *
  598. *--------------------*
  599. * Description:
  600. * Determines if there is anything to apply right now
  601. ******************************************************************************/
  602. void CTTSDlg::KickCPLUI()
  603. {
  604. bool fChanged = false;
  605. // Compare IDs
  606. CSpDynamicString dstrSelTokenID;
  607. CSpDynamicString dstrOriginalDefaultTokenID;
  608. HRESULT hr = E_FAIL;
  609. if ( m_cpOriginalDefaultVoiceToken && m_cpCurVoiceToken )
  610. {
  611. hr = m_cpCurVoiceToken->GetId( &dstrSelTokenID );
  612. }
  613. if ( SUCCEEDED( hr ) )
  614. {
  615. hr = m_cpOriginalDefaultVoiceToken->GetId( &dstrOriginalDefaultTokenID );
  616. }
  617. if ( SUCCEEDED( hr )
  618. && ( 0 != wcsicmp( dstrOriginalDefaultTokenID, dstrSelTokenID ) ) )
  619. {
  620. fChanged = true;
  621. }
  622. // Check the audio device
  623. if ( m_pAudioDlg && m_pAudioDlg->IsAudioDeviceChanged() )
  624. {
  625. fChanged = true;
  626. }
  627. // Check the voice rate
  628. int iSpeed = (int) ::SendDlgItemMessage( m_hDlg, IDC_SLIDER_SPEED,
  629. TBM_GETPOS, 0, 0 );
  630. if ( m_iOriginalRateSliderPos != iSpeed )
  631. {
  632. fChanged = true;
  633. }
  634. // Tell the main propsheet
  635. HWND hwndParent = ::GetParent( m_hDlg );
  636. ::SendMessage( hwndParent, fChanged ? PSM_CHANGED : PSM_UNCHANGED, (WPARAM)(m_hDlg), 0 );
  637. } /* CTTSDlg::KickCPLUI */
  638. /*****************************************************************************
  639. * CTTSDlg::ChangeSpeed *
  640. *----------------------*
  641. * Description:
  642. * Called when the slider has moved.
  643. * Adjusts the speed of the voice
  644. ****************************************************************** BECKYW ***/
  645. void CTTSDlg::ChangeSpeed()
  646. {
  647. HWND hSlider = ::GetDlgItem( m_hDlg, IDC_SLIDER_SPEED );
  648. m_iSpeed = (int)::SendMessage( hSlider, TBM_GETPOS, 0, 0 );
  649. m_cpVoice->SetRate( m_iSpeed );
  650. KickCPLUI();
  651. } /* CTTSDlg::ChangeSpeed */
  652. /*****************************************************************************
  653. * CTTSDlg::ChangeSpeakButton *
  654. *----------------------------*
  655. * Description:
  656. * Changes the text in the "Preview Voice" button in order to
  657. * reflect whether there is currently a speak happening.
  658. ****************************************************************** BECKYW ***/
  659. void CTTSDlg::ChangeSpeakButton()
  660. {
  661. WCHAR pszButtonCaption[ MAX_LOADSTRING ];
  662. HWND hButton = ::GetDlgItem( m_hDlg, IDC_SPEAK );
  663. ::LoadString( _Module.GetResourceInstance(), m_bIsSpeaking ? IDS_STOP_PREVIEW : IDS_PREVIEW,
  664. pszButtonCaption, MAX_LOADSTRING );
  665. ::SendMessage( hButton, WM_SETTEXT, 0, (LPARAM) pszButtonCaption );
  666. if (!m_bIsSpeaking)
  667. {
  668. ::SetFocus(GetDlgItem(m_hDlg, IDC_SPEAK));
  669. }
  670. } /* CTTSDlg::ChangeSpeakButton */
  671. /*****************************************************************************
  672. * CTTSDlg::Speak *
  673. *----------------*
  674. * Description:
  675. * Speaks the contents of the edit control.
  676. * If it is already speaking, shuts up.
  677. ****************************************************************** BECKYW ***/
  678. void CTTSDlg::Speak()
  679. {
  680. SPDBG_FUNC( "CTTSDlg::Speak" );
  681. if ( m_bIsSpeaking )
  682. {
  683. // This forces the voice to stop speaking
  684. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  685. m_bIsSpeaking = false;
  686. ChangeSpeakButton();
  687. }
  688. else
  689. {
  690. ChangeSpeed();
  691. GETTEXTEX gtex = { sp_countof(m_wszCurSpoken), GT_DEFAULT, 1200, NULL, NULL };
  692. m_wszCurSpoken[0] = 0;
  693. LRESULT cChars = ::SendDlgItemMessage(m_hDlg,
  694. IDC_EDIT_SPEAK, EM_GETTEXTEX, (WPARAM)&gtex, (LPARAM)m_wszCurSpoken);
  695. if (cChars)
  696. {
  697. HRESULT hr = m_cpVoice->Speak(m_wszCurSpoken, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
  698. if ( SUCCEEDED( hr ) )
  699. {
  700. m_bIsSpeaking = true;
  701. ::SetFocus(GetDlgItem(m_hDlg, IDC_EDIT_SPEAK));
  702. ChangeSpeakButton();
  703. }
  704. else
  705. {
  706. // Warn the user that he needs to apply the changes
  707. WCHAR szError[MAX_LOADSTRING];
  708. szError[0] = 0;
  709. LoadString( _Module.GetResourceInstance(), IDS_SPEAK_ERROR, szError, MAX_LOADSTRING);
  710. MessageBox( GetHDlg(), szError, m_szCaption, MB_ICONWARNING | g_dwIsRTLLayout );
  711. }
  712. }
  713. }
  714. } /* CTTSDlg::Speak */
  715. /*****************************************************************************
  716. * CTTSDlg::StopSpeak *
  717. *----------------*
  718. * Description:
  719. * Stops the voice from speaking.
  720. * If it is already speaking, shuts up.
  721. ****************************************************************** BECKYW ***/
  722. void CTTSDlg::StopSpeak()
  723. {
  724. SPDBG_FUNC( "CTTSDlg::StopSpeak" );
  725. if ( m_bIsSpeaking )
  726. {
  727. // This forces the voice to stop speaking
  728. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  729. m_bIsSpeaking = false;
  730. }
  731. ChangeSpeakButton();
  732. } /* CTTSDlg::StopSpeak */
  733. /*****************************************************************************
  734. * CTTSDlg::NotifyCallback *
  735. *-------------------------*
  736. * Description:
  737. * Callback function that highlights words as they are spoken.
  738. ****************************************************************** MIKEAR ***/
  739. STDMETHODIMP CTTSDlg::NotifyCallback(WPARAM /* wParam */, LPARAM /* lParam */)
  740. {
  741. SPDBG_FUNC( "CTTSDlg::NotifyCallback" );
  742. SPVOICESTATUS Stat;
  743. m_cpVoice->GetStatus(&Stat, NULL);
  744. WPARAM nStart;
  745. LPARAM nEnd;
  746. if (Stat.dwRunningState & SPRS_DONE)
  747. {
  748. nStart = nEnd = 0;
  749. m_bIsSpeaking = false;
  750. ChangeSpeakButton();
  751. // Set the selection to an IP at the start of the text to speak
  752. ::SendDlgItemMessage( m_hDlg, IDC_EDIT_SPEAK, EM_SETSEL, 0, 0 );
  753. }
  754. else
  755. {
  756. nStart = (LPARAM)Stat.ulInputWordPos;
  757. nEnd = nStart + Stat.ulInputWordLen;
  758. CHARRANGE cr;
  759. cr.cpMin = (LONG)nStart;
  760. cr.cpMax = (LONG)nEnd;
  761. ::SendDlgItemMessage( m_hDlg, IDC_EDIT_SPEAK, EM_EXSETSEL, 0, (LPARAM) &cr );
  762. }
  763. return S_OK;
  764. } /* CTTSDlg::NotifyCallback */