#include "stdafx.h" #include "resource.h" #include #include #include "SRDlg.h" #include #include #include "helpresource.h" #include "richedit.h" #include "mlang.h" #include "Lmcons.h" static DWORD aKeywordIds[] = { // Control ID // Help Context ID IDC_ADD, IDH_ADD, IDC_MODIFY, IDH_SETTINGS, IDC_DELETE, IDH_DELETE, IDC_TRN_ADVICE, IDH_NOHELP, IDC_USER, IDH_USER, IDC_MIC_ICON, IDH_NOHELP, IDC_COMBO_RECOGNIZERS, IDH_ENGINES, IDC_SR_ADV, IDH_SR_ADV, IDC_USERTRAINING, IDH_USERTRAINING, IDC_PROGRESS1, IDH_PROGRESS1, IDC_AUD_IN, IDH_AUD_IN, IDC_MICWIZ, IDH_MICWIZ, IDC_SR_ICON, IDH_NOHELP, IDC_SR_CAPTION, IDH_NOHELP, IDC_SR_LIST_CAP, IDH_NOHELP, IDC_TRAIN_GROUP, IDH_NOHELP, IDC_ADVICE, IDH_NOHELP, IDC_IN_GROUP, IDH_NOHELP, IDC_MIC_CAP, IDH_NOHELP, IDC_MIC_INST, IDH_NOHELP, 0, 0 }; /***************************************************************************** * CSRDlg::CreateRecoContext * *---------------------------* * Description: * This creates a new instance of the recognizer with whatever is the * current defaults for the recognizer. * The "fInitialize" argument is FALSE by default. If set, it does * NOT attempt to set the m_pCurUserToken reco profile and instead * just picks up whatever CoCreateInstance() on the shared recognizer * gave it. * NOTE: The caller is responsible for displaying error messages to * the user when this fails. * Return: * S_OK * Failed HRESULT from recognizer/recocontext initialization functions ****************************************************************** BECKYW ***/ HRESULT CSRDlg::CreateRecoContext(BOOL *pfContextInitialized, BOOL fInitialize, ULONG ulFlags) { // Kill the reco context and notify sink first, if we have one if ( m_cpRecoCtxt ) { m_cpRecoCtxt->SetNotifySink( NULL ); } m_cpRecoCtxt.Release(); HRESULT hr; // SOFTWARE ENGINEERING OPPORTUNITY (beckyw 8/24): This is a workaround for a // bug that appears to repro only on my dev machine, in which the recostate // needs to be inactive for this whole thing. if ( m_cpRecoEngine ) { m_cpRecoEngine->SetRecoState( SPRST_INACTIVE ); } if ( m_cpRecoEngine ) { SPRECOSTATE recostate; hr = m_cpRecoEngine->GetRecoState( &recostate ); // This is due to a SOFTWARE ENGINEERING OPPORTUNITY in which SetRecognizer( NULL ) // doesn't work if the recostate is SPRST_ACTIVE_ALWAYS. // In this case, we temporarily switch the recostate if ( SUCCEEDED( hr ) && (SPRST_ACTIVE_ALWAYS == recostate) ) { hr = m_cpRecoEngine->SetRecoState( SPRST_INACTIVE ); } // Kick the recognizer if ( SUCCEEDED( hr ) && (ulFlags & SRDLGF_RECOGNIZER) ) { hr = m_cpRecoEngine->SetRecognizer( NULL ); } // Kick the audio input if ( SUCCEEDED( hr ) && (ulFlags & SRDLGF_AUDIOINPUT)) { hr = m_cpRecoEngine->SetInput( NULL, TRUE ); } // Set the recostate back if we changed it. if ( (SPRST_ACTIVE_ALWAYS == recostate) ) { HRESULT hrRecoState = m_cpRecoEngine->SetRecoState( recostate ); if ( FAILED( hrRecoState ) ) { hr = hrRecoState; } } } else { hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpSharedRecognizer ); } if(!fInitialize && SUCCEEDED( hr )) { // Normally set to m_pCurUserToken // When initializing this is not created yet so just set to default hr = m_cpRecoEngine->SetRecoProfile(m_pCurUserToken); } if ( SUCCEEDED( hr ) ) { hr = m_cpRecoEngine->CreateRecoContext(&m_cpRecoCtxt); } if ( SUCCEEDED( hr ) ) { hr = m_cpRecoCtxt->SetNotifyWindowMessage(m_hDlg, WM_RECOEVENT, 0, 0); } if ( SUCCEEDED( hr ) ) { const ULONGLONG ullInterest = SPFEI(SPEI_SR_AUDIO_LEVEL); hr = m_cpRecoCtxt->SetInterest(ullInterest, ullInterest); } // Set the pfContextInitialized flag if everything has gone OK; // if something has not gone OK, clean up if ( pfContextInitialized ) { // If we got here, the reco context has been initialized *pfContextInitialized = SUCCEEDED( hr ); } if ( FAILED( hr )) { m_cpRecoCtxt.Release(); m_cpRecoEngine.Release(); return hr; } #ifdef _DEBUG // Let's make sure we actually have the right recognizer now CComPtr cpCurDefaultToken; // What it should be SpGetDefaultTokenFromCategoryId(SPCAT_RECOGNIZERS, &cpCurDefaultToken); CComPtr cpRecognizerToken; m_cpRecoEngine->GetRecognizer( &cpRecognizerToken ); if ( cpRecognizerToken ) { CSpDynamicString dstrCurDefaultToken; cpCurDefaultToken->GetId( &dstrCurDefaultToken ); CSpDynamicString dstrRecognizerToken; cpRecognizerToken->GetId( &dstrRecognizerToken ); if ( 0 != wcsicmp( dstrCurDefaultToken, dstrRecognizerToken ) ) { OutputDebugString( L"Warning: We just created a recognizer that isn't the default!\n" ); } } #endif // Now turn on the reco state for the volume meter hr = m_cpRecoEngine->SetRecoState( SPRST_ACTIVE_ALWAYS ); return(hr); } /***************************************************************************** * SortCols * *-----------* * Description: * Comparison function for subitems in the reco list ****************************************************************** BRENTMID ***/ int CALLBACK SortCols( LPARAM pToken1, LPARAM pToken2, LPARAM pDefToken ) { USES_CONVERSION; // Get the names CSpDynamicString dstrDesc1; CSpDynamicString dstrDesc2; SpGetDescription( (ISpObjectToken *) pToken1, &dstrDesc1 ); SpGetDescription( (ISpObjectToken *) pToken2, &dstrDesc2 ); // First check if there is no description for either one. // If there is no description, set it to "" if ( !dstrDesc1.m_psz || !dstrDesc2.m_psz ) { WCHAR szNoName[ MAX_LOADSTRING ]; szNoName[0] = 0; ::LoadString( _Module.GetResourceInstance(), IDS_UNNAMED_RECOPROFILE, szNoName, sp_countof( szNoName ) ); USES_CONVERSION; if ( !dstrDesc1 ) { dstrDesc1 = szNoName; SpSetDescription( (ISpObjectToken *) pToken1, dstrDesc1 ); } if ( !dstrDesc2 ) { dstrDesc2 = szNoName; SpSetDescription( (ISpObjectToken *) pToken2, dstrDesc2 ); } } if (pDefToken == pToken1) { return -1; // make sure pToken1 goes to top of list } else if (pDefToken == pToken2) { return 1; // make sure pToken2 goes to top of list } // PREFIX: verify memory allocation // if we failed a memory allocation somewhere return that they are equal so the sort will just leave them alone. if ((NULL == dstrDesc1.m_psz) || (NULL == dstrDesc2.m_psz)) { return 0; } return wcscmp(_wcslwr(dstrDesc1.m_psz), _wcslwr(dstrDesc2.m_psz)); } /***************************************************************************** * CSRDlg::RecoEvent * *-------------------* * Description: * Handles the SR events for the volume meter ****************************************************************** BRENTMID ***/ void CSRDlg::RecoEvent() { CSpEvent event; if (m_cpRecoCtxt) { while (event.GetFrom(m_cpRecoCtxt) == S_OK) { if (event.eEventId == SPEI_SR_AUDIO_LEVEL) { ULONG l = (ULONG)event.wParam; SendMessage( GetDlgItem ( m_hDlg, IDC_PROGRESS1 ), PBM_SETPOS, l, 0); } } } } /***************************************************************************** * TrySwitchDefaultEngine * *------------------------* * Description: * This function is called when we want to run some UI for the engine * the user has selected, but because we don't know which shared engine * is running and whether another app is using it we can't directly * create the UI. So this method temporarily switches the default recognizer, * and recreates the engine, and then checks its token. If another app * was using the engine we wouldn't be able to switch and we return S_FALSE. * A side effect of this method is that for the duration of the UI, the * default will be changed, even though the user hasn't yet pressed apply, * but there seems no good way round this. * * In the case that m_pCurRecoToken is actually the same token as the * one the currently-active recognizer uses, we don't need to create * a new recognizer and recocontext; instead we just return successfully. * Return: * S_OK * FAILED HRESULT of various functions * In particular, SPERR_ENGINE_BUSY means that someone else is * running the engine, so this couldn't be done. ****************************************************************** DAVEWOOD ***/ HRESULT CSRDlg::TrySwitchDefaultEngine( bool fShowErrorMessages) { HRESULT hr = S_OK; bool fMatch = false; // Set the new temporary default if(SUCCEEDED(hr)) { hr = SpSetDefaultTokenForCategoryId(SPCAT_RECOGNIZERS, m_pCurRecoToken); } if ( SUCCEEDED( hr ) && IsRecoTokenCurrentlyBeingUsed( m_pCurRecoToken ) ) { // No need to switch engine all over again: just keep the one in use return S_OK; } // Try to create the engine & context with the default // then see if this was actually the engine we expected if(SUCCEEDED(hr)) { hr = CreateRecoContext( ); } if ( FAILED( hr ) && fShowErrorMessages ) { WCHAR szError[256]; szError[0] = '\0'; // What to complain about... UINT uiErrorID = HRESULTToErrorID( hr ); if ( uiErrorID ) { LoadString(_Module.GetResourceInstance(), uiErrorID, szError, sp_countof(szError)); MessageBox(g_pSRDlg->m_hDlg, szError, m_szCaption, MB_ICONWARNING|g_dwIsRTLLayout); } } return hr; } /***************************************************************************** * CSRDlg::ResetDefaultEngine * *----------------------------* * Description: * This function resets the engine default back to its original value. * If the engine already has the right token, it doesn't bother trying * to create the engine again and returns S_OK * Return: * S_OK * S_FALSE if the default was set back but no engine was created * FAILED HRESULT of SpSetDefaultTokenForCategoryId() ****************************************************************** DAVEWOOD ***/ HRESULT CSRDlg::ResetDefaultEngine( bool fShowErrorMessages ) { HRESULT hr = S_OK; // Reset the old default if(m_pDefaultRecToken) { hr = SpSetDefaultTokenForCategoryId(SPCAT_RECOGNIZERS, m_pDefaultRecToken); } HRESULT hrRet = hr; BOOL fContextInitialized = FALSE; if ( SUCCEEDED( hr ) ) { if ( IsRecoTokenCurrentlyBeingUsed( m_pDefaultRecToken ) ) { // No need to switch engine all over again: just keep the one in use if ( m_cpRecoCtxt ) { fContextInitialized = TRUE; } else { hr = SPERR_UNINITIALIZED; } // The UI might have monkeyed with the recostate. // Just in case, let's set it back to ACTIVE_ALWAYS if ( SUCCEEDED( hr ) ) { hr = m_cpRecoEngine->SetRecoState( SPRST_ACTIVE_ALWAYS ); } } else { // Create the engine & context using the old default hr = g_pSRDlg->CreateRecoContext( &fContextInitialized ); } } if ( FAILED( hr ) ) { BOOL fContextInitialized = FALSE; hr = g_pSRDlg->CreateRecoContext( &fContextInitialized ); // Let's not complain about unsupported languages twice as this may be confusing // to the user. if ( FAILED( hr ) && ( SPERR_UNSUPPORTED_LANG != hr ) ) { RecoContextError( fContextInitialized, fShowErrorMessages, hr ); // The default was set back but no engine was successfully set up. // A FAILED hresult is not necessary here since the user // has been notified of the error hrRet = S_FALSE; } // Gray out all the buttons ::EnableWindow(::GetDlgItem(m_hDlg, IDC_USERTRAINING), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MICWIZ), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_SR_ADV), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MODIFY), FALSE); } return hrRet; } /* CSRDlg::ResetDefaultEngine */ /***************************************************************************** * CSRDlg::IsRecoTokenCurrentlyBeingUsed * *---------------------------------------* * Description: * Call GetRecognizer() on the recognizer currently in use, and * compare IDs ****************************************************************** BECKYW ****/ bool CSRDlg::IsRecoTokenCurrentlyBeingUsed( ISpObjectToken *pRecoToken ) { if ( !pRecoToken || !m_cpRecoEngine ) { return false; } CComPtr cpRecoTokenInUse; HRESULT hr = m_cpRecoEngine->GetRecognizer( &cpRecoTokenInUse ); CSpDynamicString dstrTokenID; CSpDynamicString dstrTokenInUseID; if ( SUCCEEDED( hr ) ) { hr = pRecoToken->GetId( &dstrTokenID ); } if ( SUCCEEDED( hr ) ) { hr = cpRecoTokenInUse->GetId( &dstrTokenInUseID ); } return ( SUCCEEDED( hr ) && (0 == wcscmp(dstrTokenID, dstrTokenInUseID)) ); } /* CSRDlg::IsRecoTokenCurrentlyBeingUsed */ /***************************************************************************** * CSRDlg::HasRecognizerChanged * *------------------------------* * Description: * Look at the currently-requested default recognizer, compare against the * original default recognizer, and return true iff it is * different ****************************************************************** BECKYW ****/ bool CSRDlg::HasRecognizerChanged() { bool fChanged = false; // Check the recognizer token CSpDynamicString dstrCurDefaultRecognizerID; CSpDynamicString dstrCurSelectedRecognizerID; HRESULT hr = E_FAIL; if ( m_pDefaultRecToken ) { hr = m_pDefaultRecToken->GetId( &dstrCurDefaultRecognizerID ); } if ( SUCCEEDED( hr ) && m_pCurRecoToken ) { hr = m_pCurRecoToken->GetId( &dstrCurSelectedRecognizerID ); } if (SUCCEEDED( hr ) && ( 0 != wcsicmp( dstrCurDefaultRecognizerID, dstrCurSelectedRecognizerID ) )) { fChanged = true; } return fChanged; } /* CSRDlg::HasRecognizerChanged */ /***************************************************************************** * CSRDlg::KickCPLUI * *-------------------* * Description: * Look at the currently-requested defaults, compare against the * original defaults, and enable the Apply button iff anything is * different ****************************************************************** BECKYW ****/ void CSRDlg::KickCPLUI() { // Check the default recognizer token bool fChanged = HasRecognizerChanged(); // Check the default user token CSpDynamicString dstrCurSelectedProfileID; HRESULT hr = E_FAIL; if ( m_pCurUserToken ) { hr = m_pCurUserToken->GetId( &dstrCurSelectedProfileID ); } if (SUCCEEDED( hr ) && m_dstrOldUserTokenId && ( 0 != wcsicmp( dstrCurSelectedProfileID, m_dstrOldUserTokenId ) )) { fChanged = true; } // Check the audio input device if ( m_pAudioDlg && m_pAudioDlg->IsAudioDeviceChanged() ) { fChanged = true; } // If any tokens have been deleted, there has been a change if ( m_iDeletedTokens > 0 ) { fChanged = true; } // If any tokens have been added, there has been a change if ( m_iAddedTokens > 0 ) { fChanged = true; } // Tell the main propsheet HWND hwndParent = ::GetParent( m_hDlg ); ::SendMessage( hwndParent, fChanged ? PSM_CHANGED : PSM_UNCHANGED, (WPARAM)(m_hDlg), 0 ); } /* CSRDlg::KickCPLUI */ /***************************************************************************** * CSRDlg::RecoContextError * *--------------------------* * Description: * Reacts to an error generated by trying to create and set up the * recognition context within the CPL by displaying an error message * and graying out the UI. ****************************************************************** BECKYW ****/ void CSRDlg::RecoContextError( BOOL fRecoContextExists, BOOL fGiveErrorMessage, HRESULT hrRelevantError ) { // Complain about the appropriate problem, if needed if ( fGiveErrorMessage ) { WCHAR szError[256]; szError[0] = '\0'; // Figure out what error to talk about UINT uiErrorID = 0; if ( fRecoContextExists ) { // There is a reco context but it couldn't be turned on uiErrorID = IDS_METER_WARNING; } else { uiErrorID = HRESULTToErrorID( hrRelevantError ); } if ( uiErrorID ) { LoadString(_Module.GetResourceInstance(), uiErrorID, szError, sp_countof(szError)); MessageBox(m_hDlg, szError, m_szCaption, MB_ICONWARNING|g_dwIsRTLLayout); } } // Gray out all the buttons if ( !fRecoContextExists ) { ::EnableWindow(::GetDlgItem(m_hDlg, IDC_USERTRAINING), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MICWIZ), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_SR_ADV), FALSE); ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MODIFY), FALSE); } } /* CSRDlg::RecoContextError */ /***************************************************************************** * CSRDlg::HRESULTToErrorID * *--------------------------* * Description: * Translates a failed HRESULT from a recognizer/recocontext * initializion into a resource string ID ****************************************************************** BECKYW ****/ UINT CSRDlg::HRESULTToErrorID( HRESULT hr ) { if ( SUCCEEDED( hr ) ) { return 0; } // What to complain about... UINT uiErrorID; switch( hr ) { case SPERR_ENGINE_BUSY: uiErrorID = IDS_ENGINE_IN_USE_WARNING; break; case SPERR_UNSUPPORTED_LANG: uiErrorID = IDS_UNSUPPORTED_LANG; break; default: // Generic error uiErrorID = IDS_ENGINE_SWITCH_ERROR; break; } return uiErrorID; } /* CSRDlg::HRESULTToErrorID */ /***************************************************************************** * CSRDlg::IsProfileNameInvisible * *--------------------------------* * Description: * A profile name is "invisible" iff it is the name of an existing * profile AND it is on the pending deletes list AND it is does not * exist for any tokens off the pending deletes list ****************************************************************** BECKYW ****/ bool CSRDlg::IsProfileNameInvisible( WCHAR *pwszProfile ) { if ( !pwszProfile ) { return false; } bool fIsInvisible = false; for ( int i=0; !fIsInvisible && (i < m_iDeletedTokens); i++ ) { ISpObjectToken *pDeletedToken = m_aDeletedTokens[i]; if ( !pDeletedToken ) { continue; } CSpDynamicString dstrDeletedDesc; HRESULT hr = SpGetDescription( pDeletedToken, &dstrDeletedDesc ); if ( FAILED( hr ) ) { continue; } if ( 0 == wcscmp( dstrDeletedDesc, pwszProfile ) ) { bool fOnList = false; // Now go through everything on the recoprofile list // that is visible to the user int cItems = ListView_GetItemCount( m_hUserList ); for ( int j=0; !fOnList && (j < cItems); j++ ) { LVITEM lvitem; ::memset( &lvitem, 0, sizeof( lvitem ) ); lvitem.iItem = j; lvitem.mask = LVIF_PARAM; BOOL fSuccess = ListView_GetItem( m_hUserList, &lvitem ); ISpObjectToken *pVisibleToken = fSuccess ? (ISpObjectToken *) lvitem.lParam : NULL; if ( pVisibleToken ) { CSpDynamicString dstrVisible; hr = SpGetDescription( pVisibleToken, &dstrVisible ); if ( SUCCEEDED( hr ) && (0 == wcscmp( dstrVisible, pwszProfile )) ) { fOnList = true; } } } if ( !fOnList ) { // The name matches something on the deleted list, // but it appears nowhere on the list of profiles visible // to the user. fIsInvisible = true; } } } return fIsInvisible; } /* IsProfileNameInvisible */ /***************************************************************************** * SRDlgProc * *-----------* * Description: * DLGPROC for managing recognition engines ****************************************************************** MIKEAR ***/ INT_PTR CALLBACK SRDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { SPDBG_FUNC( "SRDlgProc" ); USES_CONVERSION; switch (uMsg) { case WM_RECOEVENT: { g_pSRDlg->RecoEvent(); break; } case WM_DRAWITEM: // draw the items { g_pSRDlg->OnDrawItem( hWnd, ( DRAWITEMSTRUCT * )lParam ); break; } case WM_INITDIALOG: { g_pSRDlg->OnInitDialog(hWnd); break; } case WM_DESTROY: { g_pSRDlg->OnDestroy(); break; } // Handle the context sensitive help case WM_CONTEXTMENU: { WinHelp((HWND) wParam, CPL_HELPFILE, HELP_CONTEXTMENU, (DWORD_PTR)(LPWSTR) aKeywordIds); break; } case WM_HELP: { WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, CPL_HELPFILE, HELP_WM_HELP,(DWORD_PTR)(LPWSTR) aKeywordIds); break; } case WM_NOTIFY: switch (((NMHDR*)lParam)->code) { case LVN_DELETEITEM: { if (wParam != IDC_USER) { break; } if (g_pSRDlg->m_fDontDelete) { break; } ISpObjectToken *pToken = (ISpObjectToken*)(((NMLISTVIEW*)lParam)->lParam); if (pToken) { pToken->Release(); } break; } case LVN_ITEMCHANGED: { // Code ends up here when a profile is added, deleted, or changed // We verify that we weren't selected before, but are now // and then kill the current reco context, deactivate the engine, change the profile // and fire everything back up again if ( IDC_USER == wParam ) { LPNMLISTVIEW lplv = (LPNMLISTVIEW) lParam; if ( !(lplv->uOldState & LVIS_FOCUSED) && lplv->uNewState & LVIS_FOCUSED ) { if ( g_pSRDlg->m_cpRecoEngine && g_pSRDlg->m_cpRecoCtxt ) { HRESULT hr; ISpObjectToken *pSelectedToken = (ISpObjectToken *) lplv->lParam; hr = g_pSRDlg->m_cpRecoEngine->SetRecoState( SPRST_INACTIVE ); if ( SUCCEEDED( hr ) ) { hr = g_pSRDlg->m_cpRecoEngine->SetRecoProfile( pSelectedToken ); // Restart audio regardless of success of SetRecoProfile g_pSRDlg->m_cpRecoEngine->SetRecoState(SPRST_ACTIVE_ALWAYS); if ( FAILED( hr ) ) { WCHAR szError[256]; szError[0] = '\0'; LoadString(_Module.GetResourceInstance(), IDS_PROFILE_WARNING, szError, sp_countof(szError)); MessageBox(g_pSRDlg->m_hDlg, szError, g_pSRDlg->m_szCaption, MB_ICONWARNING|g_dwIsRTLLayout); } } if ( SUCCEEDED( hr ) ) { // This is now the new default g_pSRDlg->m_pCurUserToken = pSelectedToken; g_pSRDlg->UserSelChange( lplv->iItem ); } } } } break; } case PSN_APPLY: { g_pSRDlg->OnApply(); break; } case PSN_QUERYCANCEL: // user clicks the Cancel button { g_pSRDlg->OnCancel(); break; } } break; case WM_COMMAND: if (CBN_SELCHANGE == HIWORD(wParam)) { g_pSRDlg->EngineSelChange(); } else if (HIWORD(wParam) == BN_CLICKED) { HRESULT hr = S_OK; if (LOWORD(wParam) == IDC_MODIFY) // the "Modify" button { hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if ( SUCCEEDED( hr ) ) { g_pSRDlg->ProfileProperties(); } // Switch back to original default, complaining about errors only if there // wasn't a complaint from the call to TrySwitchDefaultEngine hr = g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr )); } else if (LOWORD(wParam) == IDC_ADD) // the "Add" button { // The engine we want to add this user for may not be the currently- // running engine. Try and switch it, and complain if there's // a problem hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if ( SUCCEEDED( hr ) ) { g_pSRDlg->CreateNewUser(); } // Switch back to original default, but complain about errors // only if the UI actually succeeded in showing g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr ) ); } else if (LOWORD(wParam) == IDC_DELETE) // the "Delete" button { g_pSRDlg->DeleteCurrentUser(); } else if (LOWORD(wParam) == IDC_SR_ADV) { // The engine we want to display UI for may not be the currently // running engine. Try and switch it hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if(SUCCEEDED(hr)) { // display the UI w/ the new temporary default g_pSRDlg->m_pCurRecoToken->DisplayUI(hWnd, NULL, SPDUI_EngineProperties, NULL, 0, g_pSRDlg->m_cpRecoEngine); } // Switch back to original default, complaining about errors only if there // wasn't a complaint from the call to TrySwitchDefaultEngine hr = g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr )); } else if(LOWORD(wParam) == IDC_USERTRAINING) { // The engine we want to display UI for may not be the currently // running engine. Try and switch it hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if(SUCCEEDED(hr)) { // display the UI w/ the new temporary default SPDBG_ASSERT( g_pSRDlg->m_cpRecoEngine ); g_pSRDlg->m_cpRecoEngine->DisplayUI(hWnd, NULL, SPDUI_UserTraining, NULL, 0); } // Switch back to original default, complaining about errors only if there // wasn't a complaint from the call to TrySwitchDefaultEngine hr = g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr )); } else if(LOWORD(wParam) == IDC_MICWIZ) { // The engine we want to display UI for may not be the currently // running engine. Try and switch it hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if(SUCCEEDED(hr)) { // display the UI w/ the new temporary default SPDBG_ASSERT( g_pSRDlg->m_cpRecoEngine ); g_pSRDlg->m_cpRecoEngine->DisplayUI(hWnd, NULL, SPDUI_MicTraining, NULL, 0); } // Switch back to original default, complaining about errors only if there // wasn't a complaint from the call to TrySwitchDefaultEngine hr = g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr )); } else if (LOWORD(wParam) == IDC_AUD_IN) { // The m_pAudioDlg will be non-NULL only if the audio dialog // has been previously brough up. // Otherwise, we need a newly-initialized one if ( !g_pSRDlg->m_pAudioDlg ) { g_pSRDlg->m_pAudioDlg = new CAudioDlg( eINPUT ); } ::DialogBoxParam( _Module.GetResourceInstance(), MAKEINTRESOURCE( IDD_AUDIO_DEFAULT ), hWnd, AudioDlgProc, (LPARAM) g_pSRDlg->m_pAudioDlg ); if ( g_pSRDlg->m_pAudioDlg->IsAudioDeviceChangedSinceLastTime() ) { // Warn the user that he needs to apply the changes WCHAR szWarning[MAX_LOADSTRING]; szWarning[0] = 0; LoadString( _Module.GetResourceInstance(), IDS_AUDIOIN_CHANGE_WARNING, szWarning, MAX_LOADSTRING); MessageBox( g_pSRDlg->GetHDlg(), szWarning, g_pSRDlg->m_szCaption, MB_ICONWARNING |g_dwIsRTLLayout); } // Kick the Apply button g_pSRDlg->KickCPLUI(); } } break; } return FALSE; } /* SRDlgProc */ /**************************************************************************** * CSRDlg::CreateNewUser * *-------------------------* * Description: Adds a new speech user profile to the registry * * Returns: * ********************************************************************* RAL ***/ void CSRDlg::CreateNewUser() { SPDBG_FUNC("CSRDlg::CreateNewUser"); HRESULT hr = S_OK; // Make sure that we haven't already added too many profiles to keep track of if ( m_iAddedTokens >= iMaxAddedProfiles_c ) { WCHAR wszError[ MAX_LOADSTRING ]; ::LoadString( _Module.GetResourceInstance(), IDS_MAX_PROFILES_EXCEEDED, wszError, MAX_LOADSTRING ); ::MessageBox( m_hDlg, wszError, m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout ); return; } CComPtr cpNewToken; hr = SpCreateNewToken(SPCAT_RECOPROFILES, NULL, &cpNewToken); if (SUCCEEDED(hr)) { if (!UserPropDlg(cpNewToken)) // User canceled! { cpNewToken->Remove(NULL); } else { //set the default m_pCurUserToken = cpNewToken; // Put the new token on the added tokens list cpNewToken->GetId( &(m_aAddedTokens[ m_iAddedTokens++ ]) ); // make this the default after we edit it ChangeDefaultUser(); // This will make sure that it gets displayed. // Note that m_pCurUserToken will point an AddRefed ISpObjectToken * // after the call to PopulateList() PopulateList(); // Update the UI KickCPLUI(); } } else { WCHAR szError[MAX_LOADSTRING]; szError[0] = 0; LoadString(_Module.GetResourceInstance(), IDS_RECOPROFILE_ADD_ERROR, szError, MAX_LOADSTRING); MessageBox( m_hDlg, szError, m_szCaption, MB_ICONWARNING | g_dwIsRTLLayout); } // Only enable the delete button if there are 2 or more user profiles int iNumUsers = (int)::SendMessage(m_hUserList, LVM_GETITEMCOUNT, 0, 0); if (iNumUsers < 2) { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), FALSE); } else { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), TRUE); } // Sort the items initially ::SendMessage( m_hUserList, LVM_SORTITEMS, (LPARAM)m_pCurUserToken, LPARAM(&SortCols) ); } /**************************************************************************** * CSRDlg::UserPropDlg * *-----------------------* * Description: This is for when a user wants to add a new profile * * Returns: * ********************************************************************* BRENTMID ***/ HRESULT CSRDlg::UserPropDlg(ISpObjectToken * pToken) { SPDBG_FUNC("CSRDlg::UserPropDlg"); HRESULT hr = S_OK; CEnvrPropDlg Dlg(this, pToken); hr = (HRESULT)DialogBoxParam(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDD_PROF_WIZ), m_hDlg, CEnvrPropDlg::DialogProc, (LPARAM)(&Dlg)); return hr; } /**************************************************************************** * CEnvrPropDlg::InitDialog * *--------------------------* * Description: * * Returns: * ********************************************************************* RAL ***/ BOOL CEnvrPropDlg::InitDialog(HWND hDlg) { USES_CONVERSION; CSpDynamicString dstrDescription; m_hDlg = hDlg; // // Get the description if there is one... // SpGetDescription(m_cpToken, &dstrDescription); if (dstrDescription) { ::SendDlgItemMessage(hDlg, IDC_USER_NAME, WM_SETTEXT, 0, (LPARAM) dstrDescription.m_psz); ::SendDlgItemMessage(hDlg, IDC_USER_NAME, EM_LIMITTEXT, UNLEN, 0); } // We want the EN_CHANGE notifications from the edit control ::SendDlgItemMessage( hDlg, IDC_USER_NAME, EM_SETEVENTMASK, 0, ENM_CHANGE ); if (!m_isModify) { // Set the user name to the one in the registry, if found; // otherwise set it to the user name HKEY hkUserKey; LONG lUserOpen; WCHAR szUserName[ UNLEN + 1 ]; szUserName[0] = 0; DWORD dwUserLen = UNLEN + 1; lUserOpen = ::RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\MS Setup (ACME)\\User Info", 0, KEY_READ, &hkUserKey ); if ( lUserOpen == ERROR_SUCCESS ) { lUserOpen = RegQueryValueEx( hkUserKey, L"DefName", NULL, NULL, (BYTE *) szUserName, &dwUserLen ); RegCloseKey(hkUserKey); } if ( ERROR_SUCCESS != lUserOpen ) { // Just use the win32 user name BOOL fSuccess = ::GetUserName( szUserName, &dwUserLen ); if ( !fSuccess ) { szUserName[0] = 0; } } // Now put that in the edit box. // First check to make sure the name is nonempty // and enable the UI accordingly WCHAR *pwch; for ( pwch = szUserName; *pwch && iswspace( *pwch ); pwch++ ) { } ::EnableWindow( ::GetDlgItem( m_hDlg, IDOK ), (0 != *pwch) ); ::EnableWindow( ::GetDlgItem( m_hDlg, ID_NEXT ), (0 != *pwch) ); // Set the edit box to have the user's name // Need to use SETTEXTEX since this might contain wide chars SETTEXTEX stx; stx.flags = ST_DEFAULT; stx.codepage = 1200; ::SendDlgItemMessage( m_hDlg, IDC_USER_NAME, EM_SETTEXTEX, (WPARAM) &stx, (LPARAM) szUserName ); } ::SetFocus(::GetDlgItem(hDlg, IDC_USER_NAME)); return TRUE; } /**************************************************************************** * CEnvrPropDlg::ApplyChanges * *----------------------------* * Description: * * Returns: * ********************************************************************* RAL ***/ EPD_RETURN_VALUE CEnvrPropDlg::ApplyChanges() { USES_CONVERSION; SPDBG_FUNC("CEnvrPropDlg::ApplyChanges"); WCHAR szName[UNLEN + 1]; *szName = 0; GETTEXTEX gtex = { sp_countof(szName), GT_DEFAULT, 1200, NULL, NULL }; ::SendDlgItemMessage(m_hDlg, IDC_USER_NAME, EM_GETTEXTEX, (WPARAM)>ex, (LPARAM)szName); if (*szName == 0) { return EPD_FAILED; } // Check to see if this profile name already exists CComPtr cpEnum; ISpObjectToken *pToken; CSpDynamicString dstrDescription; CSpDynamicString dInputString; CSpDynamicString dstrOldTok; bool isDuplicate = false; HRESULT hr = SpEnumTokens(SPCAT_RECOPROFILES, NULL, NULL, &cpEnum); // Get the description of the currently selected profile dstrOldTok.Clear(); hr = SpGetDescription( m_pParent->m_pCurUserToken, &dstrOldTok ); while (cpEnum && cpEnum->Next(1, &pToken, NULL) == S_OK) { // Get the description of the enumerated token dstrDescription.Clear(); hr = SpGetDescription( pToken, &dstrDescription ); pToken->Release(); // Get the input string // PREFIX: verify memory alloc if (NULL == (dInputString = szName)) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { if ( wcscmp( dstrDescription.m_psz, dInputString.m_psz ) == 0 ) { // the name is duplicated isDuplicate = true; } } } if ( isDuplicate ) // this not a modify box and the user entered a duplicate name { return EPD_DUP; // tell the user about it } if (FAILED(SpSetDescription(m_cpToken, szName))) { return EPD_FAILED; } return EPD_OK; } /***************************************************************************** * EnvrPropDialogProc * *--------------------* * Description: * Mesage handler for User Name dialog ****************************************************************** BRENTMID ***/ INT_PTR CALLBACK CEnvrPropDlg::DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { USES_CONVERSION; static CSpUnicodeSupport unicode; CEnvrPropDlg * pThis = (CEnvrPropDlg *) unicode.GetWindowLongPtr(hDlg, GWLP_USERDATA); switch (message) { case WM_INITDIALOG: unicode.SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); pThis = (CEnvrPropDlg *)lParam; return pThis->InitDialog(hDlg); case WM_COMMAND: { if (( IDC_USER_NAME == LOWORD(wParam) ) && ( EN_CHANGE == HIWORD(wParam) )) { // Edit control contents have changed: // See if we should enable the "finish" and "next" buttons by getting the // text in the edit box and making sure it has at least one // non-whitespace character WCHAR szName[ UNLEN+1 ]; *szName = 0; GETTEXTEX gtex = { UNLEN, GT_DEFAULT, 1200, NULL, NULL }; ::SendDlgItemMessage(pThis->m_hDlg, IDC_USER_NAME, EM_GETTEXTEX, (WPARAM)>ex, (LPARAM)szName); WCHAR *pch = szName; for ( ; *pch && iswspace( *pch ); pch++ ) { } ::EnableWindow( ::GetDlgItem( pThis->m_hDlg, IDOK ), (0 != *pch) ); ::EnableWindow( ::GetDlgItem( pThis->m_hDlg, ID_NEXT ), (0 != *pch) ); break; } if( LOWORD(wParam) == IDCANCEL ) { EndDialog(hDlg, FALSE); return TRUE; } // user clicks the NEXT button if ( (LOWORD( wParam ) == ID_NEXT) || (LOWORD( wParam ) == IDOK) ) { EPD_RETURN_VALUE eRet = pThis->ApplyChanges(); if ( eRet == EPD_OK ) { if ( ID_NEXT == LOWORD(wParam) ) { // Launch the micwiz, if we can // Try to switch engines in case the user has changed engines // without applying HRESULT hr = g_pSRDlg->TrySwitchDefaultEngine( true ); if ( S_OK == hr ) { SPDBG_ASSERT( g_pSRDlg->m_cpRecoEngine ); if ( g_pSRDlg->m_cpRecoEngine ) { // Switch the recoprofile to the new one (might need to turn off // recostate first // Turn off recostate before calling SetRecoProfile() if necessary SPRECOSTATE eOldRecoState = SPRST_INACTIVE; g_pSRDlg->m_cpRecoEngine->GetRecoState( &eOldRecoState ); HRESULT hrRecoState = S_OK; if ( SPRST_INACTIVE != eOldRecoState ) { hrRecoState = g_pSRDlg->m_cpRecoEngine->SetRecoState( SPRST_INACTIVE ); } // Change to the newly-added recoprofile HRESULT hrSetRecoProfile = E_FAIL; if ( SUCCEEDED( hrRecoState ) ) { hrSetRecoProfile = g_pSRDlg->m_cpRecoEngine->SetRecoProfile( pThis->m_cpToken ); // Restore the recostate g_pSRDlg->m_cpRecoEngine->SetRecoState( eOldRecoState ); } // Bring on the micwiz and the training wiz // Follow the yellow brick road. g_pSRDlg->m_cpRecoEngine->DisplayUI(hDlg, NULL, SPDUI_MicTraining, NULL, 0); if ( SUCCEEDED( hrSetRecoProfile ) ) { // Only want to train the profile if it actually _is_ this profile being // used... g_pSRDlg->m_cpRecoEngine->DisplayUI(hDlg, NULL, SPDUI_UserTraining, NULL, 0); } } } // Switch back to original default, complaining about errors only if there // wasn't a complaint from the call to TrySwitchDefaultEngine hr = g_pSRDlg->ResetDefaultEngine( SUCCEEDED( hr )); } // now we are done EndDialog(hDlg, TRUE); } else if ( eRet == EPD_DUP ) // user tried to enter a duplicate name { // What name was added? WCHAR szName[ UNLEN+1 ]; *szName = 0; GETTEXTEX gtex = { UNLEN, GT_DEFAULT, 1200, NULL, NULL }; ::SendDlgItemMessage(pThis->m_hDlg, IDC_USER_NAME, EM_GETTEXTEX, (WPARAM)>ex, (LPARAM)szName); WCHAR pszDuplicate[MAX_LOADSTRING]; LoadString(_Module.GetResourceInstance(), g_pSRDlg->IsProfileNameInvisible( szName ) ? IDS_DUP_NAME_DELETED : IDS_DUP_NAME, pszDuplicate, MAX_LOADSTRING); MessageBox( hDlg, pszDuplicate, g_pSRDlg->m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout ); } } } break; } return FALSE; } /* UserNameDialogProc */ /***************************************************************************** * CSRDlg::UserSelChange * *-------------------------* * Description: * Changes the deafult user ****************************************************************** BRENTMID ***/ void CSRDlg::UserSelChange( int iSelIndex ) { HRESULT hr = S_OK; SPDBG_FUNC( "CSRDlg::UserSelChange" ); // Get the selected item's token LVITEM lvitem; lvitem.iItem = iSelIndex; lvitem.iSubItem = 0; lvitem.mask = LVIF_PARAM; ::SendMessage( m_hUserList, LVM_GETITEM, 0, (LPARAM) &lvitem ); ISpObjectToken *pToken = (ISpObjectToken *) lvitem.lParam; if (pToken) { // Try to find the item in the list associated with the current default token LVFINDINFO lvfi; if ( iSelIndex >= 0 ) { // Something was selected; this is the new default user lvfi.flags = LVFI_PARAM; lvfi.lParam = (LPARAM) m_pCurUserToken; int iCurDefaultIndex = (int)::SendMessage( m_hUserList, LVM_FINDITEM, -1, (LPARAM) &lvfi ); if ( iCurDefaultIndex >= 0 ) { // The current default has been found in the list; remove its checkmark SetCheckmark( m_hUserList, iCurDefaultIndex, false ); } SetCheckmark( m_hUserList, iSelIndex, true ); //set the default m_pCurUserToken = pToken; m_iLastSelected = iSelIndex; // Kick the Apply button KickCPLUI(); } } } /* CSRDlg::UserSelChange */ /***************************************************************************** * CSRDlg::DeleteCurrentUser * *-------------------------* * Description: * Deletes the default user ****************************************************************** BRENTMID ***/ void CSRDlg::DeleteCurrentUser() { // Make sure that we haven't already deleted too many profiles to keep track of if ( m_iDeletedTokens >= iMaxDeletedProfiles_c ) { WCHAR wszError[ MAX_LOADSTRING ]; ::LoadString( _Module.GetResourceInstance(), IDS_MAX_PROFILES_EXCEEDED, wszError, MAX_LOADSTRING ); ::MessageBox( m_hDlg, wszError, m_szCaption, MB_ICONEXCLAMATION | g_dwIsRTLLayout ); return; } // First confirm this action with the user WCHAR pszAsk[ MAX_LOADSTRING ]; WCHAR pszWinTitle[ MAX_LOADSTRING ]; ::LoadString( _Module.GetResourceInstance(), IDS_ASK_CONFIRM, pszAsk, MAX_LOADSTRING ); ::LoadString( _Module.GetResourceInstance(), IDS_ASK_TITLE, pszWinTitle, MAX_LOADSTRING ); if ( MessageBox( m_hDlg, pszAsk, pszWinTitle, MB_YESNO | g_dwIsRTLLayout ) == IDNO ) { // User said no. return; } // We need to hang onto the current user token, since when the focus // changes because of the delete, there will be a different m_pCurUserToken ISpObjectToken *pTokenToDelete = m_pCurUserToken; SPDBG_ASSERT( pTokenToDelete ); if ( !pTokenToDelete ) { return; } m_fDontDelete = TRUE; // Try to find the item in the list associated with the current default token LVFINDINFO lvfi; lvfi.flags = LVFI_PARAM; lvfi.lParam = (LPARAM) pTokenToDelete; int iCurDefaultIndex = (int)::SendMessage( m_hUserList, LVM_FINDITEM, -1, (LPARAM) &lvfi ); if ( iCurDefaultIndex >= 0 ) { // The current default has been found in the list; remove its checkmark SetCheckmark( m_hUserList, iCurDefaultIndex, false ); } //remove the token ::SendMessage( m_hUserList, LVM_DELETEITEM, iCurDefaultIndex, NULL ); // now setup the new default // Get the first item's token LVITEM lvitem; lvitem.iItem = 0; lvitem.iSubItem = 0; lvitem.mask = LVIF_PARAM; ::SendMessage( m_hUserList, LVM_GETITEM, 0, (LPARAM) &lvitem ); ISpObjectToken *pToken = (ISpObjectToken *) lvitem.lParam; // set the selected item. // Focusing it will cause it to be the default lvitem.state = LVIS_SELECTED | LVIS_FOCUSED; lvitem.stateMask = LVIS_SELECTED | LVIS_FOCUSED; ::SendMessage( m_hUserList, LVM_SETITEMSTATE, 0, (LPARAM) &lvitem ); SetCheckmark( m_hUserList, 0, true ); // enable or disable the delete button based on # of profiles int iNumUsers = (int)::SendMessage(m_hUserList, LVM_GETITEMCOUNT, 0, 0); if (iNumUsers < 2) { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), FALSE); } else { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), TRUE); } //set the focus back to the user profiles ::SetFocus(GetDlgItem( m_hDlg, IDC_USER )); // set the new default profile, inform the SR engine, and remove the old token SpSetDefaultTokenForCategoryId(SPCAT_RECOPROFILES, m_pCurUserToken ); // Save the tokens in case the user clicks "Cancel" m_aDeletedTokens[m_iDeletedTokens] = pTokenToDelete; // save the deleted token for possible "Cancel" m_iDeletedTokens++; // increment the number deleted KickCPLUI(); // Currently we immediately APPLY this deletion, since the user has already said "YES" // when the were prompted to confirm the delete. // If we want to have an "APPLY / CANCEL" thing happen, switch the #if 1 and #if 0 // send the appropriate message to the parent HWND parentWin = ::GetParent( m_hDlg ); // now the last selected token is gone, so note that m_iLastSelected = -1; // Sort the items initially ::SendMessage( m_hUserList, LVM_SORTITEMS, (LPARAM)m_pCurUserToken, LPARAM(&SortCols) ); m_fDontDelete = FALSE; } /* CSRDlg::DeleteCurrentUser */ /***************************************************************************** * CSRDlg::ProfileProperties * *-------------------------* * Description: * Modifies the properites through engine UI ****************************************************************** BRENTMID ***/ void CSRDlg::ProfileProperties() { if ( m_cpRecoEngine ) { m_cpRecoEngine->DisplayUI(m_hDlg, NULL, SPDUI_RecoProfileProperties, NULL, 0); } } /***************************************************************************** * CSRDlg::OnInitDialog * *----------------------* * Description: * Dialog Initialization ****************************************************************** MIKEAR ***/ void CSRDlg::OnInitDialog(HWND hWnd) { SPDBG_FUNC( "CSRDlg::OnInitDialog" ); USES_CONVERSION; SPDBG_ASSERT(IsWindow(hWnd)); m_hDlg = hWnd; // This will be the caption for all MessageBoxes m_szCaption[0] = 0; ::LoadString( _Module.GetResourceInstance(), IDS_CAPTION, m_szCaption, sp_countof( m_szCaption ) ); m_hSRCombo = ::GetDlgItem( hWnd, IDC_COMBO_RECOGNIZERS ); SpInitTokenComboBox( m_hSRCombo, SPCAT_RECOGNIZERS ); // The first one in the list will be the current default int iSelected = (int) ::SendMessage( m_hSRCombo, CB_GETCURSEL, 0, 0 ); ISpObjectToken *pCurDefault = (ISpObjectToken *) ::SendMessage( m_hSRCombo, CB_GETITEMDATA, iSelected, 0 ); m_pCurRecoToken = pCurDefault; m_pDefaultRecToken = pCurDefault; // This simulates selecting the default engine - ensures the UI is setup correctly. EngineSelChange(TRUE); InitUserList( hWnd ); m_hUserList = ::GetDlgItem( hWnd, IDC_USER ); ::SendMessage( m_hUserList, LVM_SETCOLUMNWIDTH, 0, MAKELPARAM((int) LVSCW_AUTOSIZE, 0) ); int iNumUsers = (int)::SendMessage(m_hUserList, LVM_GETITEMCOUNT, 0, 0); if (iNumUsers < 2) { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), FALSE); } else { EnableWindow(GetDlgItem(m_hDlg, IDC_DELETE), TRUE); } //set the focus back to the user profiles ::SetFocus(GetDlgItem( m_hDlg, IDC_USER )); } /* CSRDlg::OnInitDialog */ /***************************************************************************** * CSRDlg::SetCheckmark * *----------------------* * Description: * Sets the specified item in the list control to be either checked * or unchecked (as the default user) ******************************************************************************/ void CSRDlg::SetCheckmark( HWND hList, int iIndex, bool bCheck ) { ListView_SetCheckState( hList, iIndex, bCheck ); } /* CSRDlg::SetCheckmark */ /***************************************************************************** * CSRDlg::OnDestroy * *-------------------* * Description: * Destruction ****************************************************************** MIKEAR ***/ void CSRDlg::OnDestroy() { SPDBG_FUNC( "CSRDlg::OnDestroy" ); // spuihelp will take care of releasing its own tokens SpDestroyTokenComboBox( m_hSRCombo ); // The tokens kepts as itemdata in the reco profile list were // released in the LVN_DELETEITEM code // Shuts off the reco engine ShutDown(); } /* CSRDlg::OnDestroy */ /***************************************************************************** * CSRDlg::ShutDown * *------------------* * Description: * Shuts down by releasing the engine and reco context ****************************************************************** MIKEAR ***/ void CSRDlg::ShutDown() { // Release objects m_cpRecoCtxt.Release(); m_cpRecoEngine.Release(); } /* CSRDlg::ShutDown */ /************************************************************ * CSRDlg::InitUserList * * Description: * Initializes user list *********************************************** BRENTMID ***/ void CSRDlg::InitUserList(HWND hWnd) { const int iInitWidth_c = 260; // pixel width of "Description Column" // Set up the "Description" column for the settings display m_hUserList = ::GetDlgItem( hWnd, IDC_USER ); WCHAR pszColumnText[ UNLEN+1 ] = L""; LVCOLUMN lvc; lvc.mask = LVCF_FMT| LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH; ::LoadString( _Module.GetResourceInstance(), IDS_DESCRIPT, pszColumnText, UNLEN ); lvc.pszText = pszColumnText; lvc.iSubItem = 0; lvc.cx = iInitWidth_c; lvc.fmt = LVCFMT_LEFT; ListView_InsertColumn( m_hUserList, 1, &lvc ); // This should be a checkbox list ::SendMessage( m_hUserList, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES ); PopulateList(); // Sort the items initially ::SendMessage( m_hUserList, LVM_SORTITEMS, (LPARAM)m_pCurUserToken, LPARAM(&SortCols) ); } // CSRDlg::InitUserList /************************************************************ * CSRDlg::PopulateList * * Description: * Populates user list *********************************************** BRENTMID ***/ void CSRDlg::PopulateList() { USES_CONVERSION; // Populate the list control int iIndex = 0; LVITEM lvitem; CComPtr cpEnum; ISpObjectToken *pToken; WCHAR *pszAttrib = NULL; HRESULT hr; // this is to lazily init the user profile if there are none - DON'T REMOVE if ( m_cpRecoEngine ) { CComPtr cpTempToken; m_cpRecoEngine->GetRecoProfile(&cpTempToken); } // Now clear the list ListView_DeleteAllItems( m_hUserList ); // We will list the tokens in the order they are enumerated hr = SpEnumTokens(SPCAT_RECOPROFILES, NULL, NULL, &cpEnum); if (hr == S_OK) { bool fSetDefault = false; while (cpEnum->Next(1, &pToken, NULL) == S_OK) { // first check to see if the token is in the "Deleted List" bool f_isDel = false; for (int iDel = 0; iDel < m_iDeletedTokens; iDel++) { CSpDynamicString dstrT1; CSpDynamicString dstrT2; pToken->GetId( &dstrT1 ); m_aDeletedTokens[ iDel ]->GetId( &dstrT2 ); if (dstrT1.m_psz && dstrT2.m_psz && !wcscmp(dstrT1.m_psz, dstrT2.m_psz)) { f_isDel = true; } } // if we should show it if ( f_isDel ) { // This token has a refcounted reference to it on the deleted list: // this reference should be released pToken->Release(); } else { // Not a pending delete: We should show it // now insert the token lvitem.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE; lvitem.iItem = iIndex; lvitem.iSubItem = 0; lvitem.lParam = (LPARAM) pToken; CSpDynamicString cUser; SpGetDescription(pToken, &cUser); lvitem.pszText = cUser; // if this is the default it should be selected/focused if ( !fSetDefault ) { lvitem.state = LVIS_SELECTED | LVIS_FOCUSED; } else { lvitem.state = 0; } iIndex = (int)::SendMessage( m_hUserList, LVM_INSERTITEM, 0, (LPARAM) &lvitem ); // the default is the first token returned by cpEnum->Next if ( !fSetDefault ) { fSetDefault = true; // Put the checkmark there SetCheckmark( m_hUserList, iIndex, true ); m_pCurUserToken = pToken; // Set the m_dstrOldUserTokenId to the first default if it hasn't been set yet. if ( !m_dstrOldUserTokenId ) { m_pCurUserToken->GetId( &m_dstrOldUserTokenId ); } } iIndex++; } } // Autosize according to the strings now in the list ::SendMessage( m_hUserList, LVM_SETCOLUMNWIDTH, 0, MAKELPARAM((int) LVSCW_AUTOSIZE, 0) ); } // now find the default item so we can scroll to it // Try to find the item in the list associated with the current default token LVFINDINFO lvfi; lvfi.flags = LVFI_PARAM; lvfi.lParam = (LPARAM) m_pCurUserToken; int iCurDefaultIndex = (int)::SendMessage( m_hUserList, LVM_FINDITEM, -1, (LPARAM) &lvfi ); if ( iCurDefaultIndex >= 0 ) { // The current default has been found in the list; scroll to it ListView_EnsureVisible( m_hUserList, iCurDefaultIndex, false ); } // Name the list view something appropriate WCHAR pszListName[ MAX_LOADSTRING ]; ::LoadString( _Module.GetResourceInstance(), IDS_PROFILE_LIST_NAME, pszListName, MAX_LOADSTRING ); ::SendMessage( m_hUserList, WM_SETTEXT, 0, (LPARAM)pszListName ); } /***************************************************************************** * CSRDlg::OnApply * *-----------------* * Description: * Set user specified options ****************************************************************** MIKEAR ***/ void CSRDlg::OnApply() { SPDBG_FUNC( "CSRDlg::OnApply" ); int iSelected = (int) ::SendMessage( m_hSRCombo, CB_GETCURSEL, 0, 0 ); ULONG ulFlags = 0; // Pick up the recognizer change, if any bool fRecognizerChange = false; ISpObjectToken *pToken = NULL; if ( HasRecognizerChanged() ) { pToken = (ISpObjectToken *) ::SendMessage( m_hSRCombo, CB_GETITEMDATA, iSelected, 0 ); if ( CB_ERR == (LRESULT) pToken ) { pToken = NULL; } HRESULT hrEngine = S_OK; if (pToken && (iSelected >=0)) { hrEngine = SpSetDefaultTokenForCategoryId(SPCAT_RECOGNIZERS, pToken ); if (FAILED(hrEngine)) { WCHAR szError[256]; szError[0] = '\0'; LoadString(_Module.GetResourceInstance(), IDS_DEFAULT_ENGINE_WARNING, szError, sp_countof(szError)); MessageBox(m_hDlg, szError, MB_OK, MB_ICONWARNING | g_dwIsRTLLayout); } else { fRecognizerChange = true; } } } // Pick up any audio changes that may have been made HRESULT hrAudio = S_OK; bool fAudioChange = false; if ( m_pAudioDlg ) { fAudioChange = m_pAudioDlg->IsAudioDeviceChanged(); if ( fAudioChange ) { hrAudio = m_pAudioDlg->OnApply(); } if ( FAILED( hrAudio ) ) { WCHAR szError[256]; szError[0] = '\0'; LoadString(_Module.GetResourceInstance(), IDS_AUDIO_CHANGE_FAILED, szError, sp_countof(szError)); MessageBox(m_hDlg, szError, NULL, MB_ICONWARNING|g_dwIsRTLLayout); } // Kill the audio dialog, as we are done with it. delete m_pAudioDlg; m_pAudioDlg = NULL; } // Permanently delete any profiles the user has deleted for (int iIndex = 0; iIndex < m_iDeletedTokens; iIndex++) { HRESULT hr = m_aDeletedTokens[iIndex]->Remove(NULL); if (FAILED(hr)) { // might fail if a user has another app open WCHAR szError[256]; szError[0] = '\0'; LoadString(_Module.GetResourceInstance(), IDS_REMOVE_WARNING, szError, sp_countof(szError)); MessageBox(m_hDlg, szError, MB_OK, MB_ICONWARNING|g_dwIsRTLLayout); // This will make sure that the attempted deleted item shows up again PopulateList(); } else { // The token is now removed, we can release it m_aDeletedTokens[iIndex]->Release(); } } m_iDeletedTokens = 0; // The added token list's tokens were added as they were put onto the list, // so just clear the list so that they stay added at the end m_iAddedTokens = 0; // Now we don't care about the old user because of the apply m_dstrOldUserTokenId.Clear(); m_pCurUserToken->GetId( &m_dstrOldUserTokenId ); ChangeDefaultUser(); // Kick the engine to pick up the changes. // Note that the recoprofile change would have taken effect when // we selected that list item, and that there is no way to // pick up the audio changes right now since SetInput() is not // implemented for shared engines. if ( fRecognizerChange || fAudioChange ) { BOOL fRecoContextInitialized = FALSE; if (fRecognizerChange) { ulFlags |= SRDLGF_RECOGNIZER; } if (fAudioChange) { ulFlags |= SRDLGF_AUDIOINPUT; } HRESULT hr = CreateRecoContext( &fRecoContextInitialized, FALSE, ulFlags); if ( FAILED( hr ) ) { RecoContextError( fRecoContextInitialized, TRUE, hr ); } if ( fRecognizerChange ) { SPDBG_ASSERT( pToken ); m_pDefaultRecToken = pToken; } EngineSelChange(); } if(m_cpRecoEngine) { m_cpRecoEngine->SetRecoState( SPRST_ACTIVE ); } } /* CSRDlg::OnApply */ /************************************************************ * CSRDlg::OnDrawItem * * Description: * Handles drawing items in the list view *********************************************** BRENTMID ***/ void CSRDlg::OnDrawItem( HWND hWnd, const DRAWITEMSTRUCT * pDrawStruct ) { RECT rcClip; LVITEM lvi; UINT uiFlags = ILD_TRANSPARENT; HIMAGELIST himl; int cxImage = 0, cyImage = 0; UINT uFirstColWidth; // Get the item image to be displayed lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_PARAM; lvi.iItem = pDrawStruct->itemID; lvi.iSubItem = 0; ListView_GetItem(pDrawStruct->hwndItem, &lvi); // We want to be drawing the current default as selected LVFINDINFO lvfi; lvfi.flags = LVFI_PARAM; lvfi.lParam = (LPARAM) m_pCurUserToken; UINT uiCurDefaultIndex = (UINT)::SendMessage( m_hUserList, LVM_FINDITEM, -1, (LPARAM) &lvfi ); bool fSelected = (uiCurDefaultIndex == pDrawStruct->itemID); // Check to see if this item is selected if ( fSelected ) { // Set the text background and foreground colors SetTextColor(pDrawStruct->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); SetBkColor(pDrawStruct->hDC, GetSysColor(COLOR_HIGHLIGHT)); } else { // Set the text background and foreground colors to the standard window // colors SetTextColor(pDrawStruct->hDC, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(pDrawStruct->hDC, GetSysColor(COLOR_WINDOW)); } // Get the image list and draw the image. // The image list will consist of the checked box and the unchecked box // for the LVS_EX_CHECKBOXES style himl = ListView_GetImageList(pDrawStruct->hwndItem, LVSIL_STATE); if (himl) { // For a LVS_EX_CHECKBOXES style, image 0 is unchecked and image 1 is checked ImageList_Draw(himl, fSelected ? 1 : 0, pDrawStruct->hDC, pDrawStruct->rcItem.left, pDrawStruct->rcItem.top, uiFlags); // Find out how big the image we just drew was ImageList_GetIconSize(himl, &cxImage, &cyImage); } // Calculate the width of the first column after the image width. If // There was no image, then cxImage will be zero. LVCOLUMN pColumn; pColumn.mask = LVCF_WIDTH; ::SendMessage( m_hUserList, LVM_GETCOLUMN, 0, (LPARAM)&pColumn ); int iColWidth = pColumn.cx; // pixel width of "Description Column" uFirstColWidth = iColWidth - cxImage; // Set up the new clipping rect for the first column text and draw it rcClip.left = pDrawStruct->rcItem.left + cxImage; rcClip.right = pDrawStruct->rcItem.left + iColWidth; rcClip.top = pDrawStruct->rcItem.top; rcClip.bottom = pDrawStruct->rcItem.bottom; ISpObjectToken *pToken = (ISpObjectToken *) lvi.lParam; CSpDynamicString dstrTokenName; SpGetDescription(pToken, &dstrTokenName); DrawItemColumn(pDrawStruct->hDC, dstrTokenName, &rcClip); // If we changed the colors for the selected item, undo it if ( fSelected ) { // Set the text background and foreground colors SetTextColor(pDrawStruct->hDC, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(pDrawStruct->hDC, GetSysColor(COLOR_WINDOW)); } // If the item is focused, now draw a focus rect around the entire row if (pDrawStruct->itemState & ODS_FOCUS) { // Adjust the left edge to exclude the image rcClip = pDrawStruct->rcItem; rcClip.left += cxImage; // Draw the focus rect if ( ::GetFocus() == m_hUserList ) { DrawFocusRect(pDrawStruct->hDC, &rcClip); } } } // CSRDlg::OnDrawItem /************************************************************ * CSRDlg::DrawItemColumn * * Description: * Handles drawing of the column data *********************************************** BRENTMID ***/ void CSRDlg::DrawItemColumn(HDC hdc, WCHAR* lpsz, LPRECT prcClip) { USES_CONVERSION; int iHeight = 0; // Will cause CreateFont() to use default in case we // don't get the height below // Get the height of the text if (hdc) { TEXTMETRIC tm; if (GetTextMetrics(hdc, &tm)) { iHeight = tm.tmHeight; } } // link the font LCID dwLCID = GetUserDefaultLCID(); // Pick an appropriate font. On Windows 2000, let the system fontlink. DWORD dwVersion = GetVersion(); HFONT hfontNew = NULL; HFONT hfontOld = NULL; if ( (dwVersion >= 0x80000000) || (LOBYTE(LOWORD(dwVersion)) < 5 ) ) { // Less than NT5: Figure out what font WCHAR achCodePage[6]; UINT uiCodePage; if (0 != GetLocaleInfo(dwLCID, LOCALE_IDEFAULTANSICODEPAGE, achCodePage, 6)) { uiCodePage = _wtoi(achCodePage); } else { uiCodePage = GetACP(); } CComPtr cpMultiLanguage; MIMECPINFO MimeCpInfo; if ( SUCCEEDED(cpMultiLanguage.CoCreateInstance(CLSID_CMultiLanguage)) && SUCCEEDED(cpMultiLanguage->GetCodePageInfo(uiCodePage, &MimeCpInfo))) { USES_CONVERSION; hfontNew = CreateFont(iHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0, MimeCpInfo.bGDICharset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, MimeCpInfo.wszProportionalFont); } cpMultiLanguage.Release(); } if ( hfontNew ) { hfontOld = (HFONT) ::SelectObject( hdc, hfontNew ); } CSpDynamicString szString; CSpDynamicString szNewString; // Check to see if the string fits in the clip rect. If not, truncate // the string and add "...". szString = lpsz; szNewString = CalcStringEllipsis(hdc, szString, UNLEN, prcClip->right - prcClip->left); szString = szNewString; // print the text ExtTextOutW(hdc, prcClip->left + 2, prcClip->top + 2, ETO_CLIPPED | ETO_OPAQUE, prcClip, szString.m_psz, szString.Length(), NULL); // Replace the old font if ( hfontNew ) { ::SelectObject( hdc, hfontOld ); ::DeleteObject( hfontNew ); } } /************************************************************ * CSRDlg::CalcStringEllipsis * * Description: * If the text won't fit in the box, edit it, and make * it have ellipses *********************************************** BRENTMID ***/ CSpDynamicString CSRDlg::CalcStringEllipsis(HDC hdc, CSpDynamicString lpszString, int cchMax, UINT uColWidth) { USES_CONVERSION; WCHAR szEllipsis[] = L"..."; SIZE sizeString; SIZE sizeEllipsis; int cbString; CSpDynamicString lpszTemp; BOOL fSuccess = FALSE; // Adjust the column width to take into account the edges uColWidth -= 4; lpszTemp = lpszString; // Get the width of the string in pixels cbString = lpszTemp.Length(); if (!::GetTextExtentPoint32(hdc, lpszTemp, cbString, &sizeString)) { SPDBG_ASSERT(FALSE); } // If the width of the string is greater than the column width shave // the string and add the ellipsis if ((ULONG)sizeString.cx > uColWidth) { if (!::GetTextExtentPoint32(hdc, szEllipsis, lstrlen(szEllipsis), &sizeEllipsis)) { SPDBG_ASSERT(FALSE); } while ((cbString > 0) && (fSuccess == FALSE)) { lpszTemp[--cbString] = 0; if (!::GetTextExtentPoint32(hdc, lpszTemp, cbString, &sizeString)) { SPDBG_ASSERT(FALSE); } if ((ULONG)(sizeString.cx + sizeEllipsis.cx) <= uColWidth) { // The string with the ellipsis finally fits, now make sure // there is enough room in the string for the ellipsis if (cchMax >= (cbString + lstrlen(szEllipsis))) { // Concatenate the two strings and break out of the loop lpszTemp.Append( szEllipsis ); lpszString = lpszTemp; fSuccess = TRUE; } } } } else { // No need to do anything, everything fits great. fSuccess = TRUE; } return (lpszString); } // CSRDlg::CalStringEllipsis /************************************************************ * CSRDlg::ChangeDefaultUser * * Description: * Handles changes to the environment settings *********************************************** BRENTMID ***/ void CSRDlg::ChangeDefaultUser() { HRESULT hr; if (m_pCurUserToken) { hr = SpSetDefaultTokenForCategoryId(SPCAT_RECOPROFILES, m_pCurUserToken); } // Sort the items initially ::SendMessage( m_hUserList, LVM_SORTITEMS, (LPARAM)m_pCurUserToken, LPARAM(&SortCols) ); } // CSRDlg::ChangeDefaultUser /************************************************************ * CSRDlg::OnCancel * * Description: * Handles undoing changes to the environment settings *********************************************** BRENTMID ***/ void CSRDlg::OnCancel() { // Get the original user and make sure that is still the default. // Note that in general m_pCurUserToken does not AddRef the // ISpObjectToken it points to, so this is OK. SpGetTokenFromId( m_dstrOldUserTokenId, &m_pCurUserToken ); ChangeDefaultUser(); // Set the old recoprofile so that none of the profiles added in this // session will be in use: // This allows us to roll back the adds below // m_pCurUserToken does the trick since it is guaranteed to have // been around before this session if (m_cpRecoEngine) { m_cpRecoEngine->SetRecoState( SPRST_INACTIVE ); m_cpRecoEngine->SetRecoProfile( m_pCurUserToken ); m_cpRecoEngine->SetRecoState( SPRST_ACTIVE ); } // Roll back and delete any new profiles added int cItems = (int) ::SendMessage( m_hUserList, LVM_GETITEMCOUNT, 0, 0 ); LVITEM lvitem; for ( int i = 0; i < m_iAddedTokens; i++ ) { // Look for the list item with a ref out on this token. // We need to do this because in order for a token to be successfully // removed the only existing ref to that token has to call the Remove() // method. The list is holding a ref to that item. bool fFound = false; for ( int j=0; !fFound && (j < cItems); j++ ) { ::memset( &lvitem, 0, sizeof( lvitem ) ); lvitem.iItem = j; lvitem.mask = LVIF_PARAM; ::SendMessage( m_hUserList, LVM_GETITEM, 0, (LPARAM) &lvitem ); CSpDynamicString dstrItemId; ISpObjectToken *pItemToken = (ISpObjectToken *) lvitem.lParam; if ( pItemToken ) { HRESULT hrId = pItemToken->GetId( &dstrItemId ); if ( SUCCEEDED( hrId ) && dstrItemId && m_aAddedTokens[i] && ( 0 == wcscmp( dstrItemId, m_aAddedTokens[ i ] ) ) ) { // Should this fail, the profile just doesn't get removed: big deal pItemToken->Remove( NULL ); fFound = true; } } } } // We AddRefed it... m_pCurUserToken->Release(); } // CSRDlg::OnCancel /***************************************************************************** * CSRDlg::EngineSelChange * *-------------------------* * Description: * This function updates the list box when the user selects a new engine. * If queries the token to see which UI items the engine supports. * The parameter fInitialize determines if the engine is actually created. * It does NOT actually change the default engine. ****************************************************************** MIKEAR ***/ void CSRDlg::EngineSelChange(BOOL fInitialize) { HRESULT hr = S_OK; SPDBG_FUNC( "CSRDlg::EngineSelChange" ); int iSelected = (int) ::SendMessage( m_hSRCombo, CB_GETCURSEL, 0, 0 ); ISpObjectToken *pToken = (ISpObjectToken *) ::SendMessage( m_hSRCombo, CB_GETITEMDATA, iSelected, 0 ); if ( CB_ERR == (LRESULT) pToken ) { pToken = NULL; } if (pToken) { // Now the current reco token is the one we got off the currently-selected combobox item m_pCurRecoToken = pToken; // Kick the UI to enable the Apply button if necessary KickCPLUI(); HRESULT hrRecoContextOK = S_OK; if(fInitialize) { BOOL fContextInitialized = FALSE; hrRecoContextOK = CreateRecoContext(&fContextInitialized, TRUE); if ( FAILED( hrRecoContextOK ) ) { RecoContextError( fContextInitialized, true, hrRecoContextOK ); } } if ( FAILED( hrRecoContextOK ) ) { // Don't continue, all the buttons are grayed out, // which is what we want return; } } // Check for something being wrong, in which case we want to gray out all // the UI and stop here. // For instance, if we had trouble creating the reco context that's supposed to // be on now (the one for m_pDefaultRecToken), we certainly shouldn't // enable the UI buttons... if ( !pToken || (!m_cpRecoCtxt && (pToken == m_pDefaultRecToken)) ) { RecoContextError( FALSE, FALSE ); return; } // Determine if the training UI component is supported. // We can pass the current reco engine in as an argument only // if it's the same as the one who's token we're asking about. IUnknown *punkObject = (pToken == m_pDefaultRecToken) ? m_cpRecoEngine : NULL; BOOL fSupported = FALSE; hr = pToken->IsUISupported(SPDUI_UserTraining, NULL, 0, punkObject, &fSupported); if (FAILED(hr)) { fSupported = FALSE; } ::EnableWindow(::GetDlgItem(m_hDlg, IDC_USERTRAINING), fSupported); // Determine if the Mic Wiz UI component is supported fSupported = FALSE; hr = pToken->IsUISupported(SPDUI_MicTraining, NULL, 0, punkObject, &fSupported); if (FAILED(hr)) { fSupported = FALSE; } ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MICWIZ), fSupported); // Determine if the Engine Prop UI component is supported fSupported = FALSE; hr = pToken->IsUISupported(SPDUI_EngineProperties, NULL, 0, punkObject, &fSupported); if (FAILED(hr)) { fSupported = FALSE; } ::EnableWindow(::GetDlgItem(m_hDlg, IDC_SR_ADV), fSupported); // Determine if the Reco Profile Prop UI component is supported fSupported = FALSE; hr = pToken->IsUISupported(SPDUI_RecoProfileProperties, NULL, 0, punkObject, &fSupported); if (FAILED(hr)) { fSupported = FALSE; } ::EnableWindow(::GetDlgItem(m_hDlg, IDC_MODIFY), fSupported); } /* CSRDlg::EngineSelChange */ /***************************************************************************** * CSRDlg::IsCurRecoEngineAndCurRecoTokenMatch * *---------------------------------------------* * Description: * Returns true in pfMatch iff the m_pCurRecoToken is the same * as the token for m_cpRecoEngine. * Return: * S_OK * E_POINTER * Failed HRESULTs from any of the SAPI calls ****************************************************************** BECKYW ***/ HRESULT CSRDlg::IsCurRecoEngineAndCurRecoTokenMatch( bool *pfMatch ) { if ( !pfMatch ) { return E_POINTER; } if ( !m_cpRecoEngine || !m_pCurRecoToken ) { return E_FAIL; } *pfMatch = false; // This gets the object token for the engine CComPtr cpRecoEngineToken; HRESULT hr = m_cpRecoEngine->GetRecognizer( &cpRecoEngineToken ); WCHAR *pwszRecoEngineTokenID = NULL; WCHAR *pwszCurRecoTokenID = NULL; if ( SUCCEEDED( hr ) ) { hr = cpRecoEngineToken->GetId( &pwszRecoEngineTokenID ); } if ( SUCCEEDED( hr ) ) { hr = m_pCurRecoToken->GetId( &pwszCurRecoTokenID ); } if ( pwszRecoEngineTokenID && pwszCurRecoTokenID ) { *pfMatch = ( 0 == wcscmp( pwszRecoEngineTokenID, pwszCurRecoTokenID ) ); } return hr; } /* CSRDlg::IsCurRecoEngineAndCurRecoTokenMatch */