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.
3304 lines
101 KiB
3304 lines
101 KiB
//
|
|
// sptask.cpp
|
|
//
|
|
// implements a notification callback ISpTask
|
|
//
|
|
// created: 4/30/99
|
|
//
|
|
//
|
|
|
|
#include "private.h"
|
|
#include "globals.h"
|
|
#include "sapilayr.h"
|
|
#include "propstor.h"
|
|
#include "dictctxt.h"
|
|
#include "nui.h"
|
|
#include "mui.h"
|
|
#include "shlguid.h"
|
|
#include "spgrmr.h"
|
|
|
|
|
|
//
|
|
//
|
|
// CSpTask class impl
|
|
//
|
|
//
|
|
STDMETHODIMP CSpTask::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown)
|
|
/* || IsEqualIID(riid, IID_ISpNotifyCallback) */
|
|
)
|
|
{
|
|
*ppvObj = SAFECAST(this, CSpTask *);
|
|
}
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CSpTask::AddRef(void)
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CSpTask::Release(void)
|
|
{
|
|
long cr;
|
|
|
|
cr = --m_cRef;
|
|
Assert(cr >= 0);
|
|
|
|
if (cr == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
//
|
|
// ctor
|
|
//
|
|
//
|
|
CSpTask::CSpTask(CSapiIMX *pime)
|
|
{
|
|
// CSpTask is initialized with an TFX instance
|
|
// so store the pointer to the TFX
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask is generated");
|
|
|
|
m_pime = pime;
|
|
|
|
// addref so it doesn't go away during session
|
|
m_pime->AddRef();
|
|
|
|
// init data members here
|
|
m_cpResMgr = NULL;
|
|
m_cpRecoCtxt = NULL;
|
|
m_cpRecoCtxtForCmd = NULL;
|
|
m_cpRecoEngine = NULL;
|
|
m_cpVoice = NULL;
|
|
m_bInSound = NULL;
|
|
m_bGotReco = NULL;
|
|
|
|
m_fSapiInitialized = FALSE;
|
|
m_fDictationReady = FALSE;
|
|
|
|
m_fInputState = FALSE;
|
|
m_pLangBarSink = NULL;
|
|
|
|
// M2 SAPI workaround
|
|
m_fIn_Activate = FALSE;
|
|
m_fIn_SetModeBias = FALSE;
|
|
m_fIn_GetAlternates = FALSE;
|
|
m_fIn_SetInputOnOffState = FALSE;
|
|
|
|
m_fSelectStatus = FALSE; // By default, current selection is empty.
|
|
m_fDictationDeactivated = FALSE;
|
|
m_fSpellingModeEnabled = FALSE;
|
|
m_fCallbackInitialized = FALSE;
|
|
|
|
m_fSelectionEnabled = FALSE;
|
|
m_fDictationInitialized = FALSE;
|
|
|
|
m_fDictCtxtEnabled = FALSE;
|
|
m_fCmdCtxtEnabled = FALSE;
|
|
|
|
m_fTestedForOldMicrosoftEngine = FALSE;
|
|
m_fOldMicrosoftEngine = FALSE;
|
|
|
|
#ifdef RECOSLEEP
|
|
m_pSleepClass = NULL;
|
|
#endif
|
|
|
|
m_cRef = 1;
|
|
}
|
|
|
|
CSpTask::~CSpTask()
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask is destroyed");
|
|
|
|
if (m_pdc)
|
|
delete m_pdc;
|
|
|
|
if (m_pITNFunc)
|
|
delete m_pITNFunc;
|
|
|
|
m_pime->Release();
|
|
}
|
|
//
|
|
// CSpTask::_InitializeSAPIObjects
|
|
//
|
|
// initialize SAPI objects for SR
|
|
// later we'll get other objects initialized here
|
|
// (TTS, audio etc)
|
|
//
|
|
HRESULT CSpTask::InitializeSAPIObjects(LANGID langid)
|
|
{
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is called");
|
|
|
|
if (m_fSapiInitialized == TRUE)
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeSAPIObjects is intialized already\n");
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// m_xxx are CComPtrs from ATL
|
|
//
|
|
HRESULT hr = m_cpResMgr.CoCreateInstance( CLSID_SpResourceManager );
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CLSID_SpResourceManager is created, hr=%x", hr);
|
|
|
|
|
|
if (!m_pime->IsSharedReco())
|
|
{
|
|
// create a recognition engine
|
|
|
|
TraceMsg(TF_SAPI_PERF,"Inproc engine is generated");
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
|
|
hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpInprocRecognizer );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
CComPtr<ISpObjectToken> cpAudioToken;
|
|
SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpAudioToken);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_cpRecoEngine->SetInput(cpAudioToken, TRUE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = m_cpRecoEngine.CoCreateInstance( CLSID_SpSharedRecognizer );
|
|
TraceMsg(TF_SAPI_PERF, "Shared Engine is generated! hr=%x", hr);
|
|
}
|
|
|
|
// create the recognition context
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxt );
|
|
|
|
TraceMsg(TF_SAPI_PERF, "RecoContext is generated, hr=%x", hr);
|
|
}
|
|
|
|
GUID guidFormatId = GUID_NULL;
|
|
WAVEFORMATEX *pWaveFormatEx = NULL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SpConvertStreamFormatEnum(SPSF_8kHz8BitMono, &guidFormatId, &pWaveFormatEx);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "SpConvertStreamFormatEnum is done, hr=%x", hr);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_cpRecoCtxt->SetAudioOptions(SPAO_RETAIN_AUDIO, &guidFormatId, pWaveFormatEx);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "RecoContext SetAudioOptions, RETAIN AUDIO, hr=%x", hr);
|
|
|
|
if (pWaveFormatEx)
|
|
::CoTaskMemFree(pWaveFormatEx);
|
|
}
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
|
|
|
|
TraceMsg(TF_SAPI_PERF, "SpVoice is generated, hr=%x", hr);
|
|
}
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// this has to be extended so that
|
|
// we choose default voice as far as lang matches
|
|
// and pick the best match if not
|
|
//
|
|
// hr = _SetVoice(langid);
|
|
}
|
|
|
|
//
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
m_langid = _GetPreferredEngineLanguage(langid);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "_GetPreferredEngineLanguage is Done, m_langid=%x", m_langid);
|
|
}
|
|
|
|
#ifdef RECOSLEEP
|
|
InitSleepClass( );
|
|
#endif
|
|
|
|
if (SUCCEEDED(hr))
|
|
m_fSapiInitialized = TRUE;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "InitializeSAPIObjects is Done!!!!!, hr=%x\n", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// CSpTask::_InitializeSAPIForCmd
|
|
//
|
|
// initialize SAPI RecoContext for Voice Command mode
|
|
//
|
|
// this function should be called after _InitializeSAPIObject.
|
|
//
|
|
HRESULT CSpTask::InitializeSAPIForCmd( )
|
|
{
|
|
|
|
TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd is called");
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!m_cpRecoCtxtForCmd && m_cpRecoEngine && m_langid)
|
|
{
|
|
hr = m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxtForCmd );
|
|
TraceMsg(TF_SAPI_PERF, "m_cpRecoCtxtForCmd is generated, hr=%x", hr);
|
|
|
|
// Set the RecoContextState as DISABLE by default to improve SAPI Perf.
|
|
//
|
|
// After initializing, caller must set the context state explicitly.
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
|
|
m_fCmdCtxtEnabled = FALSE;
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Initialize Callback for RecoCtxtForCmd");
|
|
|
|
// set recognition notification
|
|
CComPtr<ISpNotifyTranslator> cpNotify;
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for RecoCtxtForCmd is generated, hr=%x", hr);
|
|
|
|
// set this class instance to notify control object
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pime->_EnsureWorkerWnd();
|
|
|
|
hr = cpNotify->InitCallback( NotifyCallbackForCmd, 0, (LPARAM)this );
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_cpRecoCtxtForCmd->SetNotifySink(cpNotify);
|
|
TraceMsg(TF_SAPI_PERF, "SetNotifySink for RecoCtxtForCmd is Done, hr=%x", hr);
|
|
}
|
|
|
|
// set the events we're interested in
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
const ULONGLONG ulInterest = SPFEI(SPEI_RECOGNITION) |
|
|
SPFEI(SPEI_FALSE_RECOGNITION) |
|
|
SPFEI(SPEI_RECO_OTHER_CONTEXT);
|
|
|
|
hr = m_cpRecoCtxtForCmd->SetInterest(ulInterest, ulInterest);
|
|
TraceMsg(TF_SAPI_PERF, "SetInterest for m_cpRecoCtxtForCmd is Done, hr=%x", hr);
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "InitializeCallback for m_cpRecoCtxtForCmd is done!!! hr=%x", hr);
|
|
|
|
// Load the shard command grammars and activate them by default.
|
|
|
|
if (SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpRecoCtxtForCmd->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInVoiceCmd);
|
|
TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammar In Voice cmd, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = S_FALSE;
|
|
|
|
// try resource first because loading cmd from file takes
|
|
// quite long time
|
|
//
|
|
if (m_langid == 0x409 || // English
|
|
m_langid == 0x411 || // Japanese
|
|
m_langid == 0x804 ) // Simplified Chinese
|
|
{
|
|
hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromResource(
|
|
g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_DYNAMIC);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Load shared cmd.cfg, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
// in case if we don't have built-in grammar
|
|
// it provides a way for customer to localize their grammars in different languages
|
|
_GetCmdFileName(m_langid);
|
|
if (m_szShrdCmdFile[0])
|
|
{
|
|
hr = m_cpSharedGrammarInVoiceCmd->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC);
|
|
}
|
|
}
|
|
|
|
// Activate the grammar by default
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
if (m_pime->_AllCmdsEnabled( ))
|
|
{
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(NULL, NULL, SPRS_ACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSharedGrammarInVoiceCmd");
|
|
}
|
|
else
|
|
{
|
|
// Some category commands are disabled.
|
|
// active them individually.
|
|
|
|
hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_COMMAND_MODE );
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_COMMAND_MODE);
|
|
}
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
m_cpSharedGrammarInVoiceCmd.Release();
|
|
}
|
|
else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH ||
|
|
PRIMARYLANGID(m_langid) == LANG_JAPANESE ||
|
|
PRIMARYLANGID(m_langid) == LANG_CHINESE )
|
|
{
|
|
// means this language's grammar support Textbuffer commands.
|
|
m_fSelectionEnabled = TRUE;
|
|
}
|
|
|
|
#ifdef RECOSLEEP
|
|
InitSleepClass( );
|
|
#endif
|
|
}
|
|
TraceMsg(TF_SAPI_PERF, "Finish the initalization for RecoCtxtForCmd");
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "InitializeSAPIForCmd exits!");
|
|
|
|
return hr;
|
|
}
|
|
|
|
#ifdef RECOSLEEP
|
|
void CSpTask::InitSleepClass( )
|
|
{
|
|
// Load the Sleep/Wakeup grammar.
|
|
if ( !m_pSleepClass )
|
|
{
|
|
m_pSleepClass = new CRecoSleepClass(this);
|
|
if ( m_pSleepClass )
|
|
m_pSleepClass->InitRecoSleepClass( );
|
|
}
|
|
}
|
|
|
|
BOOL CSpTask::IsInSleep( )
|
|
{
|
|
BOOL fSleep = FALSE;
|
|
|
|
if ( m_pSleepClass )
|
|
fSleep = m_pSleepClass->IsInSleep( );
|
|
|
|
return fSleep;
|
|
}
|
|
#endif
|
|
|
|
HRESULT CSpTask::_SetDictRecoCtxtState( BOOL fEnable )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState is called, fEnable=%d", fEnable);
|
|
|
|
if ( m_cpRecoCtxt && (fEnable != m_fDictCtxtEnabled))
|
|
{
|
|
if (fEnable )
|
|
{
|
|
// if Voice command reco Context is enabled, just disable it.
|
|
if (m_cpRecoCtxtForCmd && m_fCmdCtxtEnabled)
|
|
{
|
|
hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
|
|
m_fCmdCtxtEnabled = FALSE;
|
|
TraceMsg(TF_SAPI_PERF, "Disable Voice command Reco Context");
|
|
}
|
|
|
|
// Build toolbar grammar if it is not built out yet.
|
|
if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( ))
|
|
m_pLangBarSink->_OnSetFocus( );
|
|
|
|
// Enable Dictation Reco Context.
|
|
if ( hr == S_OK )
|
|
{
|
|
hr = m_cpRecoCtxt->SetContextState(SPCS_ENABLED);
|
|
TraceMsg(TF_SAPI_PERF, "Enable Dictation Reco Context");
|
|
|
|
if ( hr == S_OK && !m_fDictationReady )
|
|
{
|
|
WCHAR sz[128];
|
|
sz[0] = '\0';
|
|
CicLoadStringWrapW(g_hInst, IDS_NUI_BEGINDICTATION, sz, ARRAYSIZE(sz));
|
|
|
|
m_pime->GetSpeechUIServer()->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
|
|
m_fDictationReady = TRUE;
|
|
TraceMsg(TF_SAPI_PERF, "Show Begin Dictation!");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
|
|
TraceMsg(TF_SAPI_PERF, "Disable Dictation Reco Context");
|
|
}
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
m_fDictCtxtEnabled = fEnable;
|
|
}
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "_SetDictRecoCtxtState exit");
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetCmdRecoCtxtState( BOOL fEnable )
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState is called, fEnable=%d", fEnable);
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( fEnable != m_fCmdCtxtEnabled )
|
|
{
|
|
if ( fEnable )
|
|
{
|
|
if ( !m_cpRecoCtxtForCmd )
|
|
hr = InitializeSAPIForCmd( );
|
|
|
|
if ( hr == S_OK && m_cpRecoCtxtForCmd )
|
|
{
|
|
// Disable Dictation Context if it is enabled now.
|
|
if (m_cpRecoCtxt && m_fDictCtxtEnabled)
|
|
{
|
|
hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
|
|
m_fDictCtxtEnabled = FALSE;
|
|
TraceMsg(TF_SAPI_PERF, "DISABLE Dictation RecoContext");
|
|
}
|
|
|
|
if ( hr == S_OK && m_pime && !m_pime->_AllCmdsDisabled( ) )
|
|
{
|
|
// Build toolbar grammar if it is not built out yet.
|
|
if (m_pLangBarSink && !m_pLangBarSink->_IsTBGrammarBuiltOut( ))
|
|
m_pLangBarSink->_OnSetFocus( );
|
|
|
|
// Fill text to selection grammar's buffer.
|
|
|
|
_UpdateTextBuffer(m_cpRecoCtxtForCmd);
|
|
|
|
hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_ENABLED);
|
|
m_fCmdCtxtEnabled = fEnable;
|
|
TraceMsg(TF_SAPI_PERF, "Enable Voice command Reco Context");
|
|
}
|
|
}
|
|
}
|
|
else if ( m_cpRecoCtxtForCmd ) // fEnable is FALSE
|
|
{
|
|
hr = m_cpRecoCtxtForCmd->SetContextState(SPCS_DISABLED);
|
|
m_fCmdCtxtEnabled = FALSE;
|
|
TraceMsg(TF_SAPI_PERF, "Disable Voice Command Reco Context");
|
|
}
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "_SetCmdRecoCtxtState exits");
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
LANGID CSpTask::_GetPreferredEngineLanguage(LANGID langid)
|
|
{
|
|
SPRECOGNIZERSTATUS stat;
|
|
LANGID langidRet = 0;
|
|
|
|
// (possible TODO) After M3 SPG may come up with GetAttrRank API that
|
|
// would give us the info about whether a token has a particular
|
|
// attrib supported. Then we could use that for checking langid
|
|
// a recognizer supports without using the real engine instance.
|
|
// We could also consolidate a method to check if SR is enabled
|
|
// for the current language once we have that.
|
|
//
|
|
Assert(m_cpRecoEngine);
|
|
if (S_OK == m_cpRecoEngine->GetStatus(&stat))
|
|
{
|
|
for (ULONG ulId = 0; ulId < stat.cLangIDs; ulId++)
|
|
{
|
|
if (langid == stat.aLangID[ulId])
|
|
{
|
|
langidRet = langid;
|
|
break;
|
|
}
|
|
}
|
|
if (!langidRet)
|
|
{
|
|
// if there's no match, just return the most prefered one
|
|
langidRet = stat.aLangID[0];
|
|
}
|
|
}
|
|
return langidRet;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetVoice(LANGID langid)
|
|
{
|
|
CComPtr<ISpObjectToken> cpToken;
|
|
|
|
char szLang[MAX_PATH];
|
|
WCHAR wsz[MAX_PATH];
|
|
|
|
StringCchPrintfA(szLang,ARRAYSIZE(szLang), "Language=%x", langid);
|
|
MultiByteToWideChar(CP_ACP, NULL, szLang, -1, wsz, ARRAYSIZE(wsz));
|
|
|
|
HRESULT hr = SpFindBestToken( SPCAT_VOICES, wsz, NULL, &cpToken);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = m_cpVoice->SetVoice(cpToken);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// GetSAPIInterface(riid, (void **)ppunk)
|
|
//
|
|
// here, try pass through the given IID
|
|
// to SAPI5 interface
|
|
//
|
|
// CComPtr<ISpResourceManager> m_cpResMgr;
|
|
// CComPtr<ISpRecoContext> m_cpRecoCtxt;
|
|
// CComPtr<ISpRecognizer> m_cpRecoEngine;
|
|
// CComPtr<ISpVoice> m_cpVoice;
|
|
//
|
|
// the above 5 interfaces are currently used by
|
|
// Cicero/Sapi Layer
|
|
//
|
|
// if a client calls ITfFunctionProvider::GetFunction()
|
|
// for a SAPI interface, we return what we've already
|
|
// instantiated so the caller can setup options
|
|
// for the currently used SAPI objects (reco ctxt for ex)
|
|
//
|
|
HRESULT CSpTask::GetSAPIInterface(REFIID riid, void **ppunk)
|
|
{
|
|
Assert(ppunk);
|
|
|
|
*ppunk = NULL;
|
|
|
|
|
|
if (IsEqualGUID(riid, IID_ISpResourceManager))
|
|
{
|
|
*ppunk = m_cpResMgr;
|
|
}
|
|
else if (IsEqualGUID(riid,IID_ISpRecoContext))
|
|
{
|
|
*ppunk = m_cpRecoCtxt;
|
|
}
|
|
else if (IsEqualGUID(riid,IID_ISpRecognizer))
|
|
{
|
|
*ppunk = m_cpRecoEngine;
|
|
}
|
|
else if (IsEqualGUID(riid,IID_ISpVoice))
|
|
{
|
|
*ppunk = m_cpVoice;
|
|
}
|
|
else if (IsEqualGUID(riid,IID_ISpRecoGrammar))
|
|
{
|
|
*ppunk = m_cpDictGrammar;
|
|
}
|
|
if(*ppunk)
|
|
{
|
|
((IUnknown *)(*ppunk))->AddRef();
|
|
}
|
|
|
|
return *ppunk ? S_OK : E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//
|
|
// Get RecoContext for Voice Command mode.
|
|
//
|
|
HRESULT CSpTask::GetRecoContextForCommand(ISpRecoContext **ppRecoCtxt)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
Assert(ppRecoCtxt);
|
|
|
|
if ( m_cpRecoCtxtForCmd )
|
|
{
|
|
*ppRecoCtxt = m_cpRecoCtxtForCmd;
|
|
(*ppRecoCtxt)->AddRef( );
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// test: use Message callback
|
|
LRESULT CALLBACK CSapiIMX::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CSapiIMX *_this = (CSapiIMX *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
CSpTask *_sptask = _this ? _this->m_pCSpTask : NULL;
|
|
|
|
switch(uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
CREATESTRUCT *pcs = (CREATESTRUCT *)lParam;
|
|
if (pcs)
|
|
{
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)(pcs->lpCreateParams));
|
|
}
|
|
break;
|
|
}
|
|
case WM_TIMER:
|
|
|
|
if ( wParam != TIMER_ID_CHARTYPED )
|
|
KillTimer( hWnd, wParam );
|
|
|
|
if (wParam == TIMER_ID_OPENCLOSE)
|
|
{
|
|
// i've seen this null case once but is it possible?
|
|
TraceMsg(TF_SAPI_PERF, "TIMER_ID_OPENCLOSE is fired off ...");
|
|
if (_this->_tim)
|
|
_this->_HandleOpenCloseEvent(MICSTAT_ON);
|
|
}
|
|
else if ( wParam == TIMER_ID_CHARTYPED )
|
|
{
|
|
DWORD dwNumCharTyped;
|
|
BOOL fDictOn;
|
|
|
|
fDictOn = _this->GetOnOff( ) && _this->GetDICTATIONSTAT_DictOnOff( );
|
|
dwNumCharTyped = _this->_GetNumCharTyped( );
|
|
|
|
TraceMsg(TF_GENERAL, "dwNumCharTyped=%d", dwNumCharTyped);
|
|
|
|
_this->_KillCharTypeTimer( );
|
|
|
|
Assert(S_OK == _this->IsActiveThread());
|
|
// We should never try to reactivate dictation on a thread that shouldn't be active.
|
|
|
|
if ( fDictOn && _sptask && (S_OK == _this->IsActiveThread()) )
|
|
{
|
|
if ( dwNumCharTyped <= 1 )
|
|
{
|
|
// There is no more typing during this period
|
|
// possible, user finished typing.
|
|
//
|
|
// we need to resume dication again if the Dictation mode is ON.
|
|
ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) |
|
|
SPFEI(SPEI_SOUND_END) |
|
|
SPFEI(SPEI_PHRASE_START) |
|
|
SPFEI(SPEI_RECOGNITION) |
|
|
SPFEI(SPEI_FALSE_RECOGNITION) |
|
|
SPFEI(SPEI_RECO_OTHER_CONTEXT) |
|
|
SPFEI(SPEI_HYPOTHESIS) |
|
|
SPFEI(SPEI_INTERFERENCE) |
|
|
SPFEI(SPEI_ADAPTATION);
|
|
|
|
_sptask->_SetDictRecoCtxtState(TRUE);
|
|
_sptask->_SetRecognizerInterest(ulInterest);
|
|
_sptask->_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
|
|
}
|
|
else
|
|
{
|
|
// There are more typing during this period,
|
|
// we want to set another timer to watch the end of the typing.
|
|
//
|
|
_this->_SetCharTypeTimer( );
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case WM_PRIV_FEEDCONTEXT:
|
|
if (_sptask && lParam != NULL && _sptask->m_pdc)
|
|
{
|
|
_sptask->m_pdc->FeedContextToGrammar(_sptask->m_cpDictGrammar);
|
|
delete _sptask->m_pdc;
|
|
_sptask->m_pdc = NULL;
|
|
}
|
|
break;
|
|
case WM_PRIV_LBARSETFOCUS:
|
|
if (_sptask)
|
|
_sptask->m_pLangBarSink->_OnSetFocus();
|
|
break;
|
|
case WM_PRIV_SPEECHOPTION:
|
|
{
|
|
_this->_ResetDefaultLang();
|
|
BOOL fSREnabledForLanguage = _this->InitializeSpeechButtons();
|
|
|
|
_this->SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
|
|
}
|
|
break;
|
|
case WM_PRIV_ADDDELETE:
|
|
_this->_DisplayAddDeleteUI();
|
|
break;
|
|
|
|
case WM_PRIV_SPEECHOPENCLOSE:
|
|
TraceMsg(TF_SAPI_PERF, "WM_PRIV_SPEECHOPENCLOSE is handled");
|
|
_this->_HandleOpenCloseEvent();
|
|
break;
|
|
|
|
case WM_PRIV_OPTIONS:
|
|
_this->_InvokeSpeakerOptions();
|
|
break;
|
|
|
|
case WM_PRIV_DORECONVERT :
|
|
_this->_DoReconvertOnRange( );
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CSpTask::NotifyCallbackForCmd(WPARAM wParam, LPARAM lParam )
|
|
{
|
|
CSpTask *_this = (CSpTask *)lParam;
|
|
|
|
// SAPI M2 work around which is to be removed when M3 comes up.
|
|
// See comments in CSpTask::_SetInputOnOffState for more detail
|
|
//
|
|
// TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
|
|
/* if (_this->m_fInputState == FALSE)
|
|
{
|
|
return;
|
|
}*/
|
|
|
|
if (_this->m_pime->fDeactivated())
|
|
return;
|
|
|
|
if (!_this->m_cpRecoCtxtForCmd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_this->SharedRecoNotify(_this->m_cpRecoCtxtForCmd);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void CSpTask::NotifyCallback( WPARAM wParam, LPARAM lParam )
|
|
{
|
|
CSpTask *_this = (CSpTask *)lParam;
|
|
|
|
// SAPI M2 work around which is to be removed when M3 comes up.
|
|
// See comments in CSpTask::_SetInputOnOffState for more detail
|
|
//
|
|
|
|
// TABLETPC - NEEDED TO ALLOW FINAL RECOGNITIONS TO BE RECEIVED AFTER AUDIO STOPPED.
|
|
/* if (_this->m_fInputState == FALSE)
|
|
{
|
|
return;
|
|
}*/
|
|
|
|
if (_this->m_pime->fDeactivated())
|
|
return;
|
|
|
|
if (!_this->m_cpRecoCtxt)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_this->SharedRecoNotify(_this->m_cpRecoCtxt);
|
|
|
|
return;
|
|
}
|
|
|
|
// This is real handler for the recognition notification.
|
|
//
|
|
// it could be shared by two RecoContexts.
|
|
//
|
|
void CSpTask::SharedRecoNotify(ISpRecoContext *pRecoCtxt)
|
|
{
|
|
CSpEvent event;
|
|
#ifdef SAPI_PERF_DEBUG
|
|
static int iCount = 0;
|
|
|
|
if ( iCount == 0 )
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "The first time Get Notification from Engine!!!");
|
|
iCount ++;
|
|
}
|
|
#endif
|
|
|
|
Assert (pRecoCtxt);
|
|
|
|
while ( event.GetFrom(pRecoCtxt) == S_OK )
|
|
{
|
|
switch (event.eEventId)
|
|
{
|
|
case SPEI_SOUND_START:
|
|
ATLASSERT(!m_bInSound);
|
|
m_bInSound = TRUE;
|
|
break;
|
|
|
|
case SPEI_INTERFERENCE:
|
|
//
|
|
// we do not need interference when not in dictation
|
|
// mode
|
|
//
|
|
if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
|
|
S_OK == m_pime->IsActiveThread())
|
|
{
|
|
_HandleInterference((ULONG)(event.lParam));
|
|
}
|
|
break;
|
|
|
|
case SPEI_PHRASE_START:
|
|
ATLASSERT(m_bInSound);
|
|
m_bGotReco = FALSE;
|
|
|
|
if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
|
|
S_OK == m_pime->IsActiveThread())
|
|
{
|
|
// Before inject feedback UI, we need to save the current IP
|
|
// and check if we want to pop up the Add/Remove SR dialog UI.
|
|
|
|
// And then inject FeedbackUI as usual
|
|
|
|
m_pime->SaveCurIPAndHandleAddDelete_InjectFeedbackUI( );
|
|
|
|
// show "dictating..." to the balloon
|
|
_ShowDictatingToBalloon(TRUE);
|
|
}
|
|
|
|
break;
|
|
|
|
case SPEI_HYPOTHESIS:
|
|
ATLASSERT(!m_bGotReco);
|
|
|
|
|
|
// if current microphone status is OFF
|
|
// we do not want to show any hypothesis
|
|
// at least
|
|
//
|
|
|
|
// DO NOT HAVE DEBUG CODE TO SHOW ENGINE STATE. CAN BLOCK CICERO AND CHANGE BEHAVIOR.
|
|
if (!GetSystemMetrics(SM_TABLETPC))
|
|
_ShowDictatingToBalloon(TRUE);
|
|
|
|
//
|
|
// we do not need the feedback UI when not in dictation
|
|
// mode
|
|
//
|
|
if (m_pime->GetDICTATIONSTAT_DictOnOff() &&
|
|
S_OK == m_pime->IsActiveThread())
|
|
{
|
|
m_pime->_HandleHypothesis(event);
|
|
}
|
|
|
|
break;
|
|
|
|
case SPEI_RECO_OTHER_CONTEXT:
|
|
case SPEI_FALSE_RECOGNITION:
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( event.eEventId == SPEI_FALSE_RECOGNITION )
|
|
{
|
|
// Set 'What was that?' feedback text.
|
|
_UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE);
|
|
}
|
|
|
|
// set this flag anyways
|
|
//
|
|
ATLASSERT(!m_bGotReco);
|
|
m_bGotReco = TRUE;
|
|
|
|
// Reset hypothesis counters.
|
|
m_pime->_HandleFalseRecognition();
|
|
|
|
hr = m_pime->EraseFeedbackUI();
|
|
ATLASSERT("Failed to erase potential feedback on a false recognition." && SUCCEEDED(hr));
|
|
|
|
break;
|
|
}
|
|
|
|
case SPEI_RECOGNITION:
|
|
|
|
// Set 'Listening...' feedback text. Can be overwritten by command feedback.
|
|
_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
|
|
|
|
// set this flag anyways
|
|
//
|
|
ATLASSERT(!m_bGotReco);
|
|
m_bGotReco = TRUE;
|
|
|
|
ULONGLONG ullGramID;
|
|
|
|
if ( S_OK == m_pime->IsActiveThread() )
|
|
{
|
|
m_pime->_HandleRecognition(event, &ullGramID);
|
|
}
|
|
|
|
// if ( _GetSelectionStatus( ) )
|
|
if (ullGramID == GRAM_ID_SPELLING)
|
|
{
|
|
_SetSelectionStatus(FALSE);
|
|
_SetSpellingGrammarStatus(FALSE);
|
|
}
|
|
|
|
_UpdateTextBuffer(pRecoCtxt);
|
|
|
|
if ( (ullGramID == GRAM_ID_DICT) || (ullGramID == GRAM_ID_SPELLING) )
|
|
{
|
|
// Update Balloon.
|
|
if (!GetSystemMetrics(SM_TABLETPC))
|
|
_UpdateBalloon(IDS_LISTENING, IDS_LISTENING_TOOLTIP);
|
|
|
|
// every time dictated text is injected, we want to watch
|
|
// again if there is IP change after that.
|
|
// so clear the flag now.
|
|
m_pime->_SetIPChangeStatus( FALSE );
|
|
}
|
|
|
|
break;
|
|
|
|
case SPEI_SOUND_END:
|
|
m_bInSound = FALSE;
|
|
|
|
break;
|
|
|
|
case SPEI_ADAPTATION:
|
|
TraceMsg(TF_GENERAL, "Get SPEI_ADAPTATION notification");
|
|
|
|
if ( m_pime->_HasMoreContent( ) )
|
|
{
|
|
m_pime->_GetNextRangeEditSession( );
|
|
}
|
|
else
|
|
// There is no more content for this doc.
|
|
// set the interesting event value to avoid this notification.
|
|
m_pime->_UpdateRecoContextInterestSet(FALSE);
|
|
|
|
break;
|
|
#ifdef SYSTEM_GLOBAL_MIC_STATUS
|
|
case SPEI_RECO_STATE_CHANGE:
|
|
m_pime->SetOnOff(_GetInputOnOffState());
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
HRESULT CSpTask::_UpdateTextBuffer(ISpRecoContext *pRecoCtxt)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( !_IsSelectionEnabled( ) )
|
|
return S_OK;
|
|
|
|
if ( !pRecoCtxt || !m_pime)
|
|
return E_FAIL;
|
|
|
|
if ( m_pime->_SelectCorrectCmdEnabled( ) || m_pime->_NavigationCmdEnabled( ) )
|
|
{
|
|
BOOL fDictOn, fCmdOn;
|
|
|
|
fDictOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_DictOnOff( );
|
|
fCmdOn = m_pime->GetOnOff( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( );
|
|
|
|
|
|
if ( fDictOn && m_cpSharedGrammarInDict && !m_pime->_AllDictCmdsDisabled( ))
|
|
hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInDict);
|
|
else if (fCmdOn && m_cpSharedGrammarInVoiceCmd )
|
|
hr = m_pime->UpdateTextBuffer(pRecoCtxt, m_cpSharedGrammarInVoiceCmd);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// When selection grammar status is changed from inactive to active
|
|
// this function will be called to fill text to the grammar buffer.
|
|
//
|
|
HRESULT CSpTask::_UpdateSelectGramTextBufWhenStatusChanged( )
|
|
{
|
|
BOOL fDictOn, fCmdOn;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Check current mode status.
|
|
|
|
fDictOn = m_pime->GetDICTATIONSTAT_DictOnOff( );
|
|
fCmdOn = m_pime->GetDICTATIONSTAT_CommandingOnOff( );
|
|
|
|
if ( fDictOn )
|
|
hr = _UpdateTextBuffer(m_cpRecoCtxt);
|
|
else if ( fCmdOn )
|
|
hr = _UpdateTextBuffer(m_cpRecoCtxtForCmd);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpTask::_OnSpEventRecognition(ISpRecoResult *pResult, ITfContext *pic, TfEditCookie ec)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fDiscard = FALSE;
|
|
BOOL fCtrlSymChar = FALSE; // Control or Punctuation character
|
|
|
|
|
|
if (pResult)
|
|
{
|
|
static const WCHAR szUnrecognized[] = L"<Unrecognized>";
|
|
LANGID langid;
|
|
|
|
SPPHRASE *pPhrase;
|
|
|
|
hr = pResult->GetPhrase(&pPhrase);
|
|
if (SUCCEEDED(hr) && pPhrase)
|
|
{
|
|
// AJG - ADDED FILTERING CODE.
|
|
switch (pPhrase->Rule.ulCountOfElements)
|
|
{
|
|
case 0:
|
|
{
|
|
ASSERT(pPhrase->Rule.ulCountOfElements != 0);
|
|
// SHOULD NEVER OCCUR.
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
const SPPHRASEELEMENT *pElement;
|
|
|
|
pElement = pPhrase->pElements;
|
|
|
|
if (!m_fTestedForOldMicrosoftEngine)
|
|
{
|
|
// Test token name to see if it contains MSASREnglish.
|
|
CComPtr<ISpObjectToken> cpRecoToken;
|
|
WCHAR *pwszCoMemTokenId;
|
|
m_cpRecoEngine->GetRecognizer(&cpRecoToken);
|
|
if (cpRecoToken)
|
|
{
|
|
if (SUCCEEDED(cpRecoToken->GetId(&pwszCoMemTokenId)))
|
|
{
|
|
if (wcsstr(pwszCoMemTokenId, L"MSASREnglish") != NULL)
|
|
{
|
|
// It is an old Microsoft engine. Check for registry key that tells us to disable the heuristic anyway.
|
|
BOOL fDisableHeuristicAnyway = FALSE;
|
|
if (FAILED(cpRecoToken->MatchesAttributes(L"DisableCiceroConfidence", &fDisableHeuristicAnyway)) || fDisableHeuristicAnyway == FALSE)
|
|
{
|
|
m_fOldMicrosoftEngine = TRUE;
|
|
// Means we *will* apply single word confidence heuristic to improve performance.
|
|
}
|
|
}
|
|
CoTaskMemFree(pwszCoMemTokenId);
|
|
}
|
|
}
|
|
|
|
// One of lazy initialization. Do not do this again.
|
|
m_fTestedForOldMicrosoftEngine = TRUE;
|
|
}
|
|
if (m_fOldMicrosoftEngine && m_pime->_RequireHighConfidenceForShorWord( ) )
|
|
{
|
|
// Only apply this heuristic to 5.x Microsoft engines (Token name contains MSASREnglish).
|
|
if (pElement && pElement->ActualConfidence != 1 &&
|
|
(!pElement->pszLexicalForm || wcslen(pElement->pszLexicalForm) <= 5) &&
|
|
(!pElement->pszDisplayText || wcslen(pElement->pszDisplayText) <= 5) )
|
|
{
|
|
TraceMsg(TF_GENERAL, "Discarded Result : Single Word, Low Confidence!");
|
|
_UpdateBalloon(IDS_INT_NOISE, IDS_INTTOOLTIP_NOISE );
|
|
fDiscard = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
if (pPhrase->pElements[0].pszDisplayText )
|
|
{
|
|
WCHAR wch;
|
|
|
|
wch = pPhrase->pElements[0].pszDisplayText[0];
|
|
|
|
if ( iswcntrl(wch) || iswpunct(wch) )
|
|
fCtrlSymChar = TRUE;
|
|
}
|
|
|
|
}
|
|
case 2:
|
|
{
|
|
// Do something here?
|
|
}
|
|
default:
|
|
{
|
|
// Do no filtering of the result.
|
|
}
|
|
}
|
|
// AJG - CHECK WE AREN'T IN THE MIDDLE OF A WORD. NOT GENERALLY A DESIRED 'FEATURE'. CAUSES ANNOYING ERRORS.
|
|
// if this is spelled text, don't check if it is inside of a word.
|
|
if ((pPhrase->ullGrammarID != GRAM_ID_SPELLING) && _IsSelectionInMiddleOfWord(ec) && !fCtrlSymChar)
|
|
{
|
|
TraceMsg(TF_GENERAL, "Discarded Result : IP is in middle of a word!");
|
|
_UpdateBalloon(IDS_BALLOON_DICTAT_PAUSED, IDS_BALLOON_TOOLTIP_IP_INSIDE_WORD);
|
|
fDiscard = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( SUCCEEDED(hr) && fDiscard )
|
|
{
|
|
|
|
// This phrase will not be injected to the document.
|
|
// the code needs to feed context to the SR engine so that
|
|
// SR engine will not base on wrong assumption.
|
|
|
|
if ( m_pime && m_pime->GetDICTATIONSTAT_DictOnOff() )
|
|
m_pime->_SetCurrentIPtoSR();
|
|
|
|
}
|
|
|
|
if (SUCCEEDED(hr) && pPhrase && !fDiscard)
|
|
{
|
|
// retrieve LANGID from phrase
|
|
langid = pPhrase->LangID;
|
|
|
|
// SPPHRASE includes non-serialized text
|
|
CSpDynamicString dstr;
|
|
ULONG ulNumElements = pPhrase->Rule.ulCountOfElements;
|
|
|
|
hr = _GetTextFromResult(pResult, langid, dstr);
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
// check the current IP to see if it was a selection,
|
|
// then see if the best hypothesis already matches the current
|
|
// selection.
|
|
|
|
int lCommitHypothesis = 0;
|
|
for (int nthHypothesis = 1;_DoesSelectionHaveMatchingText(dstr, ec); nthHypothesis++)
|
|
{
|
|
CSpDynamicString dsNext;
|
|
|
|
TraceMsg(TF_GENERAL, "Switched to alternate result as main result exactly matched selection!");
|
|
|
|
// We could add one to request hypothesis since 1 = the main phrase and we already know that matched.
|
|
// However I don't believe this is guaranteed to be the case by SAPI - it just happens to be the case
|
|
// with the Microsoft engine.
|
|
if (_GetNextBestHypothesis(pResult, nthHypothesis, &ulNumElements, langid, dstr, dsNext, ec))
|
|
{
|
|
dstr.Clear();
|
|
dstr.Append(dsNext);
|
|
// Need to commit phrase to prevent stored result object being out of sync with count of
|
|
// elements in wrapping object.
|
|
lCommitHypothesis = nthHypothesis;
|
|
// Note - at this point, we don't know if we can use it. We have to loop once more to determine this.
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "No alternate found that differed from the user selection.\n");
|
|
// No more alternate phrase
|
|
// There is no any alt phrase which has different text from current selection.
|
|
// should stop here, otherwise, infinite loop.
|
|
lCommitHypothesis = 0;
|
|
// Reset element count to match primary phrase.
|
|
ulNumElements = pPhrase->Rule.ulCountOfElements;
|
|
// Reset text:
|
|
dstr.Clear();
|
|
hr = _GetTextFromResult(pResult, langid, dstr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 != lCommitHypothesis)
|
|
{
|
|
ULONG cAlt = lCommitHypothesis;
|
|
ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(cAlt*sizeof(ISpPhraseAlt *));
|
|
if (ppAlt)
|
|
{
|
|
memset(ppAlt, 0, cAlt * sizeof(ISpPhraseAlt *));
|
|
hr = pResult->GetAlternates( 0, ulNumElements, cAlt, ppAlt, &cAlt );
|
|
|
|
Assert( cAlt == lCommitHypothesis );
|
|
|
|
if ((S_OK == hr) && (cAlt == lCommitHypothesis))
|
|
{
|
|
((ppAlt)[lCommitHypothesis-1])->Commit();
|
|
}
|
|
|
|
// Release references to alternate phrases.
|
|
for (UINT i = 0; i < cAlt; i++)
|
|
{
|
|
if (NULL != (ppAlt)[i])
|
|
{
|
|
((ppAlt)[i])->Release();
|
|
}
|
|
}
|
|
|
|
cicMemFree(ppAlt);
|
|
}
|
|
}
|
|
|
|
CComPtr<ITfRange> cpTextRange;
|
|
ITfRange *pSavedIP;
|
|
|
|
pSavedIP = m_pime->GetSavedIP( );
|
|
|
|
if (pSavedIP)
|
|
pSavedIP->Clone(&cpTextRange);
|
|
|
|
// this call will have to be per element. see my comment below.
|
|
if (pPhrase->ullGrammarID == GRAM_ID_SPELLING)
|
|
{
|
|
hr = m_pime->InjectSpelledText(dstr, langid);
|
|
}
|
|
else
|
|
{
|
|
hr = m_pime->InjectText(dstr, langid);
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
// now we use the result object directly to attach
|
|
// to a docuement.
|
|
// the result object gets addref'd in the Attach()
|
|
// call.
|
|
//
|
|
hr = m_pime->AttachResult(pResult, 0, ulNumElements);
|
|
}
|
|
|
|
// Handle spaces carefully and specially.
|
|
if ( hr == S_OK && cpTextRange )
|
|
{
|
|
hr = m_pime->HandleSpaces(pResult, 0, ulNumElements, cpTextRange, langid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pPhrase)
|
|
::CoTaskMemFree( pPhrase );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// _GetTextFromResult
|
|
//
|
|
// synopsis: get text from phrase considering space control
|
|
// based on locale
|
|
//
|
|
HRESULT CSpTask::_GetTextFromResult(ISpRecoResult *pResult, LANGID langid, CSpDynamicString &dstr)
|
|
{
|
|
BYTE bAttr;
|
|
HRESULT hr = S_OK;
|
|
|
|
Assert(pResult);
|
|
|
|
if ( !pResult )
|
|
return E_INVALIDARG;
|
|
|
|
hr = pResult->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstr, &bAttr);
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
if (bAttr & SPAF_ONE_TRAILING_SPACE)
|
|
{
|
|
dstr.Append(L" ");
|
|
}
|
|
else if (bAttr & SPAF_TWO_TRAILING_SPACES)
|
|
{
|
|
dstr.Append(L" ");
|
|
}
|
|
|
|
if (bAttr & SPAF_CONSUME_LEADING_SPACES)
|
|
{
|
|
// we need to figure out the correct behavior based on LANGID
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// _IsSelectionInMiddleOfWord
|
|
//
|
|
// synopsis: check if the current IP is empty and inside of a word
|
|
//
|
|
BOOL CSpTask::_IsSelectionInMiddleOfWord(TfEditCookie ec)
|
|
{
|
|
BOOL fInsideWord = FALSE;
|
|
|
|
if ( m_langid == 0x0409 )
|
|
{
|
|
if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
|
|
{
|
|
WCHAR szSurrounding[3] = L" ";
|
|
|
|
// clone the IP range since we want to move the anchor
|
|
//
|
|
CComPtr<ITfRange> cpClonedRange;
|
|
cpInsertionPoint->Clone(&cpClonedRange);
|
|
|
|
BOOL fEmpty;
|
|
cpClonedRange->IsEmpty(ec, &fEmpty);
|
|
if (fEmpty)
|
|
{
|
|
LONG l1, l2;
|
|
ULONG ul;
|
|
HRESULT hr;
|
|
|
|
cpClonedRange->Collapse(ec, TF_ANCHOR_START);
|
|
cpClonedRange->ShiftStart(ec, -1, &l1, NULL);
|
|
cpClonedRange->ShiftEnd(ec, 1, &l2, NULL);
|
|
if (l1 != 0) // Not at start of document.
|
|
{
|
|
hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szSurrounding, (l2!=0)?(2):(1), &ul);
|
|
if (SUCCEEDED(hr) && iswalpha(szSurrounding[0]) && iswalpha(szSurrounding[1]) )
|
|
{
|
|
fInsideWord = TRUE;
|
|
}
|
|
}
|
|
// if l1 == 0, means the ip is at the start of document.
|
|
// fInsideWord is set to FALSE already by default.
|
|
}
|
|
}
|
|
}
|
|
return fInsideWord;
|
|
}
|
|
|
|
//
|
|
// DoesSelectionHaveMatchingText
|
|
//
|
|
// synopsis: check if the current saved IP has a selection that matches the text
|
|
// passed in
|
|
//
|
|
#define SPACEBUFFER 4
|
|
// 2 characters either side of a word or phrase.
|
|
|
|
BOOL CSpTask::_DoesSelectionHaveMatchingText(WCHAR *psz, TfEditCookie ec)
|
|
{
|
|
BOOL fMatch = FALSE;
|
|
Assert(psz);
|
|
|
|
if ( !psz )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
WCHAR *pszStripped = psz;
|
|
ULONG ulCch = wcslen(psz);
|
|
|
|
// Remove trailing space.
|
|
while (ulCch > 0 && psz[ulCch-1] == L' ')
|
|
{
|
|
// Do not set null terminating character as this is a passed in string.
|
|
ulCch --;
|
|
}
|
|
// Skip leading space in input text.
|
|
while (pszStripped[0] == L' ')
|
|
{
|
|
pszStripped ++;
|
|
ulCch --;
|
|
}
|
|
// Now have space-stripped word pointed to by pszTmp and with length ulCch
|
|
|
|
if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
|
|
{
|
|
WCHAR *szRange = new WCHAR[ulCch+SPACEBUFFER+1];
|
|
WCHAR *szRangeStripped = szRange;
|
|
|
|
if (szRange)
|
|
{
|
|
// clone the IP range since we want to move the anchor
|
|
//
|
|
CComPtr<ITfRange> cpClonedRange;
|
|
cpInsertionPoint->Clone(&cpClonedRange);
|
|
|
|
ULONG cchRange; // max is the reco result
|
|
|
|
HRESULT hr = cpClonedRange->GetText(ec, TF_TF_MOVESTART, szRange, ulCch+SPACEBUFFER, &cchRange);
|
|
// Remove trailing space.
|
|
while (cchRange > 0 && szRange[cchRange-1] == L' ')
|
|
{
|
|
// Can set null terminating character as this is our string.
|
|
szRange[cchRange-1] = 0;
|
|
cchRange --;
|
|
}
|
|
// Skip leading space in input text.
|
|
while (szRangeStripped[0] == L' ')
|
|
{
|
|
szRangeStripped ++;
|
|
cchRange --;
|
|
}
|
|
// Now have space-stripped word pointed to by pszTmp and with length ulCch
|
|
if (S_OK == hr && cchRange > 0 && cchRange == ulCch)
|
|
{
|
|
if (wcsnicmp(pszStripped, szRangeStripped, ulCch) == 0) // Case insensitive compare.
|
|
{
|
|
fMatch = TRUE;
|
|
}
|
|
}
|
|
delete [] szRange;
|
|
}
|
|
}
|
|
return fMatch;
|
|
}
|
|
|
|
//
|
|
// GetNextBestHypothesis
|
|
//
|
|
// synopsis: this actually gets the nth alternative from the given reco result
|
|
// then adjusts the length accordingly based on the current selection
|
|
//
|
|
//
|
|
BOOL CSpTask::_GetNextBestHypothesis
|
|
(
|
|
ISpRecoResult *pResult,
|
|
ULONG nthHypothesis,
|
|
ULONG *pulNumElements,
|
|
LANGID langid,
|
|
WCHAR *pszBest,
|
|
CSpDynamicString & dsNext,
|
|
TfEditCookie ec
|
|
)
|
|
{
|
|
if ( pulNumElements )
|
|
*pulNumElements = 0;
|
|
|
|
// get the entire text & length from the saved IP
|
|
if (CComPtr<ITfRange> cpInsertionPoint = m_pime->GetSavedIP())
|
|
{
|
|
CSpDynamicString dstr;
|
|
CComPtr<ITfRange> cpClonedRange;
|
|
CComPtr<ISpRecoResult> cpRecoResult;
|
|
|
|
// clone the range since we move the anchor
|
|
HRESULT hr = cpInsertionPoint->Clone(&cpClonedRange);
|
|
|
|
ULONG cchRangeBuf = wcslen(pszBest);
|
|
|
|
cchRangeBuf *= 2; // guess the possible # of char
|
|
|
|
WCHAR *szRangeBuf = new WCHAR[cchRangeBuf+1];
|
|
|
|
if ( !szRangeBuf )
|
|
{
|
|
// Error: Out of Memory
|
|
// Return here as FALSE.
|
|
return FALSE;
|
|
}
|
|
|
|
while(S_OK == hr && !_IsRangeEmpty(ec, cpClonedRange))
|
|
{
|
|
hr = m_pime->_GetRangeText(cpClonedRange, TF_TF_MOVESTART, szRangeBuf, &cchRangeBuf);
|
|
if (S_OK == hr)
|
|
{
|
|
szRangeBuf[cchRangeBuf] = L'\0';
|
|
dstr.Append(szRangeBuf);
|
|
}
|
|
}
|
|
delete [] szRangeBuf;
|
|
|
|
// then get a best matching length of next best hypothesis
|
|
|
|
// the current recognition should at least have a good guess for # of elements
|
|
// since it turned out to be longer than the IP range.
|
|
//
|
|
Assert(pulNumElements);
|
|
|
|
ISpPhraseAlt **ppAlt = (ISpPhraseAlt **)cicMemAlloc(nthHypothesis*sizeof(ISpPhraseAlt *));
|
|
ULONG cAlt = 0;
|
|
if (!ppAlt)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
memset(ppAlt, 0, nthHypothesis * sizeof(ISpPhraseAlt *));
|
|
hr = pResult->GetAlternates( 0, *pulNumElements, nthHypothesis, ppAlt, &cAlt );
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
UINT i;
|
|
SPPHRASE *pPhrase;
|
|
|
|
if (nthHypothesis > cAlt)
|
|
{
|
|
*pulNumElements = 0;
|
|
goto no_more_alt;
|
|
}
|
|
|
|
Assert(nthHypothesis); // 1 based, can't be 0
|
|
|
|
hr = ((ppAlt)[nthHypothesis-1])->GetPhrase(&pPhrase);
|
|
if (S_OK == hr)
|
|
{
|
|
for (i = 0; i < pPhrase->Rule.ulCountOfElements; i++ )
|
|
{
|
|
int cchElement = wcslen(pPhrase->pElements[i].pszDisplayText) + 1;
|
|
|
|
WCHAR *szElement = new WCHAR[cchElement + 2];
|
|
|
|
if ( szElement )
|
|
{
|
|
// add +2 for trailing spaces
|
|
ParseSRElementByLocale(szElement, cchElement+2, pPhrase->pElements[i].pszDisplayText,
|
|
langid, pPhrase->pElements[i].bDisplayAttributes );
|
|
|
|
dsNext.Append(szElement);
|
|
|
|
delete [] szElement;
|
|
}
|
|
else
|
|
{
|
|
// Out of Memory.
|
|
// stop here.
|
|
break;
|
|
}
|
|
}
|
|
// now i holds the number of elements that we want to use in the result
|
|
// object
|
|
*pulNumElements = i;
|
|
|
|
::CoTaskMemFree(pPhrase);
|
|
} // if S_OK == GetPhrase
|
|
} // if S_OK == GetAlternates
|
|
|
|
no_more_alt:
|
|
// Release phrase alternates objects.
|
|
for (UINT i = 0; i < cAlt; i++)
|
|
{
|
|
if (NULL != ((ppAlt)[i]))
|
|
{
|
|
((ppAlt)[i])->Release();
|
|
}
|
|
}
|
|
// Free memory for array holding references to alternates objects.
|
|
if (ppAlt)
|
|
{
|
|
::cicMemFree(ppAlt);
|
|
}
|
|
|
|
}
|
|
|
|
return *pulNumElements > 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CSapiIMX::_EnsureWorkerWnd(void)
|
|
{
|
|
if (!m_hwndWorker)
|
|
{
|
|
m_hwndWorker = CreateWindow(c_szWorkerWndClass, "", WS_POPUP,
|
|
0,0,0,0,
|
|
NULL, 0, g_hInst, this);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// CSapiIMX::_GetAppMainWnd
|
|
//
|
|
// This function gets the real main window of current application.
|
|
// This main window would be used as the parent window of Add/Delete dialog
|
|
// and Training wizard.
|
|
//
|
|
HWND CSapiIMX::_GetAppMainWnd(void)
|
|
{
|
|
|
|
HWND hParentWnd = NULL;
|
|
HWND hMainWnd = NULL;
|
|
|
|
hMainWnd = GetFocus( );
|
|
|
|
if ( hMainWnd != NULL )
|
|
{
|
|
hParentWnd = GetParent(hMainWnd);
|
|
|
|
while ( hParentWnd != NULL )
|
|
{
|
|
hMainWnd = hParentWnd;
|
|
hParentWnd = GetParent(hMainWnd);
|
|
}
|
|
}
|
|
|
|
return hMainWnd;
|
|
}
|
|
//
|
|
// CSpTask::InitializeCallback
|
|
//
|
|
//
|
|
HRESULT CSpTask::InitializeCallback()
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called");
|
|
|
|
if (m_fCallbackInitialized)
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "m_fCallbackInitialized is true");
|
|
return S_OK;
|
|
}
|
|
|
|
if (!m_fSapiInitialized)
|
|
return S_FALSE; // can't do this without SAPI
|
|
|
|
// set recognition notification
|
|
CComPtr<ISpNotifyTranslator> cpNotify;
|
|
HRESULT hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
|
|
TraceMsg(TF_SAPI_PERF, "SpNotifyTranslator for Reco is generated, hr=%x", hr);
|
|
|
|
|
|
// set this class instance to notify control object
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pime->_EnsureWorkerWnd();
|
|
|
|
hr = cpNotify->InitCallback( NotifyCallback, 0, (LPARAM)this );
|
|
TraceMsg(TF_SAPI_PERF, "InitCallback is Done, hr=%x", hr);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_cpRecoCtxt->SetNotifySink(cpNotify);
|
|
TraceMsg(TF_SAPI_PERF, "SetNotifySink is Done, hr=%x", hr);
|
|
}
|
|
|
|
// set the events we're interested in
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
const ULONGLONG ulInterest = SPFEI(SPEI_SOUND_START) |
|
|
SPFEI(SPEI_SOUND_END) |
|
|
SPFEI(SPEI_PHRASE_START) |
|
|
SPFEI(SPEI_RECOGNITION) |
|
|
SPFEI(SPEI_RECO_OTHER_CONTEXT) |
|
|
SPFEI(SPEI_FALSE_RECOGNITION) |
|
|
SPFEI(SPEI_HYPOTHESIS) |
|
|
SPFEI(SPEI_RECO_STATE_CHANGE) |
|
|
SPFEI(SPEI_INTERFERENCE);
|
|
|
|
hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest);
|
|
TraceMsg(TF_SAPI_PERF, "SetInterest is Done, hr=%x", hr);
|
|
}
|
|
|
|
if ( SUCCEEDED(hr) && m_cpVoice)
|
|
{
|
|
// set recognition notification
|
|
CComPtr<ISpNotifyTranslator> cpNotify;
|
|
hr = cpNotify.CoCreateInstance(CLSID_SpNotifyTranslator);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Create SpNotifyTranslator for spVoice, hr=%x", hr);
|
|
|
|
// set this class instance to notify control object
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pime->_EnsureWorkerWnd();
|
|
|
|
hr = cpNotify->InitCallback( SpeakNotifyCallback, 0, (LPARAM)this );
|
|
TraceMsg(TF_SAPI_PERF, "InitCallback for SpVoice, hr=%x", hr);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_cpVoice->SetNotifySink(cpNotify);
|
|
TraceMsg(TF_SAPI_PERF, "SetNotifySink for SpVoice, hr=%x", hr);
|
|
}
|
|
|
|
if ( hr == S_OK )
|
|
{
|
|
const ULONGLONG ulInterestSpeak = SPFEI(SPEI_WORD_BOUNDARY) |
|
|
SPFEI(SPEI_START_INPUT_STREAM) |
|
|
SPFEI(SPEI_END_INPUT_STREAM);
|
|
|
|
hr = m_cpVoice->SetInterest(ulInterestSpeak, ulInterestSpeak);
|
|
TraceMsg(TF_SAPI_PERF, "SetInterest for spVoice, hr=%x", hr);
|
|
}
|
|
}
|
|
|
|
m_fCallbackInitialized = TRUE;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::InitializeCallback is called is done!!! hr=%x", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// _LoadGrammars
|
|
//
|
|
// synopsis - load CFG for dictation and commands available during dictation
|
|
//
|
|
|
|
HRESULT CSpTask::_LoadGrammars()
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is called");
|
|
|
|
if (m_cpRecoCtxt)
|
|
{
|
|
|
|
hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_DICT, &m_cpDictGrammar);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Create Dict Grammar, hr=%x", hr);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_cpDictGrammar->LoadDictation(NULL, SPLO_STATIC);
|
|
TraceMsg(TF_SAPI_PERF, "Load Dictation, hr = %x", hr);
|
|
}
|
|
|
|
if ( S_OK == hr && m_langid != 0x0804) // Chinese Engine doesn't support SPTOPIC_SPELLING,
|
|
// This is the temporal workaround.
|
|
{
|
|
// we keep going regardless of availabillity of spelling topic
|
|
// in the SR engine for the language so we use internal HRESULT
|
|
// for this block of code
|
|
//
|
|
HRESULT hrInternal;
|
|
|
|
// Load Spelling topic
|
|
hrInternal = m_cpRecoCtxt->CreateGrammar(GRAM_ID_SPELLING, &m_cpSpellingGrammar);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Create Spelling grammar, hrInternal=%x", hrInternal);
|
|
|
|
if (SUCCEEDED(hrInternal))
|
|
{
|
|
hrInternal = m_cpSpellingGrammar->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC);
|
|
TraceMsg(TF_SAPI_PERF, "Load Spelling dictation grammar, hrInternal=%x", hrInternal);
|
|
}
|
|
|
|
|
|
// this is now an experiment for English/Japanese only
|
|
//
|
|
if (SUCCEEDED(hrInternal))
|
|
{
|
|
hrInternal = m_cpSpellingGrammar->LoadCmdFromResource(
|
|
g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SPELLING_TOPIC_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_STATIC);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Load CFG grammar spell.cfg, hr=%x", hrInternal);
|
|
|
|
}
|
|
|
|
if (S_OK == hrInternal)
|
|
{
|
|
m_fSpellingModeEnabled = TRUE;
|
|
}
|
|
else
|
|
m_fSpellingModeEnabled = FALSE;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "m_fSpellingModeEnabled=%d", m_fSpellingModeEnabled);
|
|
}
|
|
|
|
//
|
|
// load the dictation mode commands
|
|
//
|
|
if (SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CCDICT, &m_cpDictCmdGrammar);
|
|
TraceMsg(TF_SAPI_PERF, "Create DictCmdGrammar, hr=%x", hr);
|
|
}
|
|
if (S_OK == hr)
|
|
{
|
|
hr = S_FALSE;
|
|
|
|
|
|
// try resource first because loading cmd from file takes
|
|
// quite long time
|
|
//
|
|
if (m_langid == 0x409 || // English
|
|
m_langid == 0x411 || // Japanese
|
|
m_langid == 0x804 ) // Simplified Chinese
|
|
{
|
|
hr = m_cpDictCmdGrammar->LoadCmdFromResource(
|
|
g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_DYNAMIC);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Load dictcmd.cfg, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
// in case if we don't have built-in grammar
|
|
_GetCmdFileName(m_langid);
|
|
if (m_szCmdFile[0])
|
|
{
|
|
hr = m_cpDictCmdGrammar->LoadCmdFromFile(m_szCmdFile, SPLO_DYNAMIC);
|
|
}
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
m_cpDictCmdGrammar.Release();
|
|
}
|
|
}
|
|
|
|
// load shared command grammars
|
|
|
|
if (SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_CMDSHARED, &m_cpSharedGrammarInDict);
|
|
TraceMsg(TF_SAPI_PERF, "Create SharedCmdGrammarInDict, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = S_FALSE;
|
|
|
|
if (m_langid == 0x409 || // English
|
|
m_langid == 0x411 || // Japanese
|
|
m_langid == 0x804 ) // Simplified Chinese
|
|
{
|
|
hr = m_cpSharedGrammarInDict->LoadCmdFromResource(
|
|
g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_SHAREDCMD_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_DYNAMIC);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Load Shrdcmd.cfg, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
// in case if we don't have built-in grammar
|
|
// it provides a way for customer to localize their grammars in different languages
|
|
_GetCmdFileName(m_langid);
|
|
if (m_szShrdCmdFile[0])
|
|
{
|
|
hr = m_cpSharedGrammarInDict->LoadCmdFromFile(m_szShrdCmdFile, SPLO_DYNAMIC);
|
|
}
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
m_cpSharedGrammarInDict.Release();
|
|
}
|
|
else if ( PRIMARYLANGID(m_langid) == LANG_ENGLISH ||
|
|
PRIMARYLANGID(m_langid) == LANG_JAPANESE ||
|
|
PRIMARYLANGID(m_langid) == LANG_CHINESE)
|
|
{
|
|
// means this language's grammar support Textbuffer commands.
|
|
m_fSelectionEnabled = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// load mode bias grammars
|
|
//
|
|
if (S_OK == hr)
|
|
{
|
|
hr = m_cpRecoCtxt->CreateGrammar(GRID_INTEGER_STANDALONE, &m_cpNumModeGrammar);
|
|
TraceMsg(TF_SAPI_PERF, "Create NumModeGrammar, hr=%x", hr);
|
|
}
|
|
if (S_OK == hr)
|
|
{
|
|
hr = S_FALSE;
|
|
|
|
|
|
// try resource first because loading cmd from file takes
|
|
// quite long time
|
|
//
|
|
if ( m_langid == 0x409 // English
|
|
|| m_langid == 0x411 // Japanese
|
|
|| m_langid == 0x804 // Simplified Chinese
|
|
)
|
|
{
|
|
hr = m_cpNumModeGrammar->LoadCmdFromResource(
|
|
g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_NUMMODE_COMMAND_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_DYNAMIC);
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Load dictnum.cfg, hr=%x", hr);
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
// in case if we don't have buit-in grammar
|
|
//
|
|
if (m_szNumModeCmdFile[0])
|
|
{
|
|
hr = m_cpNumModeGrammar->LoadCmdFromFile(m_szNumModeCmdFile, SPLO_DYNAMIC);
|
|
}
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
m_cpNumModeGrammar.Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
// By default, Activate all the grammars and Disable the context for Perfomance.
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpRecoCtxt->SetContextState(SPCS_DISABLED);
|
|
m_fDictCtxtEnabled = FALSE;
|
|
}
|
|
|
|
// Activate Dictation and spell.
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = _ActiveDictOrSpell(DC_Dictation, TRUE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveDictOrSpell(DC_Dict_Spell, TRUE);
|
|
}
|
|
|
|
// Automatically active all rules in C&C grammars.
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
if ( m_pime->_AllDictCmdsDisabled( ) )
|
|
{
|
|
hr = _ActivateCmdInDictMode(FALSE);
|
|
|
|
// Still needs to activate spelling grammar if it exists.
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
|
|
|
|
// Needs to activate "Force Num" grammar in dication strong mode.
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
}
|
|
else
|
|
{
|
|
if ( m_pime->_AllCmdsEnabled( ) )
|
|
hr = _ActivateCmdInDictMode(TRUE);
|
|
else
|
|
{
|
|
// Some category commands are disabled.
|
|
// active them individually.
|
|
|
|
hr = _ActiveCategoryCmds(DC_CC_SelectCorrect, m_pime->_SelectCorrectCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Navigation, m_pime->_NavigationCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Casing, m_pime->_CasingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Editing, m_pime->_EditingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Keyboard, m_pime->_KeyboardCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_TTS, m_pime->_TTSCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_LangBar, m_pime->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = _ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// we don't fail even if C&C grammars are not available
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::_LoadGrammars is done!!!!");
|
|
return S_OK;
|
|
}
|
|
|
|
WCHAR * CSpTask::_GetCmdFileName(LANGID langid)
|
|
{
|
|
|
|
if (!m_szCmdFile[0])
|
|
{
|
|
_GetCmdFileName(langid, m_szCmdFile, ARRAYSIZE(m_szCmdFile), IDS_CMD_FILE);
|
|
}
|
|
|
|
// load the name of shared commands grammar
|
|
if (!m_szShrdCmdFile[0])
|
|
{
|
|
_GetCmdFileName(langid, m_szShrdCmdFile, ARRAYSIZE(m_szShrdCmdFile), IDS_SHARDCMD_FILE);
|
|
}
|
|
|
|
// load the name of optional grammar
|
|
if (!m_szNumModeCmdFile[0])
|
|
{
|
|
_GetCmdFileName(langid, m_szNumModeCmdFile, ARRAYSIZE(m_szNumModeCmdFile), IDS_NUMMODE_CMD_FILE );
|
|
}
|
|
|
|
return m_szCmdFile;
|
|
}
|
|
|
|
void CSpTask::_GetCmdFileName(LANGID langid, WCHAR *sz, int cch, DWORD dwId)
|
|
{
|
|
/*
|
|
// now we only have a command file for English/Japanese
|
|
// when cfgs are available, we'll get the name of cmd file
|
|
// and the rule names from resources using findresourceex
|
|
//
|
|
if ((PRIMARYLANGID(langid) == LANG_ENGLISH)
|
|
|| (PRIMARYLANGID(langid) == LANG_JAPANESE)
|
|
|| (PRIMARYLANGID(langid) == LANG_CHINESE))
|
|
{
|
|
|
|
// To supply customers a way to localize their grammars in different languages,
|
|
// we don't want the above condition check.
|
|
*/
|
|
char szFilePath[MAX_PATH];
|
|
char *pszFileName;
|
|
char szCp[MAX_PATH];
|
|
int ilen;
|
|
|
|
if (!GetModuleFileName(g_hInst, szFilePath, ARRAYSIZE(szFilePath)))
|
|
return;
|
|
|
|
// is this dbcs safe?
|
|
pszFileName = strrchr(szFilePath, (int)'\\');
|
|
|
|
if (pszFileName)
|
|
{
|
|
pszFileName++;
|
|
*pszFileName = '\0';
|
|
}
|
|
else
|
|
{
|
|
szFilePath[0] = '\\';
|
|
szFilePath[1] = '\0';
|
|
pszFileName = &szFilePath[1];
|
|
}
|
|
|
|
ilen = lstrlen(szFilePath);
|
|
|
|
CicLoadStringA(g_hInst, dwId, pszFileName, ARRAYSIZE(szFilePath)-ilen);
|
|
|
|
if (GetLocaleInfo(langid, LOCALE_IDEFAULTANSICODEPAGE, szCp, ARRAYSIZE(szCp))>0)
|
|
{
|
|
int iACP = atoi(szCp);
|
|
|
|
MultiByteToWideChar(iACP, NULL, szFilePath, -1, sz, cch);
|
|
}
|
|
// }
|
|
}
|
|
|
|
void CSpTask::_ReleaseSAPI(void)
|
|
{
|
|
// - release data or memory from recognition context
|
|
// - release interfaces if they are not defined as CComPtr
|
|
_UnloadGrammars();
|
|
|
|
m_cpResMgr.Release();
|
|
|
|
if ( m_cpVoice)
|
|
m_cpVoice->SetNotifySink(NULL);
|
|
|
|
m_cpVoice.Release();
|
|
|
|
if (m_cpRecoCtxt)
|
|
m_cpRecoCtxt->SetNotifySink(NULL);
|
|
|
|
if ( m_cpRecoCtxtForCmd )
|
|
m_cpRecoCtxtForCmd->SetNotifySink(NULL);
|
|
|
|
#ifdef RECOSLEEP
|
|
if ( m_pSleepClass )
|
|
{
|
|
delete m_pSleepClass;
|
|
m_pSleepClass = NULL;
|
|
}
|
|
#endif
|
|
|
|
m_cpRecoCtxt.Release();
|
|
m_cpRecoCtxtForCmd.Release();
|
|
m_cpRecoEngine.Release();
|
|
m_fSapiInitialized = FALSE;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetAudioRetainStatus(BOOL fRetain)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
// FutureConsider: support the data format
|
|
if (m_cpRecoCtxt)
|
|
hr = m_cpRecoCtxt->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL);
|
|
|
|
if (m_cpRecoCtxtForCmd)
|
|
hr = m_cpRecoCtxtForCmd->SetAudioOptions(fRetain?SPAO_RETAIN_AUDIO: SPAO_NONE, NULL, NULL);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetRecognizerInterest(ULONGLONG ulInterest)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( m_cpRecoCtxt )
|
|
{
|
|
hr = m_cpRecoCtxt->SetInterest(ulInterest, ulInterest);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
//
|
|
//
|
|
// Activate all the command grammas in Dictation mode.
|
|
//
|
|
// By default we want to set SPRS_ACTIVE to all the command grammar rules
|
|
// in dictation mode unless user disables some of commands through dictation
|
|
// property page.
|
|
//
|
|
// Please note: Only when all the commands are enabled, this function is called.
|
|
//
|
|
// otherwise,
|
|
//
|
|
// When some of the commands are disabled, we should active individual cateogry commands by
|
|
// calling _ActiveCategoryCmds( ).
|
|
//
|
|
HRESULT CSpTask::_ActivateCmdInDictMode(BOOL fActive)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
BOOL fRealActive = fActive;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "CSpTask::_ActivateCmdInDictMode is called, fActive=%d", fActive);
|
|
|
|
if (m_cpRecoCtxt)
|
|
{
|
|
// Automatically active or inactive all rules in grammar.
|
|
|
|
// Rules in Dictcmd.cfg
|
|
|
|
if ( m_cpDictCmdGrammar )
|
|
{
|
|
hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in DictCmdGrammar, fRealActive=%d", fRealActive);
|
|
}
|
|
|
|
// Rules in Sharedcmd.cfg
|
|
|
|
if ( m_cpSharedGrammarInDict )
|
|
{
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in SharedCmdGrammar In Dictation Mode, fRealActive=%d", fRealActive);
|
|
}
|
|
|
|
// Rules in ITN grammar
|
|
|
|
if ( hr == S_OK && m_cpNumModeGrammar )
|
|
{
|
|
hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpNumModeGrammar, fRealActive=%d", fRealActive);
|
|
}
|
|
|
|
// Rules in Spell grammar
|
|
|
|
if ( m_cpSpellingGrammar )
|
|
{
|
|
hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fRealActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fRealActive=%d", fRealActive);
|
|
}
|
|
}
|
|
|
|
TraceMsg(TF_SAPI_PERF, "Exit from CSpTask::_ActivateCmdInDictMode");
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Active commands by category.
|
|
//
|
|
// Some commands are dictation mode only such as "spell that" and Number mode commands.
|
|
// some others are available in both modes,
|
|
//
|
|
// When some category commands are disabled, caller must call this function instead of
|
|
// _ActivateCmdInDictMode to set individual category commands.
|
|
//
|
|
HRESULT CSpTask::_ActiveCategoryCmds(DICT_CATCMD_ID dcId, BOOL fActive, DWORD dwMode)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fActiveDictMode, fActiveCommandMode;
|
|
|
|
if ( dcId >= DC_Max ) return E_INVALIDARG;
|
|
|
|
if (m_fIn_Activate)
|
|
return hr;
|
|
|
|
fActiveDictMode = (m_cpRecoCtxt && (dwMode & ACTIVE_IN_DICTATION_MODE) ) ? TRUE : FALSE;
|
|
|
|
if ( m_cpRecoCtxtForCmd && m_cpSharedGrammarInVoiceCmd && ( dwMode & ACTIVE_IN_COMMAND_MODE) )
|
|
fActiveCommandMode = TRUE;
|
|
else
|
|
fActiveCommandMode = FALSE;
|
|
|
|
m_fIn_Activate = TRUE;
|
|
|
|
switch (dcId)
|
|
{
|
|
case DC_CC_SelectCorrect :
|
|
|
|
// This category includes below rules in different grammars.
|
|
//
|
|
// shrdcmd.xml:
|
|
// selword, SelectThrough, SelectSimpleCmds,
|
|
//
|
|
// dictcmd.xml:
|
|
// commands
|
|
//
|
|
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_SelectCorrect status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode)
|
|
{
|
|
// for dictation mode
|
|
if ( m_cpSharedGrammarInDict )
|
|
{
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode )
|
|
{
|
|
// for voice command mode
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelword, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelThrough, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
if ( hr == S_OK )
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szSelectSimple, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_Navigation :
|
|
|
|
// This category includes rule NavigationCmds in shrdcmd.xml
|
|
//
|
|
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_Navigation status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode && m_cpSharedGrammarInDict)
|
|
{
|
|
// for dictation mode
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode )
|
|
{
|
|
// for voice command mode
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szNavigationCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_Casing :
|
|
|
|
// This category includes rule CasingCmds in shrdcmd.xml
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_Casing status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode && m_cpSharedGrammarInDict )
|
|
{
|
|
// for dictation mode
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode)
|
|
{
|
|
// for voice command mode
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szCasingCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_Editing :
|
|
|
|
// This category includes rule EditCmds in shrdcmd.xml
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_Editing status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode && m_cpSharedGrammarInDict)
|
|
{
|
|
// for dictation mode
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode)
|
|
{
|
|
// for voice command mode
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szEditCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_Keyboard :
|
|
|
|
// This category includes rule KeyboardCmds in shrdcmd.xml
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_Keyboard status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode && m_cpSharedGrammarInDict)
|
|
{
|
|
// for dictation mode
|
|
hr = m_cpSharedGrammarInDict->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode)
|
|
{
|
|
// for voice command mode
|
|
hr = m_cpSharedGrammarInVoiceCmd->SetRuleState(c_szKeyboardCmds, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_TTS :
|
|
|
|
// The rule for this category is not implemented yet!!!
|
|
|
|
break;
|
|
|
|
case DC_CC_LangBar :
|
|
|
|
// This category includes rule ToolbarCmd in dictcmd.xml for dictation mode.
|
|
// for voice command mode, it is a dynamical rule.
|
|
//
|
|
|
|
TraceMsg(TF_SAPI_PERF, "DC_CC_LangBar status: %d, mode=%d", fActive, dwMode);
|
|
|
|
if ( fActiveDictMode && m_cpDictCmdGrammar)
|
|
{
|
|
// for dictation mode
|
|
hr = m_cpDictCmdGrammar->SetRuleState(c_szDictTBRule, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
}
|
|
|
|
if ( (hr == S_OK) && fActiveCommandMode )
|
|
{
|
|
// for voice command mode
|
|
// Change toolbar grammar status if it has already been built.
|
|
if (m_pLangBarSink && m_pLangBarSink->_IsTBGrammarBuiltOut( ))
|
|
m_pLangBarSink->_ActivateGrammar(fActive);
|
|
}
|
|
|
|
break;
|
|
|
|
case DC_CC_Num_Mode :
|
|
|
|
if (fActiveDictMode && m_cpNumModeGrammar)
|
|
{
|
|
hr = m_cpNumModeGrammar->SetRuleState(NULL, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "CC Number rule status changed to %d", fActive);
|
|
}
|
|
break;
|
|
|
|
case DC_CC_UrlHistory :
|
|
|
|
if (fActiveDictMode && m_cpDictCmdGrammar)
|
|
{
|
|
hr = m_cpDictCmdGrammar->SetRuleState(c_szStaticUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
if (S_OK == hr)
|
|
hr = m_cpDictCmdGrammar->SetRuleState(c_szDynUrlHist, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
if (S_OK == hr && m_cpUrlSpellingGrammar)
|
|
{
|
|
hr = m_cpUrlSpellingGrammar->SetRuleState(c_szStaticUrlSpell, NULL, fActive ? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DC_CC_Spelling :
|
|
|
|
if ( fActiveDictMode && m_cpSpellingGrammar )
|
|
{
|
|
hr = m_cpSpellingGrammar->SetRuleState(NULL, NULL, fActive? SPRS_ACTIVE: SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Set rules status in m_cpSpellingGrammar, fActive=%d", fActive);
|
|
}
|
|
break;
|
|
}
|
|
|
|
m_fIn_Activate = FALSE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Set the status for Dictation grammar or spelling grammar In Dictation mode only.
|
|
HRESULT CSpTask::_ActiveDictOrSpell(DICT_CATCMD_ID dcId, BOOL fActive)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( dcId >= DC_Max ) return E_INVALIDARG;
|
|
|
|
if (m_fIn_Activate)
|
|
return hr;
|
|
|
|
m_fIn_Activate = TRUE;
|
|
|
|
switch (dcId)
|
|
{
|
|
case DC_Dictation :
|
|
if (m_cpDictGrammar)
|
|
{
|
|
hr = m_cpDictGrammar->SetDictationState(fActive ? SPRS_ACTIVE : SPRS_INACTIVE);
|
|
TraceMsg(TF_SAPI_PERF, "Dictation status changed to %d", fActive);
|
|
}
|
|
break;
|
|
case DC_Dict_Spell :
|
|
if (m_cpSpellingGrammar)
|
|
{
|
|
hr = _SetSpellingGrammarStatus(fActive);
|
|
TraceMsg(TF_SAPI_PERF, "Dict Spell status changed to %d", fActive);
|
|
}
|
|
break;
|
|
}
|
|
|
|
m_fIn_Activate = FALSE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetSpellingGrammarStatus( BOOL fActive, BOOL fForce)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceMsg(TF_GENERAL, "_SetSpellingGrammarStatus is called, fActive=%d, m_fSelectStatus=%d",fActive, m_fSelectStatus);
|
|
|
|
|
|
if ( m_cpSpellingGrammar )
|
|
{
|
|
// if dictation is previously deactivated because of 'force' spelling
|
|
// we need to reactivate the dictation grammar
|
|
if (m_fDictationDeactivated)
|
|
{
|
|
hr = _ActiveDictOrSpell(DC_Dictation, TRUE);
|
|
if (S_OK == hr)
|
|
{
|
|
m_fDictationDeactivated = FALSE;
|
|
}
|
|
}
|
|
|
|
// if this is 'force' mode, we deactivate dictation for the moment
|
|
if (fForce)
|
|
{
|
|
hr = _ActiveDictOrSpell(DC_Dictation, FALSE);
|
|
if (S_OK == hr)
|
|
{
|
|
m_fDictationDeactivated = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( (m_fSelectStatus || fForce) && fActive) // It is not empty
|
|
hr = m_cpSpellingGrammar->SetDictationState(SPRS_ACTIVE);
|
|
else
|
|
hr = m_cpSpellingGrammar->SetDictationState(SPRS_INACTIVE);
|
|
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpTask::_AddUrlPartsToGrammar(STATURL *pStat)
|
|
{
|
|
Assert(pStat);
|
|
|
|
// get a url broken down to pieces
|
|
if (!pStat->pwcsUrl)
|
|
return S_FALSE;
|
|
|
|
WCHAR *pch = pStat->pwcsUrl;
|
|
|
|
const WCHAR c_szHttpSlash2[] = L"http://";
|
|
|
|
// skip the prefixed http: stuff because we've already added it by now
|
|
if (_wcsnicmp(pch, c_szHttpSlash2, ARRAYSIZE(c_szHttpSlash2)-1) == 0)
|
|
pch += ARRAYSIZE(c_szHttpSlash2)-1;
|
|
|
|
WCHAR *pchWord = pch;
|
|
HRESULT hr = S_OK;
|
|
|
|
// an assumption 1) people speak the first portion of URL www.microsoft.com
|
|
// as a sentence
|
|
|
|
WCHAR *pchUrl = pch; // points either biggining of url or
|
|
// right after 'http://' add the first part
|
|
BOOL fUrlAdded = FALSE; // of url that is between after this and '/'
|
|
|
|
while(S_OK == hr && *pch)
|
|
{
|
|
if (*pch == L'/')
|
|
{
|
|
if (!fUrlAdded)
|
|
{
|
|
if( pch - pchUrl > 1)
|
|
{
|
|
WCHAR ch = *pch;
|
|
*pch = L'\0';
|
|
|
|
SPPROPERTYINFO pi = {0};
|
|
pi.pszValue = pchUrl;
|
|
hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi);
|
|
*pch = ch;
|
|
}
|
|
fUrlAdded = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*pch = L'\0';
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*pch == L'.' || *pch == L'/' || *pch == L'?' || *pch == '=' || *pch =='&')
|
|
{
|
|
WCHAR ch = *pch;
|
|
|
|
*pch = L'\0';
|
|
|
|
// reject 1 character parts
|
|
if (pch - pchWord > 1)
|
|
{
|
|
SPPROPERTYINFO pi = {0};
|
|
pi.pszValue = pchWord;
|
|
|
|
if (wcscmp(c_szWWW, pchWord) != 0 && wcscmp(c_szCom, pchWord) != 0)
|
|
{
|
|
// a few words can possibly return 'ambiguity' errors
|
|
// we need to ignore it and continue. so we're not checking
|
|
// the return here.
|
|
//
|
|
m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi);
|
|
}
|
|
}
|
|
*pch = ch;
|
|
|
|
pchWord = pch + 1;
|
|
}
|
|
pch++;
|
|
}
|
|
|
|
// add the last part of URL
|
|
if (S_OK == hr && *pchWord && pch - pchWord > 1)
|
|
{
|
|
SPPROPERTYINFO pi = {0};
|
|
pi.pszValue = pchWord;
|
|
hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchWord, L" ", SPWT_LEXICAL, (float)1, &pi);
|
|
}
|
|
// add the first part of url if we haven't yet
|
|
if (S_OK == hr && !fUrlAdded && pch - pchUrl > 1)
|
|
{
|
|
SPPROPERTYINFO pi = {0};
|
|
pi.pszValue = pchUrl;
|
|
hr = m_cpDictCmdGrammar->AddWordTransition(m_hRuleUrlHist, NULL, pchUrl, L".", SPWT_LEXICAL, (float)1, &pi);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
BOOL CSpTask::_EnsureModeBiasGrammar()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( m_cpDictCmdGrammar )
|
|
{
|
|
// Check if the grammar has a static rule UrlSpelling
|
|
SPSTATEHANDLE hRuleUrlSpell = 0;
|
|
hr = m_cpDictCmdGrammar->GetRule(c_szStaticUrlSpell, 0, SPRAF_TopLevel|SPRAF_Active, FALSE, &hRuleUrlSpell);
|
|
|
|
if ( !hRuleUrlSpell )
|
|
return FALSE;
|
|
}
|
|
|
|
// ensure spelling LM
|
|
if (!m_cpUrlSpellingGrammar)
|
|
{
|
|
CComPtr<ISpRecoGrammar> cpUrlSpelling;
|
|
hr = m_cpRecoCtxt->CreateGrammar(GRAM_ID_URLSPELL, &cpUrlSpelling);
|
|
|
|
// load dictation with spelling topic
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpUrlSpelling->LoadDictation(SPTOPIC_SPELLING, SPLO_STATIC);
|
|
}
|
|
|
|
// load the 'rule' for the free form dictation
|
|
if (S_OK == hr)
|
|
{
|
|
// i'm sharing the command cfg here for spelling with dictation
|
|
// command for simplicity
|
|
//
|
|
hr = cpUrlSpelling->LoadCmdFromResource( g_hInstSpgrmr,
|
|
(const WCHAR*)MAKEINTRESOURCE(ID_SPTIP_DICTATION_COMMAND_CFG),
|
|
L"SRGRAMMAR",
|
|
m_langid,
|
|
SPLO_STATIC);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
m_cpUrlSpellingGrammar = cpUrlSpelling; // add the ref count
|
|
}
|
|
}
|
|
|
|
if (m_hRuleUrlHist)
|
|
{
|
|
hr = m_cpDictCmdGrammar->ClearRule(m_hRuleUrlHist);
|
|
m_hRuleUrlHist = 0;
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
hr = m_cpDictCmdGrammar->GetRule(c_szDynUrlHist, 0, SPRAF_TopLevel|SPRAF_Active|SPRAF_Dynamic, TRUE, &m_hRuleUrlHist);
|
|
|
|
|
|
// first add basic parts for URL
|
|
CComPtr<IUrlHistoryStg> cpUrlHistStg;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER, IID_IUrlHistoryStg, (void **)&cpUrlHistStg);
|
|
}
|
|
|
|
CComPtr<IEnumSTATURL> cpEnumUrl;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = cpUrlHistStg->EnumUrls(&cpEnumUrl);
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
int i = 0;
|
|
STATURL stat;
|
|
stat.cbSize = SIZEOF(stat.cbSize);
|
|
while(i < 10 && S_OK == hr && cpEnumUrl->Next(1, &stat, NULL)==S_OK && stat.pwcsUrl)
|
|
{
|
|
hr = _AddUrlPartsToGrammar(&stat);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = m_cpDictCmdGrammar->Commit(0);
|
|
}
|
|
|
|
return (S_OK == hr) ? TRUE : FALSE;
|
|
}
|
|
|
|
HRESULT CSpTask::_SetModeBias(BOOL fActive, REFGUID rGuid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fKillDictation = FALSE;
|
|
|
|
if (m_fIn_SetModeBias)
|
|
return E_FAIL;
|
|
|
|
m_fIn_SetModeBias = TRUE;
|
|
if (m_cpDictGrammar)
|
|
{
|
|
fKillDictation = !m_pime->_IsModeBiasDictationEnabled();
|
|
if (fActive)
|
|
{
|
|
BOOL fUrlHistory = FALSE;
|
|
|
|
if (IsEqualGUID(GUID_MODEBIAS_URLHISTORY, rGuid)
|
|
|| IsEqualGUID(GUID_MODEBIAS_FILENAME, rGuid))
|
|
fUrlHistory = TRUE;
|
|
|
|
|
|
// first deactivate rules when we are not setting them
|
|
if (!fUrlHistory && m_fUrlHistoryMode)
|
|
{
|
|
hr = _ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE);
|
|
}
|
|
|
|
// this check with m_fUrlHistoryMode is preventing us from updating url dynamic grammar
|
|
// when mic is re-opened. we think removing this won't cause much perf degredation.
|
|
//
|
|
if (fUrlHistory /* && !m_fUrlHistoryMode */)
|
|
{
|
|
if (m_cpDictCmdGrammar && m_pime->GetDICTATIONSTAT_DictOnOff() && _EnsureModeBiasGrammar())
|
|
{
|
|
hr = _ActiveCategoryCmds(DC_CC_UrlHistory, TRUE, ACTIVE_IN_DICTATION_MODE);
|
|
}
|
|
else
|
|
fUrlHistory = FALSE;
|
|
|
|
if (fUrlHistory)
|
|
{
|
|
fKillDictation = TRUE;
|
|
}
|
|
}
|
|
|
|
// sync the global status
|
|
m_fUrlHistoryMode = fUrlHistory;
|
|
}
|
|
else
|
|
{
|
|
// reset all modebias
|
|
if (m_fUrlHistoryMode)
|
|
_ActiveCategoryCmds(DC_CC_UrlHistory, FALSE, ACTIVE_IN_DICTATION_MODE);
|
|
}
|
|
|
|
|
|
// kill dictation grammar when mode requires it
|
|
// we should activate dictation only when deactivating
|
|
// the modebias grammar *and* when we are the focus
|
|
// thread because we've already deactivated dictation
|
|
// when focus switched away
|
|
//
|
|
if (/* !fActive && */
|
|
m_cpDictGrammar &&
|
|
m_pime->GetDICTATIONSTAT_DictOnOff() &&
|
|
S_OK == m_pime->IsActiveThread())
|
|
{
|
|
#ifdef _DEBUG_
|
|
TCHAR szModule[MAX_PATH];
|
|
GetModuleFileName(NULL, szModule, ARRAYSIZE(szModule));
|
|
|
|
TraceMsg(TF_GENERAL, "%s : CSpTask::_SetModeBias() - Turning Dictation grammar %s", szModule, fKillDictation ? "Off" : "On");
|
|
#endif
|
|
if (!fActive && fKillDictation)
|
|
{
|
|
fKillDictation = FALSE;
|
|
}
|
|
|
|
hr = _ActiveDictOrSpell(DC_Dictation, fKillDictation ? SPRS_INACTIVE : SPRS_ACTIVE);
|
|
}
|
|
}
|
|
m_fIn_SetModeBias = FALSE;
|
|
return hr;
|
|
}
|
|
|
|
void CSpTask::_SetInputOnOffState(BOOL fOn)
|
|
{
|
|
TraceMsg(TF_GENERAL, "_SetInputOnOffState is called, fOn=%d", fOn);
|
|
|
|
if (m_fIn_SetInputOnOffState)
|
|
return;
|
|
|
|
m_fIn_SetInputOnOffState = TRUE;
|
|
|
|
// here we make sure we erase feedback UI
|
|
|
|
// We only adjust these if we are the active thread. Otherwise leave in current state since we either do
|
|
// not have focus or the stage is visible. This maintains our previous behavior where we would not get here
|
|
// if we were not the active thread.
|
|
if (S_OK == m_pime->IsActiveThread())
|
|
{
|
|
if (fOn)
|
|
{
|
|
if (!m_pime->Get_SPEECH_DISABLED_DictationDisabled() && m_pime->GetDICTATIONSTAT_DictOnOff())
|
|
_SetDictRecoCtxtState(TRUE);
|
|
|
|
if ( !m_pime->Get_SPEECH_DISABLED_CommandingDisabled( ) && m_pime->GetDICTATIONSTAT_CommandingOnOff( ) )
|
|
_SetCmdRecoCtxtState(TRUE);
|
|
}
|
|
else
|
|
{
|
|
_SetDictRecoCtxtState(FALSE);
|
|
_SetCmdRecoCtxtState(FALSE);
|
|
_StopInput();
|
|
}
|
|
}
|
|
|
|
// Regardless of focus / stage visibility, we need to turn on the engine if necessary here since there
|
|
// may be *no* speech tip with focus to do this. This means we may have multiple tips turning the reco
|
|
// state on / off simultaneously.
|
|
if(m_cpRecoEngine)
|
|
{
|
|
m_fInputState = fOn;
|
|
|
|
if ( _GetInputOnOffState( ) != fOn )
|
|
{
|
|
TraceMsg(TF_GENERAL, "Call SetRecoState, %s", fOn ? "SPRST_ACTIVE" : "SPRST_INACTIVE");
|
|
|
|
m_cpRecoEngine->SetRecoState(fOn ? SPRST_ACTIVE : SPRST_INACTIVE);
|
|
}
|
|
|
|
// DO NOT ADD DEBUGGING CODE HERE TO PRINT OUT STATE - CAN BLOCK CICERO RESULTING
|
|
// IN DIFFERENT BEHAVIOR TO THE RELEASE VERSION.
|
|
}
|
|
|
|
m_fIn_SetInputOnOffState = FALSE;
|
|
}
|
|
|
|
BOOL CSpTask::_GetInputOnOffState(void)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
if(m_cpRecoEngine)
|
|
{
|
|
SPRECOSTATE srs;
|
|
|
|
m_cpRecoEngine->GetRecoState(&srs);
|
|
|
|
if (srs == SPRST_ACTIVE)
|
|
{
|
|
fRet = TRUE; // on
|
|
}
|
|
else if (srs == SPRST_INACTIVE)
|
|
{
|
|
fRet = FALSE; // off
|
|
}
|
|
// anything else is 'off'
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
HRESULT CSpTask::_StopInput(void)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceMsg(TF_SAPI_PERF, "_StopInput is called");
|
|
|
|
if (!m_bInSound && m_pime->GetDICTATIONSTAT_DictOnOff())
|
|
{
|
|
TraceMsg(TF_SAPI_PERF, "m_bInSound is FALSE, GetDICTATIONSTAT_DictOnOff returns TRUE");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
m_pime->EraseFeedbackUI();
|
|
|
|
_ShowDictatingToBalloon(FALSE);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// _ClearQueuedRecoEvent(void)
|
|
//
|
|
// synopsis: get rid of remaining events from reco context's
|
|
// event queue. This is only called from _StopInput()
|
|
// when TerminateComposition() is called, or Mic is
|
|
// turned off
|
|
//
|
|
//
|
|
void CSpTask::_ClearQueuedRecoEvent(void)
|
|
{
|
|
if (m_cpRecoCtxt)
|
|
{
|
|
SPEVENTSOURCEINFO esi;
|
|
|
|
if (S_OK == m_cpRecoCtxt->GetInfo(&esi))
|
|
{
|
|
ULONG ulcount = esi.ulCount;
|
|
CSpEvent event;
|
|
while(ulcount > 0)
|
|
{
|
|
if (S_OK == event.GetFrom(m_cpRecoCtxt))
|
|
{
|
|
event.Clear();
|
|
}
|
|
ulcount--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// CSpTask::GetResltObjectFromStream()
|
|
//
|
|
// synopsis - a wrapper function that takes a stream ptr to a SAPI result blob
|
|
// and gets alternates out of the object
|
|
//
|
|
HRESULT CSpTask::GetResultObjectFromStream(IStream *pStream, ISpRecoResult **ppResult)
|
|
{
|
|
LARGE_INTEGER li0 = {0, 0};
|
|
HRESULT hr = E_INVALIDARG;
|
|
SPSERIALIZEDRESULT *pPhraseBlob = 0;
|
|
|
|
if (pStream)
|
|
{
|
|
hr = pStream->Seek(li0, STREAM_SEEK_SET, NULL);
|
|
|
|
STATSTG stg;
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pStream->Stat(&stg, STATFLAG_NONAME);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
pPhraseBlob = (SPSERIALIZEDRESULT *)CoTaskMemAlloc(stg.cbSize.LowPart+sizeof(ULONG)*4);
|
|
|
|
if (pPhraseBlob)
|
|
hr = pStream->Read(pPhraseBlob, stg.cbSize.LowPart, NULL);
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ISpRecoResult *pResult;
|
|
|
|
if (SUCCEEDED(m_cpRecoCtxt->DeserializeResult(pPhraseBlob, &pResult)) && pResult)
|
|
{
|
|
if (ppResult)
|
|
{
|
|
pResult->AddRef();
|
|
*ppResult = pResult;
|
|
}
|
|
pResult->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// GetAlternates
|
|
//
|
|
HRESULT CSpTask::GetAlternates(CRecoResultWrap *pResultWrap, ULONG ulStartElem, ULONG ulcElem, ISpPhraseAlt **ppAlt, ULONG *pcAlt, ISpRecoResult **ppRecoResult)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
if (m_fIn_GetAlternates)
|
|
return E_FAIL;
|
|
|
|
m_fIn_GetAlternates = TRUE;
|
|
|
|
Assert(pResultWrap);
|
|
Assert(ppAlt);
|
|
Assert(pcAlt);
|
|
|
|
hr = pResultWrap->GetResult(ppRecoResult);
|
|
|
|
if (S_OK == hr && *ppRecoResult)
|
|
{
|
|
|
|
hr = (*ppRecoResult)->GetAlternates(
|
|
ulStartElem,
|
|
ulcElem,
|
|
*pcAlt,
|
|
ppAlt, /* [out] ISpPhraseAlt **ppPhrases, */
|
|
pcAlt /* [out] ULONG *pcPhrasesReturned */
|
|
);
|
|
}
|
|
|
|
m_fIn_GetAlternates = FALSE;
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpTask::_SpeakText(WCHAR *pwsz)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (m_cpVoice)
|
|
hr = m_cpVoice->Speak( pwsz, /* SPF_DEFAULT */ SPF_ASYNC /* | SPF_PURGEBEFORESPEAK*/, NULL );
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpTask::_SpeakAudio( ISpStreamFormat *pStream )
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if ( !pStream )
|
|
return E_INVALIDARG;
|
|
|
|
if (m_cpVoice)
|
|
hr = m_cpVoice->SpeakStream(pStream, SPF_ASYNC, NULL);
|
|
|
|
return hr;
|
|
}
|
|
|
|
void CSapiIMX::RegisterWorkerClass(HINSTANCE hInstance)
|
|
{
|
|
WNDCLASSEX wndclass;
|
|
|
|
memset(&wndclass, 0, sizeof(wndclass));
|
|
wndclass.cbSize = sizeof(wndclass);
|
|
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
|
|
wndclass.hInstance = hInstance;
|
|
wndclass.lpfnWndProc = CSapiIMX::_WndProc;
|
|
wndclass.lpszClassName = c_szWorkerWndClass;
|
|
wndclass.cbWndExtra = 8;
|
|
RegisterClassEx(&wndclass);
|
|
}
|
|
|
|
//
|
|
// ParseSRElementByLocale
|
|
//
|
|
// Parse SR result elements in locale specific mannar
|
|
//
|
|
// dependency caution: this function has to be rewritten when SAPI5 changes
|
|
// SR element format, which is very likely
|
|
//
|
|
// 12/15/99 : As of SAPI1214, an element now includes display text,
|
|
// lexical form, pronunciation separately. this function
|
|
// takes the display text at szSrc.
|
|
// langid parameter is not used at this moment
|
|
//
|
|
HRESULT CSpTask::ParseSRElementByLocale(WCHAR *szDst, int cchDst, const WCHAR * szSrc, LANGID langid, BYTE bAttr)
|
|
{
|
|
if (!szDst || !szSrc || !cchDst)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// handle leading space.
|
|
if (bAttr & SPAF_CONSUME_LEADING_SPACES)
|
|
{
|
|
const WCHAR *psz = szSrc;
|
|
while(*psz && *psz == L' ')
|
|
{
|
|
psz++;
|
|
}
|
|
szSrc = psz;
|
|
}
|
|
|
|
wcsncpy(szDst, szSrc, cchDst - 2); // -2 for possible sp
|
|
|
|
// handle trailing space
|
|
if (bAttr & SPAF_ONE_TRAILING_SPACE)
|
|
{
|
|
StringCchCatW(szDst, cchDst, L" ");
|
|
}
|
|
else if (bAttr & SPAF_TWO_TRAILING_SPACES)
|
|
{
|
|
StringCchCatW(szDst,cchDst, L" ");
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// FeedDictContext
|
|
//
|
|
// synopsis: feed the text surrounding the current IP to SR engine
|
|
//
|
|
void CSpTask::FeedDictContext(CDictContext *pdc)
|
|
{
|
|
Assert(pdc);
|
|
if (!m_pime->_GetWorkerWnd())
|
|
{
|
|
delete pdc;
|
|
return ;
|
|
}
|
|
|
|
// wait until the current feeding is done
|
|
// it's not efficient to feed IP everytime user
|
|
// click around
|
|
if (m_pdc)
|
|
{
|
|
delete pdc;
|
|
return;
|
|
}
|
|
|
|
if (!m_cpDictGrammar)
|
|
{
|
|
delete pdc;
|
|
return;
|
|
}
|
|
|
|
// remove unprocessed messages from the queue
|
|
// FutureConsider: this could be moved to wndproc so that
|
|
// we can remove this private msg at the
|
|
// moment we process it. It depends on
|
|
// profiling we'll do.
|
|
//
|
|
MSG msg;
|
|
while(PeekMessage(&msg, m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, WM_PRIV_FEEDCONTEXT, TRUE))
|
|
;
|
|
|
|
// queue up the context
|
|
m_pdc = pdc;
|
|
PostMessage(m_pime->_GetWorkerWnd(), WM_PRIV_FEEDCONTEXT, 0, (LPARAM)TRUE);
|
|
}
|
|
|
|
void CSpTask::CleanupDictContext(void)
|
|
{
|
|
if (m_pdc)
|
|
delete m_pdc;
|
|
|
|
m_pdc = NULL;
|
|
}
|
|
|
|
// _UpdateBalloon( )
|
|
|
|
void CSpTask::_UpdateBalloon(ULONG uidBalloon, ULONG uidBalloonTooltip)
|
|
{
|
|
WCHAR wszBalloonText[MAX_PATH] = {0};
|
|
WCHAR wszBalloonTooltip[MAX_PATH] = {0};
|
|
|
|
#ifndef RECOSLEEP
|
|
if (!m_pime->GetSpeechUIServer())
|
|
#else
|
|
if (!m_pime->GetSpeechUIServer() || IsInSleep( ))
|
|
#endif
|
|
return;
|
|
|
|
CicLoadStringWrapW(g_hInst, uidBalloon, wszBalloonText, ARRAYSIZE(wszBalloonText));
|
|
CicLoadStringWrapW(g_hInst, uidBalloonTooltip, wszBalloonTooltip, ARRAYSIZE(wszBalloonTooltip));
|
|
|
|
if (wszBalloonText[0] && wszBalloonTooltip[0])
|
|
{
|
|
m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO,
|
|
wszBalloonText, -1,
|
|
wszBalloonTooltip, -1 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// ShowDictatingToBalloon
|
|
//
|
|
//
|
|
void CSpTask::_ShowDictatingToBalloon(BOOL fShow)
|
|
{
|
|
#ifndef RECOSLEEP
|
|
if (!m_pime->GetSpeechUIServer())
|
|
#else
|
|
if (!m_pime->GetSpeechUIServer() || IsInSleep( ))
|
|
#endif
|
|
return;
|
|
|
|
static WCHAR s_szDictating[MAX_PATH] = {0};
|
|
static WCHAR s_szDictatingTooltip[MAX_PATH] = {0};
|
|
|
|
if (!s_szDictating[0])
|
|
{
|
|
CicLoadStringWrapW(g_hInst, IDS_DICTATING,
|
|
s_szDictating,
|
|
ARRAYSIZE(s_szDictating));
|
|
}
|
|
if (!s_szDictatingTooltip[0])
|
|
{
|
|
CicLoadStringWrapW(g_hInst, IDS_DICTATING_TOOLTIP,
|
|
s_szDictatingTooltip,
|
|
ARRAYSIZE(s_szDictatingTooltip));
|
|
}
|
|
|
|
if (fShow && s_szDictating[0] && s_szDictatingTooltip[0])
|
|
{
|
|
m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, s_szDictating, -1, s_szDictatingTooltip, -1 );
|
|
}
|
|
else if (!fShow)
|
|
{
|
|
m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, L" ", -1, L" ", -1 );
|
|
}
|
|
}
|
|
//
|
|
// _HandleInterference
|
|
//
|
|
// synopsis: bubble up reco errors to the balloon UI
|
|
//
|
|
//
|
|
void CSpTask::_HandleInterference(ULONG lParam)
|
|
{
|
|
if (!m_pime->GetSpeechUIServer())
|
|
return;
|
|
|
|
WCHAR sz[MAX_PATH];
|
|
WCHAR szTooltip[MAX_PATH];
|
|
|
|
if (S_OK ==
|
|
_GetLocSRErrorString((SPINTERFERENCE)lParam,
|
|
sz, ARRAYSIZE(sz),
|
|
szTooltip, ARRAYSIZE(szTooltip)))
|
|
{
|
|
m_pime->GetSpeechUIServer()->UpdateBalloonAndTooltip(TF_LB_BALLOON_RECO, sz, -1, szTooltip, -1 );
|
|
}
|
|
}
|
|
|
|
HRESULT CSpTask::_GetLocSRErrorString
|
|
(
|
|
SPINTERFERENCE sif,
|
|
WCHAR *psz, ULONG cch,
|
|
WCHAR *pszTooltip, ULONG cchTooltip
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
static struct
|
|
{
|
|
ULONG uidRes;
|
|
WCHAR szErr[MAX_PATH];
|
|
ULONG uidResTooltip;
|
|
WCHAR szTooltip[MAX_PATH];
|
|
} rgIntStr[] =
|
|
{
|
|
{IDS_INT_NONE, {0}, IDS_INTTOOLTIP_NONE, {0}},
|
|
{IDS_INT_NOISE, {0}, IDS_INTTOOLTIP_NOISE, {0}},
|
|
{IDS_INT_NOSIGNAL, {0}, IDS_INTTOOLTIP_NOSIGNAL, {0}},
|
|
{IDS_INT_TOOLOUD, {0}, IDS_INTTOOLTIP_TOOLOUD, {0}},
|
|
{IDS_INT_TOOQUIET, {0}, IDS_INTTOOLTIP_TOOQUIET, {0}},
|
|
{IDS_INT_TOOFAST, {0}, IDS_INTTOOLTIP_TOOFAST, {0}},
|
|
{IDS_INT_TOOSLOW, {0}, IDS_INTTOOLTIP_TOOSLOW, {0}}
|
|
};
|
|
|
|
if ((ULONG)sif < ARRAYSIZE(rgIntStr)-1)
|
|
{
|
|
if (!rgIntStr[sif].szErr[0])
|
|
{
|
|
hr = CicLoadStringWrapW(g_hInst,
|
|
rgIntStr[sif].uidRes,
|
|
rgIntStr[sif].szErr,
|
|
ARRAYSIZE(rgIntStr[0].szErr)) > 0 ? S_OK : E_FAIL;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = CicLoadStringWrapW(g_hInst,
|
|
rgIntStr[sif].uidResTooltip,
|
|
rgIntStr[sif].szTooltip,
|
|
ARRAYSIZE(rgIntStr[0].szTooltip)) > 0 ? S_OK : E_FAIL;
|
|
}
|
|
}
|
|
else
|
|
hr = S_OK; // the value is cached
|
|
}
|
|
if (S_OK == hr)
|
|
{
|
|
Assert(psz);
|
|
wcsncpy(psz, rgIntStr[sif].szErr, cch);
|
|
|
|
Assert(pszTooltip);
|
|
wcsncpy(pszTooltip, rgIntStr[sif].szTooltip, cchTooltip);
|
|
}
|
|
return hr;
|
|
}
|