Files
2025-05-21 21:20:08 +03:00

1017 lines
36 KiB
C++

/******************************************************************************
* coffee.cpp
* Contains entry point for the coffee tutorial application, as
* well as implementation of all application features.
*
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* The source code supplied here is intended as a sample, so some
* error handling, etc. has been omitted for the sake of clarity.
******************************************************************************/
#include "stdafx.h"
#include <sphelper.h> // Contains definitions of SAPI functions
#include <spddkhlp.h> // Defines for dynamic grammars
#include "common.h" // Contains common defines
#include "coffee.h" // Forward declarations and constants
#include "cofgram.h" // This header is created by the grammar
// compiler and has our rule ids
/******************************************************************************
* WinMain *
*---------*
* Description:
* coffee entry point.
*
* Return:
* exit code
******************************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
// Register the main window class
MyRegisterClass(hInstance, WndProc);
// Initialize pane handler state
g_fpCurrentPane = EntryPaneProc;
// Only continue if COM is successfully initialized
if ( SUCCEEDED( CoInitialize( NULL ) ) )
{
// Perform application initialization:
if (!InitInstance( hInstance, nCmdShow ))
{
return FALSE;
}
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
}
return msg.wParam;
}
/******************************************************************************
* WndProc *
*---------*
* Description:
* Main window procedure.
*
******************************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
// Try to initialize, quit with error message if we can't
if ( FAILED( InitSAPI( hWnd ) ) )
{
const iMaxTitleLength = 64;
TCHAR tszBuf[ MAX_PATH ];
LoadString( g_hInst, IDS_FAILEDINIT, tszBuf, MAX_PATH );
TCHAR tszName[ iMaxTitleLength ];
LoadString( g_hInst, IDS_APP_TITLE, tszName, iMaxTitleLength );
MessageBox( hWnd, tszBuf, tszName, MB_OK|MB_ICONWARNING );
return( -1 );
}
// Let the entry pane know its time to do initialization work
PostMessage( hWnd, WM_INITPANE, 0, 0 );
break;
// This is our application defined window message to let us know that a
// speech recognition event has occurred.
case WM_RECOEVENT:
ProcessRecoEvent( hWnd );
break;
case WM_ERASEBKGND:
EraseBackground( (HDC) wParam );
return ( 1 );
case WM_GETMINMAXINFO:
{
LPMINMAXINFO lpMM = (LPMINMAXINFO) lParam;
lpMM->ptMaxSize.x = MINMAX_WIDTH;
lpMM->ptMaxSize.y = MINMAX_HEIGHT;
lpMM->ptMinTrackSize.x = MINMAX_WIDTH;
lpMM->ptMinTrackSize.y = MINMAX_HEIGHT;
lpMM->ptMaxTrackSize.x = MINMAX_WIDTH;
lpMM->ptMaxTrackSize.y = MINMAX_HEIGHT;
return ( 0 );
}
// Release remaining SAPI related COM references before application exits
case WM_DESTROY:
// Call the current pane's handler first
(*g_fpCurrentPane)(hWnd, message, wParam, lParam);
KillTimer( hWnd, 0 );
CleanupGDIObjects();
CleanupSAPI();
PostQuitMessage(0);
break;
default:
{
_ASSERTE( g_fpCurrentPane );
// Send unhandled messages to pane specific procedure for potential action
LRESULT lRet = (*g_fpCurrentPane)(hWnd, message, wParam, lParam);
if ( 0 == lRet )
{
lRet = DefWindowProc(hWnd, message, wParam, lParam);
}
return ( lRet );
}
}
return ( 0 );
}
/******************************************************************************
* InitSAPI *
*----------*
* Description:
* Called once to get SAPI started.
*
******************************************************************************/
HRESULT InitSAPI( HWND hWnd )
{
HRESULT hr = S_OK;
while ( 1 )
{
// create a recognition engine
hr = g_cpEngine.CoCreateInstance(CLSID_SpSharedRecognizer);
if ( FAILED( hr ) )
{
break;
}
// create the command recognition context
hr = g_cpEngine->CreateRecoContext( &g_cpRecoCtxt );
if ( FAILED( hr ) )
{
break;
}
// Let SR know that window we want it to send event information to, and using
// what message
hr = g_cpRecoCtxt->SetNotifyWindowMessage( hWnd, WM_RECOEVENT, 0, 0 );
if ( FAILED( hr ) )
{
break;
}
// Tell SR what types of events interest us. Here we only care about command
// recognition.
hr = g_cpRecoCtxt->SetInterest( SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION) );
if ( FAILED( hr ) )
{
break;
}
// Load our grammar, which is the compiled form of simple.xml bound into this executable as a
// user defined ("SRGRAMMAR") resource type.
hr = g_cpRecoCtxt->CreateGrammar(GRAMMARID1, &g_cpCmdGrammar);
if (FAILED(hr))
{
break;
}
hr = g_cpCmdGrammar->LoadCmdFromResource(NULL, MAKEINTRESOURCEW(IDR_CMD_CFG),
L"SRGRAMMAR", MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
SPLO_DYNAMIC);
if ( FAILED( hr ) )
{
break;
}
// Set navigation rule to active, espresso order rule to inactive
hr = g_cpCmdGrammar->SetRuleIdState( VID_EspressoDrinks, SPRS_INACTIVE );
if ( FAILED( hr ) )
{
break;
}
hr = g_cpCmdGrammar->SetRuleIdState( VID_Navigation, SPRS_ACTIVE );
if ( FAILED( hr ) )
{
break;
}
// Get the default voice associated with our reco context
hr = g_cpRecoCtxt->GetVoice(&g_cpVoice);
if ( FAILED( hr ) )
{
break;
}
break;
}
// if we failed and have a partially setup SAPI, close it all down
if ( FAILED( hr ) )
{
CleanupSAPI();
}
return ( hr );
}
/******************************************************************************
* CleanupSAPI *
*----------------*
* Description:
* Called to close down SAPI COM objects we have stored away.
*
******************************************************************************/
void CleanupSAPI( void )
{
// Release grammar, if loaded
if ( g_cpCmdGrammar )
{
g_cpCmdGrammar.Release();
}
// Release recognition context, if created
if ( g_cpRecoCtxt )
{
g_cpRecoCtxt->SetNotifySink(NULL);
g_cpRecoCtxt.Release();
}
// Release recognition engine instance, if created
if ( g_cpEngine )
{
g_cpEngine.Release();
}
// Release voice, if created
if ( g_cpVoice )
{
g_cpVoice.Release();
}
}
/******************************************************************************
* ProcessRecoEvent *
*------------------*
* Description:
* Called to when reco event message is sent to main window procedure.
* In the case of a recognition, it extracts result and calls ExecuteCommand.
*
******************************************************************************/
void ProcessRecoEvent( HWND hWnd )
{
CSpEvent event; // Event helper class
// Loop processing events while there are any in the queue
while (event.GetFrom(g_cpRecoCtxt) == S_OK)
{
// Look at recognition event only
switch (event.eEventId)
{
case SPEI_RECOGNITION:
ExecuteCommand(event.RecoResult(), hWnd);
break;
case SPEI_FALSE_RECOGNITION:
HandleFalseReco(event.RecoResult(), hWnd);
break;
}
}
}
/******************************************************************************
* ExecuteCommand *
*----------------*
* Description:
* Called to Execute commands that have been identified by the speech engine.
*
******************************************************************************/
void ExecuteCommand(ISpPhrase *pPhrase, HWND hWnd)
{
SPPHRASE *pElements;
// Get the phrase elements, one of which is the rule id we specified in
// the grammar. Switch on it to figure out which command was recognized.
if (SUCCEEDED(pPhrase->GetPhrase(&pElements)))
{
switch ( pElements->Rule.ulId )
{
case VID_EspressoDrinks:
{
ID_TEXT *pulIds = new ID_TEXT[MAX_ID_ARRAY]; // This memory will be freed when the WM_ESPRESSOORDER
// message is processed
const SPPHRASEPROPERTY *pProp = NULL;
const SPPHRASERULE *pRule = NULL;
ULONG ulFirstElement, ulCountOfElements;
int iCnt = 0;
if ( pulIds )
{
ZeroMemory( pulIds, sizeof( ID_TEXT[MAX_ID_ARRAY] ) );
pProp = pElements->pProperties;
pRule = pElements->Rule.pFirstChild;
// Fill in an array with the drink properties received
while ( pProp && iCnt < MAX_ID_ARRAY )
{
// Fill out a structure with all the property ids received as well
// as their corresponding text
pulIds[iCnt].ulId = static_cast< ULONG >(pProp->pFirstChild->vValue.ulVal);
// Get the count of elements from the rule ref, not the actual leaf
// property
if ( pRule )
{
ulFirstElement = pRule->ulFirstElement;
ulCountOfElements = pRule->ulCountOfElements;
}
else
{
ulFirstElement = 0;
ulCountOfElements = 0;
}
// This is the text corresponding to property iCnt - it must be
// released when we are done with it
pPhrase->GetText( ulFirstElement, ulCountOfElements,
FALSE, &(pulIds[iCnt].pwstrCoMemText), NULL);
// Loop through all properties
pProp = pProp->pNextSibling;
// Loop through rulerefs corresponding to properties
if ( pRule )
{
pRule = pRule->pNextSibling;
}
iCnt++;
}
PostMessage( hWnd, WM_ESPRESSOORDER, NULL, (LPARAM) pulIds );
}
}
break;
case VID_Navigation:
{
switch( pElements->pProperties->vValue.ulVal )
{
case VID_Counter:
PostMessage( hWnd, WM_GOTOCOUNTER, NULL, NULL );
break;
case VID_Office:
PostMessage( hWnd, WM_GOTOOFFICE, NULL, NULL );
break;
}
}
break;
case VID_Manage:
{
switch( pElements->pProperties->vValue.ulVal )
{
case VID_Employees:
PostMessage( hWnd, WM_MANAGEEMPLOYEES, NULL, NULL );
break;
}
}
break;
case VID_HearTheVoice:
{
PostMessage( hWnd, WM_HEARTHEVOICE, NULL, NULL );
}
break;
case VID_OtherRules:
{
PostMessage( hWnd, WM_MISCCOMMAND, NULL,
(LPARAM) pElements->pProperties->vValue.ulVal );
}
break;
case DYN_TTSVOICERULE:
{
PostMessage( hWnd, WM_TTSVOICESEL, NULL,
(LPARAM) pElements->pProperties->vValue.ulVal );
}
break;
}
// Free the pElements memory which was allocated for us
::CoTaskMemFree(pElements);
}
}
/******************************************************************************
* EntryPaneProc *
*---------------*
* Description:
* Handles messages specifically for the entry pane.
*
******************************************************************************/
LRESULT EntryPaneProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
USES_CONVERSION;
switch ( message )
{
case WM_INITPANE:
{
TCHAR tBuf[MAX_LOADSTRING];
LoadString( g_hInst, IDS_WELCOME, tBuf, MAX_LOADSTRING );
// Speak the welcome prompt, and do not wait for the operation to complete
g_cpVoice->Speak( T2W(tBuf), SPF_ASYNC, NULL);
return ( 1 );
}
case WM_GOTOCOUNTER:
// Set the right message handler and repaint
g_fpCurrentPane = CounterPaneProc;
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
case WM_GOTOOFFICE:
// Set the right message handler and repaint
g_fpCurrentPane = OfficePaneProc;
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
case WM_PAINT:
EntryPanePaint( hWnd );
return ( 1 );
}
return ( 0 );
}
/******************************************************************************
* CounterPaneProc *
*-----------------*
* Description:
* Handles messages specifically for the counter (order) pane.
*
******************************************************************************/
LRESULT CounterPaneProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
USES_CONVERSION;
HRESULT hr;
switch ( message )
{
case WM_ESPRESSOORDER:
{
_ASSERTE( lParam );
KillTimer( hWnd, 0 );
ID_TEXT *pulIds = (ID_TEXT *) lParam;
int i = 0, ilen = 0;
TCHAR szTempBuf[NORMAL_LOADSTRING];
TCHAR szSpace[] = _T(" ");
int iTemplen;
g_szCounterDisplay[0] = '\0';
// Sort the array
while ( 0 != pulIds[i].ulId )
{
i++;
}
for ( int j = 0; j < i; j++ )
{
int iminIndex = j;
for ( int k = j; k < i; k++ )
{
if ( pulIds[iminIndex].ulId > pulIds[k].ulId )
{
iminIndex = k;
}
}
ULONG ulId = pulIds[iminIndex].ulId;
WCHAR *pwstr = pulIds[iminIndex].pwstrCoMemText;
pulIds[iminIndex].pwstrCoMemText = pulIds[j].pwstrCoMemText;
pulIds[j].pwstrCoMemText = pwstr;
pulIds[iminIndex].ulId = pulIds[j].ulId;
pulIds[j].ulId = ulId;
}
i = 0;
// Put in the first order words if we actually have an order
if ( 0 != pulIds[0].ulId )
{
iTemplen = LoadString( g_hInst, IDS_ORDERBEGIN, szTempBuf, NORMAL_LOADSTRING );
lstrcat( g_szCounterDisplay + ilen, szTempBuf );
ilen += iTemplen;
}
while ( i < MAX_ID_ARRAY && 0 != pulIds[i].ulId )
{
TCHAR *pTempStr = W2T( pulIds[i].pwstrCoMemText );
iTemplen = lstrlen( pTempStr );
// We'll quit now so we dont overrun the buffer
if ( ilen + iTemplen >= MAX_LOADSTRING )
{
break;
}
if ( i > 0 )
{
lstrcat( g_szCounterDisplay + ilen, szSpace );
ilen += 1;
}
lstrcat( g_szCounterDisplay, pTempStr );
ilen += iTemplen;
i++;
}
// Put the thank you on this order
if ( 0 < i )
{
iTemplen = LoadString( g_hInst, IDS_ORDEREND, szTempBuf, NORMAL_LOADSTRING );
if ( ilen + iTemplen < MAX_LOADSTRING )
{
lstrcat( g_szCounterDisplay + ilen, szTempBuf );
ilen += iTemplen;
}
}
InvalidateRect( hWnd, NULL, TRUE );
SetTimer( hWnd, 0, TIMEOUT, NULL );
// Speak the order
g_cpVoice->Speak( T2W(g_szCounterDisplay), SPF_ASYNC, NULL);
// Delete the CoTaskMem we were given initially by ISpPhrase->GetText
i = 0;
while ( i < MAX_ID_ARRAY && 0 != pulIds[i].ulId )
{
CoTaskMemFree( pulIds[i].pwstrCoMemText );
i++;
}
delete [] pulIds;
return ( 1 );
}
case WM_PAINT:
CounterPanePaint( hWnd, g_szCounterDisplay );
return ( 1 );
case WM_INITPANE:
LoadString( g_hInst, IDS_PLEASEORDER, g_szCounterDisplay, MAX_LOADSTRING );
// Set the rule recognizing an espresso order to active, now that we are ready for it
g_cpCmdGrammar->SetRuleIdState( VID_EspressoDrinks, SPRS_ACTIVE );
// Set our interests to include false recognitions
hr = g_cpRecoCtxt->SetInterest( SPFEI(SPEI_RECOGNITION)|SPFEI(SPEI_FALSE_RECOGNITION),
SPFEI(SPEI_RECOGNITION)|SPFEI(SPEI_FALSE_RECOGNITION) );
_ASSERTE( SUCCEEDED( hr ) );
// Speak the welcome string
g_cpVoice->Speak( T2W(g_szCounterDisplay), SPF_ASYNC, NULL);
return ( 1 );
case WM_TIMER:
// Revert back to 'go ahead and order' message
LoadString( g_hInst, IDS_PLEASEORDER, g_szCounterDisplay, MAX_LOADSTRING );
InvalidateRect( hWnd, NULL, TRUE );
// Speak the welcome string
g_cpVoice->Speak( T2W(g_szCounterDisplay), SPF_ASYNC, NULL);
KillTimer( hWnd, 0 );
return ( 1 );
case WM_GOTOOFFICE:
KillTimer( hWnd, 0 );
// Set the rule recognizing an espresso order to inactive
// since you cant order from the office
g_cpCmdGrammar->SetRuleIdState( VID_EspressoDrinks, SPRS_INACTIVE );
// Set our interests to include only recognitions
hr = g_cpRecoCtxt->SetInterest( SPFEI(SPEI_RECOGNITION),SPFEI(SPEI_RECOGNITION) );
_ASSERTE( SUCCEEDED( hr ) );
// Set the right message handler and repaint
g_fpCurrentPane = OfficePaneProc;
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
case WM_DIDNTUNDERSTAND:
KillTimer( hWnd, 0 );
LoadString( g_hInst, IDS_DIDNTUNDERSTAND, g_szCounterDisplay, MAX_LOADSTRING );
InvalidateRect( hWnd, NULL, TRUE );
// Speak the didn't understand string
g_cpVoice->Speak( T2W(g_szCounterDisplay), SPF_ASYNC, NULL);
SetTimer( hWnd, 0, TIMEOUT, NULL );
return ( 1 );
}
return ( 0 );
}
/******************************************************************************
* OfficePaneProc *
*---------------*
* Description:
* Handles messages specifically for the office pane.
*
******************************************************************************/
LRESULT OfficePaneProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
HRESULT hr;
switch ( message )
{
case WM_GOTOCOUNTER:
// Set management rule to inactive
hr = g_cpCmdGrammar->SetRuleIdState( VID_Manage, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
// Set the right message handler and repaint
g_fpCurrentPane = CounterPaneProc;
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
case WM_MANAGEEMPLOYEES:
// Set management rule to inactive
hr = g_cpCmdGrammar->SetRuleIdState( VID_Manage, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
// Set the right message handler and repaint
g_fpCurrentPane = ManageEmployeesPaneProc;
PostMessage( hWnd, WM_INITPANE, NULL, 2 );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
case WM_PAINT:
OfficePanePaint( hWnd );
return ( 1 );
case WM_INITPANE:
// Set management rule to active
hr = g_cpCmdGrammar->SetRuleIdState( VID_Manage, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
return ( 1 );
}
return ( 0 );
}
/******************************************************************************
* HandleFalseReco *
*----------------*
* Description:
* Called to respond to false recognition events.
*
******************************************************************************/
void HandleFalseReco(ISpRecoResult *pRecoResult, HWND hWnd)
{
SPRECORESULTTIMES resultTimes;
if (SUCCEEDED( pRecoResult->GetResultTimes( &resultTimes ) ) )
{
if ( GetTickCount() - resultTimes.dwTickCount > MIN_ORDER_INTERVAL )
{
PostMessage( hWnd, WM_DIDNTUNDERSTAND, 0, 0);
}
}
}
/******************************************************************************
* ManageEmployeesPaneProc *
*-------------------------*
* Description:
* Handles messages specifically for the manage employees pane.
*
******************************************************************************/
LRESULT ManageEmployeesPaneProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
USES_CONVERSION;
static ULONG ulNumTokens;
static ULONG ulCurToken;
static WCHAR** ppszTokenIds;
static CSpDynamicString* ppcDesciptionString; // This is string helper class in sphelper.h
static UINT iCurEnum; // Indicates if we should list males, females, or both
switch ( message )
{
case WM_GOTOOFFICE:
{
// Set the right message handler and repaint
g_fpCurrentPane = OfficePaneProc;
//Cleanup our variables
ManageEmployeesPaneCleanup( ppszTokenIds, ppcDesciptionString, ulNumTokens );
ppszTokenIds = NULL;
ppcDesciptionString = NULL;
ulNumTokens = 0;
// Set the hear voice rule to inactive
HRESULT hr = g_cpCmdGrammar->SetRuleIdState( VID_HearTheVoice, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( VID_OtherRules, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( DYN_TTSVOICERULE, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
}
case WM_GOTOCOUNTER:
{
// Set the right message handler and repaint
g_fpCurrentPane = CounterPaneProc;
//Cleanup our variables
ManageEmployeesPaneCleanup( ppszTokenIds, ppcDesciptionString, ulNumTokens );
ppszTokenIds = NULL;
ppcDesciptionString = NULL;
ulNumTokens = 0;
// Set the hear voice rule to inactive
HRESULT hr = g_cpCmdGrammar->SetRuleIdState( VID_HearTheVoice, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( VID_OtherRules, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( DYN_TTSVOICERULE, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
PostMessage( hWnd, WM_INITPANE, NULL, NULL );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
}
case WM_PAINT:
{
// Do the actual UI paint
ManageEmployeesPanePaint( hWnd, ulNumTokens, ppcDesciptionString, ulCurToken, iCurEnum );
return ( 1 );
}
case WM_INITPANE:
{
ISpObjectToken *pToken = NULL; // Token interface pointer
CComPtr<IEnumSpObjectTokens> cpEnum; // Pointer to token enumerator
ULONG ulIndex = 0;
ulCurToken = 0xffffffff;
WCHAR *szRequiredAttributes = NULL;
SPSTATEHANDLE hDynamicRuleHandle; // Handle to our dynamic rule
// Set the required attributes field for the enum if we have special needs
// based on our LPARAM in
if ( 0 == lParam )
{
szRequiredAttributes = L"Gender=Male";
}
else if ( 1 == lParam )
{
szRequiredAttributes = L"Gender=Female";
}
// Get a token enumerator for tts voices available
HRESULT hr = SpEnumTokens(SPCAT_VOICES, szRequiredAttributes, NULL, &cpEnum);
if ( S_OK == hr )
{
// Get the numbers of tokens found
hr = cpEnum->GetCount( &ulNumTokens );
if ( SUCCEEDED( hr ) && 0 != ulNumTokens )
{
// Create arrays we need for storing data
ppcDesciptionString = new CSpDynamicString [ulNumTokens];
if ( NULL == ppcDesciptionString )
{
hr = E_OUTOFMEMORY;
break;
}
ppszTokenIds = new WCHAR* [ulNumTokens];
if ( NULL == ppszTokenIds )
{
hr = E_OUTOFMEMORY;
break;
}
ZeroMemory( ppszTokenIds, ulNumTokens*sizeof( WCHAR* ) );
// Get the next token in the enumeration
// State is maintained in the enumerator
while (cpEnum->Next(1, &pToken, NULL) == S_OK)
{
// Get a string which describes the token, in our case, the voice name
hr = SpGetDescription( pToken, &ppcDesciptionString[ulIndex] );
_ASSERTE( SUCCEEDED( hr ) );
// Get the token id, for a low overhead way to retrieve the token later
// without holding on to the object itself
hr = pToken->GetId( &ppszTokenIds[ulIndex] );
_ASSERTE( SUCCEEDED( hr ) );
ulIndex++;
// Release the token itself
pToken->Release();
pToken = NULL;
}
}
// if we've failed to properly initialize, then we should completely shut-down
if ( S_OK != hr )
{
if ( pToken )
{
pToken->Release();
}
ManageEmployeesPaneCleanup( ppszTokenIds, ppcDesciptionString, ulNumTokens );
ppszTokenIds = NULL;
ppcDesciptionString = NULL;
ulNumTokens = 0;
}
// Find out which token corresponds to our voice which is currently in use
else
{
WCHAR *pszCurTokenId = NULL;
// Get the token representing the current voice
HRESULT hr = g_cpVoice->GetVoice( &pToken );
if ( SUCCEEDED( hr ) )
{
// Get the current token ID, and compare it against others to figure out
// which desciption string is the one currently selected.
hr = pToken->GetId( &pszCurTokenId );
if ( SUCCEEDED( hr ) )
{
ulIndex = 0;
while ( ulIndex < ulNumTokens &&
0 != _wcsicmp( pszCurTokenId, ppszTokenIds[ulIndex] ) )
{
ulIndex++;
}
// We found it, so set the current index to that of the current token
if ( ulIndex < ulNumTokens )
{
ulCurToken = ulIndex;
}
CoTaskMemFree( pszCurTokenId );
}
pToken->Release();
}
}
}
// Initially, we see both genders
_ASSERTE( lParam >= 0 && lParam <= 2);
iCurEnum = lParam;
// Create a dynamic rule containing the description strings of the voice tokens
hr = g_cpCmdGrammar->GetRule(NULL, DYN_TTSVOICERULE, SPRAF_TopLevel | SPRAF_Active | SPRAF_Dynamic, TRUE, &hDynamicRuleHandle);
if ( SUCCEEDED( hr ) )
{
// Clear the rule first
hr = g_cpCmdGrammar->ClearRule( hDynamicRuleHandle );
_ASSERTE( SUCCEEDED( hr ) );
// Commit the changes
hr = g_cpCmdGrammar->Commit(0);
_ASSERTE( SUCCEEDED( hr ) );
// Add description names as the word, ulIndex as id
for ( ulIndex = 0; ulIndex < ulNumTokens; ulIndex++ )
{
SPPROPERTYINFO prop;
prop.pszName = L"Id";
prop.pszValue = L"Property";
prop.vValue.vt = VT_I4;
prop.vValue.ulVal = ulIndex;
hr = g_cpCmdGrammar->AddWordTransition( hDynamicRuleHandle, NULL, ppcDesciptionString[ulIndex], L" ",
SPWT_LEXICAL, 1.0, &prop);
_ASSERTE( SUCCEEDED( hr ) );
}
// Commit the changes
hr = g_cpCmdGrammar->Commit(0);
_ASSERTE( SUCCEEDED( hr ) );
// Set the dynamic rules to active
hr = g_cpCmdGrammar->SetRuleIdState( DYN_TTSVOICERULE, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
}
// Set the hear voice rule to active
hr = g_cpCmdGrammar->SetRuleIdState( VID_HearTheVoice, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( VID_OtherRules, SPRS_ACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
}
case WM_DESTROY:
// Windows is closing down, so we should cleanup
ManageEmployeesPaneCleanup( ppszTokenIds, ppcDesciptionString, ulNumTokens );
return ( 1 );
case WM_HEARTHEVOICE:
// Set the voice to play
LoadString( g_hInst, IDS_VOICESPEAK, g_szCounterDisplay, MAX_LOADSTRING );
g_cpVoice->Speak( T2W(g_szCounterDisplay), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL );
return ( 1 );
case WM_MISCCOMMAND:
{
// Find out the offset from the first property we're interested in, so we can verify that
// it's within range.
int iSelection = lParam - VID_MalesOnly;
if ( 0 <= iSelection && 2 >= iSelection )
{
// If we have a new listing criteria, we basically shutdown the pane and start it again
if ( (UINT) iSelection != iCurEnum )
{
HRESULT hr = g_cpCmdGrammar->SetRuleIdState( VID_HearTheVoice, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
hr = g_cpCmdGrammar->SetRuleIdState( VID_OtherRules, SPRS_INACTIVE );
_ASSERTE( SUCCEEDED( hr ) );
ManageEmployeesPaneCleanup( ppszTokenIds, ppcDesciptionString, ulNumTokens );
PostMessage( hWnd, WM_INITPANE, 0, (LPARAM) iSelection );
}
}
return ( 1 );
}
case WM_TTSVOICESEL:
{
// If we are out of range, it is a programming error
_ASSERTE( 0 <= lParam && ulNumTokens > (ULONG) lParam );
// The returned Id is an index into our tokenId table, so create a token from the id
CComPtr< ISpObjectToken > pToken;
HRESULT hr = SpGetTokenFromId( ppszTokenIds[lParam], &pToken, FALSE);
if ( SUCCEEDED( hr ) )
{
// Set our current voice from the returned token
hr = g_cpVoice->SetVoice( pToken );
_ASSERTE( SUCCEEDED( hr ) );
// Change our current voice index
ulCurToken = lParam;
}
InvalidateRect( hWnd, NULL, TRUE );
return ( 1 );
}
}
return ( 0 );
}
/******************************************************************************
* ManageEmployeesPaneCleanup *
*----------------------------*
* Description:
* Helper function to cleanup objects allocated while the ManageEmployees
* pane is running.
*
******************************************************************************/
void ManageEmployeesPaneCleanup( WCHAR** ppszTokenIds, CSpDynamicString* ppcDesciptionString,
ULONG ulNumTokens)
{
ULONG ulIndex;
// Free all allocated token ids
if ( ppszTokenIds )
{
for ( ulIndex = 0; ulIndex < ulNumTokens; ulIndex++ )
{
if ( NULL != ppszTokenIds[ulIndex] )
{
CoTaskMemFree( ppszTokenIds[ulIndex] );
}
}
delete [] ppszTokenIds;
}
if ( ppcDesciptionString )
{
delete [] ppcDesciptionString;
}
}