Source code of Windows XP (NT5)
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.

869 lines
28 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. BOOL 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. (DLGPROC) 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, sizeof(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, sizeof(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. swprintf( editText, text, dstrDescription );
  417. ::SendMessage( hWndEdit, EM_GETOLEINTERFACE, 0, (LPARAM)(LPVOID FAR *)&cpRichEdit );
  418. if ( !cpRichEdit )
  419. {
  420. hr = E_FAIL;
  421. }
  422. if (SUCCEEDED(hr))
  423. {
  424. hr = cpRichEdit->QueryInterface( IID_ITextDocument, (void**)&cpTextDoc );
  425. }
  426. if (SUCCEEDED(hr))
  427. {
  428. hr = cpTextDoc->Range(0, MAX_EDIT_TEXT-1, &cpTextRange);
  429. }
  430. if (SUCCEEDED(hr))
  431. {
  432. BSTR bstrText = SysAllocString(editText);
  433. hr = cpTextRange->SetText(bstrText);
  434. SysFreeString(bstrText);
  435. }
  436. if (FAILED(hr))
  437. {
  438. // Do best we can with this API - unicode languages not equal to the OS language will be replaced with ???'s.
  439. SetWindowText(hWndEdit, editText );
  440. }
  441. }
  442. }
  443. } /* CTTSDlg::PopulateEditCtrl */
  444. /*****************************************************************************
  445. * CTTSDlg::DefaultVoiceChange *
  446. *-----------------------------*
  447. * Description:
  448. * Changes the current default voice.
  449. * If there is already a default voice in effect (i.e., if this is
  450. * not the first time we are calling this function), removes the
  451. * checkmark from the appropriate item in the list.
  452. * Sets the checkmark to the appropriate item in the list.
  453. * Sets the voice with the appropriate token.
  454. * Return:
  455. * S_OK
  456. * E_UNEXPECTED if there is no token currently selected in the voice list
  457. * failure codes of SAPI voice initialization functions
  458. ******************************************************************* BECKYW ***/
  459. HRESULT CTTSDlg::DefaultVoiceChange(bool fUsePersistentRate)
  460. {
  461. SPDBG_FUNC( "CTTSDlg::DefaultVoiceChange" );
  462. if ( m_bIsSpeaking )
  463. {
  464. // This forces the voice to stop speaking
  465. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  466. m_bIsSpeaking = false;
  467. ChangeSpeakButton();
  468. }
  469. if (m_cpVoice)
  470. {
  471. m_cpVoice->SetNotifySink(NULL);
  472. m_cpVoice.Release();
  473. }
  474. // Find out what is selected
  475. int iSelIndex = (int) ::SendMessage( m_hTTSCombo, CB_GETCURSEL, 0, 0 );
  476. m_cpCurVoiceToken = (ISpObjectToken *) ::SendMessage( m_hTTSCombo, CB_GETITEMDATA, iSelIndex, 0 );
  477. if ( CB_ERR == (LRESULT) m_cpCurVoiceToken.p )
  478. {
  479. m_cpCurVoiceToken = NULL;
  480. }
  481. HRESULT hr = E_UNEXPECTED;
  482. if (m_cpCurVoiceToken)
  483. {
  484. BOOL fSupported = FALSE;
  485. hr = m_cpCurVoiceToken->IsUISupported(SPDUI_EngineProperties, NULL, 0, NULL, &fSupported);
  486. if ( FAILED( hr ) )
  487. {
  488. fSupported = FALSE;
  489. }
  490. ::EnableWindow(::GetDlgItem(m_hDlg, IDC_TTS_ADV), fSupported);
  491. // Get the voice from the SR Dlg's recognizer, if possible.
  492. // Otherwise just CoCreate the voice
  493. ISpRecoContext *pRecoContext =
  494. g_pSRDlg ? g_pSRDlg->GetRecoContext() : NULL;
  495. if ( pRecoContext )
  496. {
  497. hr = pRecoContext->GetVoice( &m_cpVoice );
  498. // Since the recocontext might not have changed, but we might have
  499. // changed the default audio object, just to be sure, we
  500. // go and get the default audio out token and SetOutput
  501. CComPtr<ISpObjectToken> cpAudioToken;
  502. if ( SUCCEEDED( hr ) )
  503. {
  504. hr = SpGetDefaultTokenFromCategoryId( SPCAT_AUDIOOUT, &cpAudioToken );
  505. }
  506. else
  507. {
  508. hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice);
  509. }
  510. if ( SUCCEEDED( hr ) )
  511. {
  512. hr = m_cpVoice->SetOutput( cpAudioToken, TRUE );
  513. }
  514. }
  515. else
  516. {
  517. hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice);
  518. }
  519. if( SUCCEEDED( hr ) )
  520. {
  521. hr = m_cpVoice->SetVoice(m_cpCurVoiceToken);
  522. }
  523. if (SUCCEEDED(hr))
  524. {
  525. CComPtr<ISpNotifyTranslator> cpNotify;
  526. cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
  527. cpNotify->InitSpNotifyCallback(this, 0, 0);
  528. m_cpVoice->SetInterest(SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_END_INPUT_STREAM), 0);
  529. m_cpVoice->SetNotifySink(cpNotify);
  530. // Set the appropriate speed on the slider
  531. if (fUsePersistentRate)
  532. {
  533. CComPtr<ISpObjectTokenCategory> cpCategory;
  534. ULONG ulCurRate=0;
  535. if (SUCCEEDED(SpGetCategoryFromId(SPCAT_VOICES, &cpCategory)))
  536. {
  537. CComPtr<ISpDataKey> cpDataKey;
  538. if (SUCCEEDED(cpCategory->GetDataKey(SPDKL_CurrentUser, &cpDataKey)))
  539. {
  540. cpDataKey->GetDWORD(SPVOICECATEGORY_TTSRATE, (ULONG*)&ulCurRate);
  541. }
  542. }
  543. m_iOriginalRateSliderPos = ulCurRate;
  544. }
  545. else
  546. {
  547. m_iOriginalRateSliderPos = m_iSpeed;
  548. }
  549. HWND hSlider = ::GetDlgItem( m_hDlg, IDC_SLIDER_SPEED );
  550. ::SendMessage( hSlider, TBM_SETPOS, true, m_iOriginalRateSliderPos );
  551. // Enable the Preview Voice button
  552. ::EnableWindow( ::GetDlgItem( g_pTTSDlg->GetHDlg(), IDC_SPEAK ), TRUE );
  553. }
  554. else
  555. {
  556. // Warn the user of failure
  557. WCHAR szError[MAX_LOADSTRING];
  558. szError[0] = 0;
  559. LoadString( _Module.GetResourceInstance(), IDS_TTS_ERROR, szError, MAX_LOADSTRING);
  560. MessageBox( GetHDlg(), szError, m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout );
  561. // Disable the Preview Voice button
  562. ::EnableWindow( ::GetDlgItem( GetHDlg(), IDC_SPEAK ), FALSE );
  563. }
  564. // Put text corresponding to this voice into the edit control
  565. PopulateEditCtrl(m_cpCurVoiceToken);
  566. // Kick the UI
  567. KickCPLUI();
  568. }
  569. return hr;
  570. } /* CTTSDlg::DefaultVoiceChange */
  571. /*****************************************************************************
  572. * CTTSDlg::SetCheckmark *
  573. *-----------------------*
  574. * Description:
  575. * Sets the specified item in the list control to be either checked
  576. * or unchecked (as the default voice)
  577. ******************************************************************************/
  578. void CTTSDlg::SetCheckmark( HWND hList, int iIndex, bool bCheck )
  579. {
  580. m_fForceCheckStateChange = true;
  581. ListView_SetCheckState( hList, iIndex, bCheck );
  582. m_fForceCheckStateChange = false;
  583. } /* CTTSDlg::SetCheckmark */
  584. /*****************************************************************************
  585. * CTTSDlg::KickCPLUI *
  586. *--------------------*
  587. * Description:
  588. * Determines if there is anything to apply right now
  589. ******************************************************************************/
  590. void CTTSDlg::KickCPLUI()
  591. {
  592. bool fChanged = false;
  593. // Compare IDs
  594. CSpDynamicString dstrSelTokenID;
  595. CSpDynamicString dstrOriginalDefaultTokenID;
  596. HRESULT hr = E_FAIL;
  597. if ( m_cpOriginalDefaultVoiceToken && m_cpCurVoiceToken )
  598. {
  599. hr = m_cpCurVoiceToken->GetId( &dstrSelTokenID );
  600. }
  601. if ( SUCCEEDED( hr ) )
  602. {
  603. hr = m_cpOriginalDefaultVoiceToken->GetId( &dstrOriginalDefaultTokenID );
  604. }
  605. if ( SUCCEEDED( hr )
  606. && ( 0 != wcsicmp( dstrOriginalDefaultTokenID, dstrSelTokenID ) ) )
  607. {
  608. fChanged = true;
  609. }
  610. // Check the audio device
  611. if ( m_pAudioDlg && m_pAudioDlg->IsAudioDeviceChanged() )
  612. {
  613. fChanged = true;
  614. }
  615. // Check the voice rate
  616. int iSpeed = (int) ::SendDlgItemMessage( m_hDlg, IDC_SLIDER_SPEED,
  617. TBM_GETPOS, 0, 0 );
  618. if ( m_iOriginalRateSliderPos != iSpeed )
  619. {
  620. fChanged = true;
  621. }
  622. // Tell the main propsheet
  623. HWND hwndParent = ::GetParent( m_hDlg );
  624. ::SendMessage( hwndParent, fChanged ? PSM_CHANGED : PSM_UNCHANGED, (WPARAM)(m_hDlg), 0 );
  625. } /* CTTSDlg::KickCPLUI */
  626. /*****************************************************************************
  627. * CTTSDlg::ChangeSpeed *
  628. *----------------------*
  629. * Description:
  630. * Called when the slider has moved.
  631. * Adjusts the speed of the voice
  632. ****************************************************************** BECKYW ***/
  633. void CTTSDlg::ChangeSpeed()
  634. {
  635. HWND hSlider = ::GetDlgItem( m_hDlg, IDC_SLIDER_SPEED );
  636. m_iSpeed = (int)::SendMessage( hSlider, TBM_GETPOS, 0, 0 );
  637. m_cpVoice->SetRate( m_iSpeed );
  638. KickCPLUI();
  639. } /* CTTSDlg::ChangeSpeed */
  640. /*****************************************************************************
  641. * CTTSDlg::ChangeSpeakButton *
  642. *----------------------------*
  643. * Description:
  644. * Changes the text in the "Preview Voice" button in order to
  645. * reflect whether there is currently a speak happening.
  646. ****************************************************************** BECKYW ***/
  647. void CTTSDlg::ChangeSpeakButton()
  648. {
  649. WCHAR pszButtonCaption[ MAX_LOADSTRING ];
  650. HWND hButton = ::GetDlgItem( m_hDlg, IDC_SPEAK );
  651. ::LoadString( _Module.GetResourceInstance(), m_bIsSpeaking ? IDS_STOP_PREVIEW : IDS_PREVIEW,
  652. pszButtonCaption, MAX_LOADSTRING );
  653. ::SendMessage( hButton, WM_SETTEXT, 0, (LPARAM) pszButtonCaption );
  654. if (!m_bIsSpeaking)
  655. {
  656. ::SetFocus(GetDlgItem(m_hDlg, IDC_SPEAK));
  657. }
  658. } /* CTTSDlg::ChangeSpeakButton */
  659. /*****************************************************************************
  660. * CTTSDlg::Speak *
  661. *----------------*
  662. * Description:
  663. * Speaks the contents of the edit control.
  664. * If it is already speaking, shuts up.
  665. ****************************************************************** BECKYW ***/
  666. void CTTSDlg::Speak()
  667. {
  668. SPDBG_FUNC( "CTTSDlg::Speak" );
  669. if ( m_bIsSpeaking )
  670. {
  671. // This forces the voice to stop speaking
  672. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  673. m_bIsSpeaking = false;
  674. ChangeSpeakButton();
  675. }
  676. else
  677. {
  678. ChangeSpeed();
  679. GETTEXTEX gtex = { sp_countof(m_wszCurSpoken), GT_DEFAULT, 1200, NULL, NULL };
  680. m_wszCurSpoken[0] = 0;
  681. LRESULT cChars = ::SendDlgItemMessage(m_hDlg,
  682. IDC_EDIT_SPEAK, EM_GETTEXTEX, (WPARAM)&gtex, (LPARAM)m_wszCurSpoken);
  683. if (cChars)
  684. {
  685. HRESULT hr = m_cpVoice->Speak(m_wszCurSpoken, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
  686. if ( SUCCEEDED( hr ) )
  687. {
  688. m_bIsSpeaking = true;
  689. ::SetFocus(GetDlgItem(m_hDlg, IDC_EDIT_SPEAK));
  690. ChangeSpeakButton();
  691. }
  692. else
  693. {
  694. // Warn the user that he needs to apply the changes
  695. WCHAR szError[MAX_LOADSTRING];
  696. szError[0] = 0;
  697. LoadString( _Module.GetResourceInstance(), IDS_SPEAK_ERROR, szError, MAX_LOADSTRING);
  698. MessageBox( GetHDlg(), szError, m_szCaption, MB_ICONWARNING | g_dwIsRTLLayout );
  699. }
  700. }
  701. }
  702. } /* CTTSDlg::Speak */
  703. /*****************************************************************************
  704. * CTTSDlg::StopSpeak *
  705. *----------------*
  706. * Description:
  707. * Stops the voice from speaking.
  708. * If it is already speaking, shuts up.
  709. ****************************************************************** BECKYW ***/
  710. void CTTSDlg::StopSpeak()
  711. {
  712. SPDBG_FUNC( "CTTSDlg::StopSpeak" );
  713. if ( m_bIsSpeaking )
  714. {
  715. // This forces the voice to stop speaking
  716. m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
  717. m_bIsSpeaking = false;
  718. }
  719. ChangeSpeakButton();
  720. } /* CTTSDlg::StopSpeak */
  721. /*****************************************************************************
  722. * CTTSDlg::NotifyCallback *
  723. *-------------------------*
  724. * Description:
  725. * Callback function that highlights words as they are spoken.
  726. ****************************************************************** MIKEAR ***/
  727. STDMETHODIMP CTTSDlg::NotifyCallback(WPARAM /* wParam */, LPARAM /* lParam */)
  728. {
  729. SPDBG_FUNC( "CTTSDlg::NotifyCallback" );
  730. SPVOICESTATUS Stat;
  731. m_cpVoice->GetStatus(&Stat, NULL);
  732. WPARAM nStart;
  733. LPARAM nEnd;
  734. if (Stat.dwRunningState & SPRS_DONE)
  735. {
  736. nStart = nEnd = 0;
  737. m_bIsSpeaking = false;
  738. ChangeSpeakButton();
  739. // Set the selection to an IP at the start of the text to speak
  740. ::SendDlgItemMessage( m_hDlg, IDC_EDIT_SPEAK, EM_SETSEL, 0, 0 );
  741. }
  742. else
  743. {
  744. nStart = (LPARAM)Stat.ulInputWordPos;
  745. nEnd = nStart + Stat.ulInputWordLen;
  746. CHARRANGE cr;
  747. cr.cpMin = (LONG)nStart;
  748. cr.cpMax = (LONG)nEnd;
  749. ::SendDlgItemMessage( m_hDlg, IDC_EDIT_SPEAK, EM_EXSETSEL, 0, (LPARAM) &cr );
  750. }
  751. return S_OK;
  752. } /* CTTSDlg::NotifyCallback */