Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2896 lines
87 KiB

//
// sapilayr.cpp
//
// implementation of CSapiIMX class body
//
#include "private.h"
#include "immxutil.h"
#include "sapilayr.h"
#include "globals.h"
#include "propstor.h"
#include "timsink.h"
#include "kes.h"
#include "nui.h"
#include "dispattr.h"
#include "lbarsink.h"
#include "miscfunc.h"
#include "nuibase.h"
#include "xstring.h"
#include "dictctxt.h"
#include "mui.h"
#include "cregkey.h"
#include "oleacc.h"
// {9597CB34-CF6A-11d3-8D69-00500486C135}
static const GUID GUID_OfficeSpeechMode = {
0x9597cb34,
0xcf6a,
0x11d3,
{ 0x8d, 0x69, 0x0, 0x50, 0x4, 0x86, 0xc1, 0x35}
};
STDAPI CICPriv::QueryInterface(REFIID riid, void **ppvObj)
{
*ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObj = SAFECAST(this, IUnknown *);
}
if (*ppvObj)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDAPI_(ULONG) CICPriv::AddRef()
{
return ++_cRef;
}
STDAPI_(ULONG) CICPriv::Release()
{
long cr;
cr = --_cRef;
Assert(cr >= 0);
if (cr == 0)
{
delete this;
}
return cr;
}
CICPriv *GetInputContextPriv(TfClientId tid, ITfContext *pic)
{
CICPriv *picp;
IUnknown *punk;
GetCompartmentUnknown(pic, GUID_IC_PRIVATE, &punk);
if (!punk)
{
// need to init priv data
if (picp = new CICPriv(pic))
{
SetCompartmentUnknown(tid, pic, GUID_IC_PRIVATE, picp);
}
}
else
{
picp = (CICPriv *)punk;
}
return picp;
}
CICPriv *EnsureInputContextPriv(CSapiIMX *pimx, ITfContext *pic)
{
CICPriv *picp = GetInputContextPriv(pimx->_GetId(), pic);
return picp;
}
//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------
CSapiIMX::CSapiIMX() : CFnConfigure(this), CLearnFromDoc(this), CAddDeleteWord(this), CSelectWord(this),
CTextToSpeech(this), CCorrectionHandler(this)
{
m_pCSpTask = NULL;
m_hwndWorker = NULL;
_fDeactivated = TRUE;
_fEditing = FALSE;
_tid = TF_CLIENTID_NULL;
_cRef = 1;
//
// Init DisplayAttribute Provider
//
// default
COLORREF crBk = GetNewLookColor();
COLORREF crText = GetTextColor();
// add feedback UI colors
TF_DISPLAYATTRIBUTE da;
StringCchCopyW(szProviderName, ARRAYSIZE(szProviderName), L"SAPI Layer");
SetAttributeColor(&da.crText, crText);
SetAttributeColor(&da.crBk, crBk);
da.lsStyle = TF_LS_NONE;
da.fBoldLine = FALSE;
ClearAttributeColor(&da.crLine);
da.bAttr = TF_ATTR_INPUT;
Add(GUID_ATTR_SAPI_GREENBAR, L"SAPI Feedback Bar", &da);
// color for level 2 application (for CUAS)
crBk = GetNewLookColor(DA_COLOR_UNAWARE);
SetAttributeColor(&da.crText, crText);
SetAttributeColor(&da.crBk, crBk);
da.lsStyle = TF_LS_NONE;
da.fBoldLine = FALSE;
ClearAttributeColor(&da.crLine);
da.bAttr = TF_ATTR_INPUT;
Add(GUID_ATTR_SAPI_GREENBAR2, L"SAPI Feedback Bar for Unaware app", &da);
SetAttributeColor(&da.crText, crText);
SetAttributeColor(&da.crBk, RGB(255, 0, 0));
da.lsStyle = TF_LS_NONE;
da.fBoldLine = FALSE;
ClearAttributeColor(&da.crLine);
da.bAttr = TF_ATTR_INPUT;
Add(GUID_ATTR_SAPI_REDBAR, L"SAPI Red bar", &da);
// create another dap for simulate 'inverted text' for selection
SetAttributeColor(&da.crBk, GetSysColor( COLOR_HIGHLIGHT ));
SetAttributeColor(&da.crText, GetSysColor( COLOR_HIGHLIGHTTEXT ));
da.lsStyle = TF_LS_NONE;
da.fBoldLine = FALSE;
ClearAttributeColor(&da.crLine);
da.bAttr = TF_ATTR_TARGET_CONVERTED;
Add(GUID_ATTR_SAPI_SELECTION, L"SPTIP selection ", &da);
m_fSharedReco = TRUE;
m_fShowBalloon = FALSE;
m_pLanguageChangeNotifySink = NULL;
m_pSpeechUIServer = NULL;
m_szCplPath[0] = _T('\0');
m_pCapCmdHandler = NULL;
m_fIPIsUpdated = FALSE;
m_dwNumCharTyped = 0;
m_ulSimulatedKey = 0;
m_pSpButtonControl = NULL;
m_fModeKeyRegistered = FALSE;
m_fStartingComposition = FALSE;
m_fStageTip = FALSE;
m_fStageVisible = FALSE;
m_hwndStage = NULL;
m_ulHypothesisLen = 0;
m_ulHypothesisNum = 0;
m_IsInHypoProcessing = FALSE;
_pCandUIEx = NULL;
m_pMouseSink = NULL;
m_fMouseDown = FALSE;
m_ichMouseSel = 0;
m_uLastEdge = 0;
m_lTimeLastClk = 0;
#ifdef SUPPORT_INTERNAL_WIDGET
m_fNoCorrectionUI = FALSE;
#endif
}
//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------
CSapiIMX::~CSapiIMX()
{
if (m_pSpeechUIServer)
{
m_pSpeechUIServer->SetIMX(NULL);
m_pSpeechUIServer->Release();
m_pSpeechUIServer = NULL;
}
ClearCandUI( );
if (m_hwndWorker)
{
DestroyWindow(m_hwndWorker);
}
if ( m_pCapCmdHandler )
delete m_pCapCmdHandler;
}
//+---------------------------------------------------------------------------
//
// PrivateAPI for profile stuff
//
//
//----------------------------------------------------------------------------
extern "C"
HRESULT WINAPI TF_CreateLangProfileUtil(ITfFnLangProfileUtil **ppFnLangUtil)
{
return CSapiIMX::CreateInstance(NULL, IID_ITfFnLangProfileUtil, (void **)ppFnLangUtil);
}
//+---------------------------------------------------------------------------
//
// OnSetThreadFocus
//
//----------------------------------------------------------------------------
STDAPI CSapiIMX::OnSetThreadFocus()
{
TraceMsg(TF_SAPI_PERF, "OnSetThreadFocus is called");
BOOL fOn = GetOnOff( );
// When Microphone is OFF, don't set any speech status to the local compartment.
// this will cause Office App initialize their SAPI objects if the mode is C&C
// even if Microphone OFF.
// Later when the Microphone is ON, this mode data will be updated correctly
// inside the MICROPHONE_OPENCLOSE handling.
//
if ( fOn )
{
// TABLETPC
// We switch states to match the global state whenever we get focus.
// This may or may not trigger changes depending on the the stage visibility and dictation state.
DWORD dwLocal, dwGlobal;
GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dwLocal, FALSE);
GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_GLOBALSTATE, &dwGlobal, TRUE);
dwGlobal = dwGlobal & (TF_DICTATION_ON | TF_COMMANDING_ON);
if ( (dwLocal & (TF_DICTATION_ON | TF_COMMANDING_ON)) != dwGlobal)
{
dwLocal = (dwLocal & ~(TF_DICTATION_ON | TF_COMMANDING_ON)) + dwGlobal;
SetCompartmentDWORD(_tid, _tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, dwLocal, FALSE);
// Now we are guaranteed the local dictation state matches the global one.
}
}
#ifdef SYSTEM_GLOBAL_MIC_STATUS
//
// The microphone UI status compartment is updated whenever the reco state
// changes. What we need to do here is to reset the dictation status
// and others things that we skip doing when the thread doesn't have
// a focus
//
//
MIC_STATUS ms = MICSTAT_NA;
if (m_pCSpTask)
{
ms = m_pCSpTask->_GetInputOnOffState() ? MICSTAT_ON : MICSTAT_OFF;
}
_HandleOpenCloseEvent(ms);
#else
_HandleOpenCloseEvent();
#endif
#ifdef SUPPORT_INTERNAL_WIDGET
// create widget instance here
if (!m_fNoCorrectionUI && !m_cpCorrectionUI)
{
if (S_OK == m_cpCorrectionUI.CoCreateInstance(CLSID_CorrectionIMX))
{
// the real widget is installed
m_cpCorrectionUI.Release();
m_fNoCorrectionUI = TRUE;
}
else if (SUCCEEDED(CCorrectionIMX::CreateInstance(NULL, IID_ITfTextInputProcessor, (void **)&m_cpCorrectionUI)))
{
m_cpCorrectionUI->Activate(_tim, _tid);
}
}
#endif
return S_OK;
}
//+---------------------------------------------------------------------------
//
// OnKillThreadFocus
//
//----------------------------------------------------------------------------
STDAPI CSapiIMX::OnKillThreadFocus()
{
// When the Application gets focus again, it will rely on the
// current status from compartment, and then decides which RecoContext
// needs to be activated.
//
TraceMsg(TF_SAPI_PERF, "CSapiIMX::OnKillThreadFocus is called");
// TABLETPC
if (m_pCSpTask && S_OK != IsActiveThread())
{
m_pCSpTask->_SetDictRecoCtxtState(FALSE);
m_pCSpTask->_SetCmdRecoCtxtState(FALSE);
}
// close candidate UI forcefully when focus shifts
CloseCandUI( );
return S_OK;
}
BOOL CSapiIMX::InitializeSpeechButtons()
{
BOOL fSREnabled = _DictationEnabled();
SetDICTATIONSTAT_DictEnabled(fSREnabled);
// We need to see if the app has commanding if it is, then it
// needs the mic even when dictation is disabled
//
if (m_pSpeechUIServer)
{
BOOL fShow = (fSREnabled || IsDICTATIONSTAT_CommandingEnable());
m_pSpeechUIServer->ShowUI(fShow);
}
return fSREnabled;
}
//+---------------------------------------------------------------------------
//
// Activate
//
//----------------------------------------------------------------------------
STDAPI CSapiIMX::Activate(ITfThreadMgr *ptim, TfClientId tid)
{
ITfLangBarItemMgr *plbim = NULL;
ITfKeystrokeMgr_P *pksm = NULL;
ITfSourceSingle *sourceSingle;
ITfSource *source;
ITfContext *pic = NULL;
BOOL fSREnabledForLanguage = FALSE;
TfClientId tidLast = _tid;
_tid = tid;
// Load spgrmr.dll module for speech grammar.
LoadSpgrmrModule();
// register notify UI stuff
HRESULT hr = GetService(ptim, IID_ITfLangBarItemMgr, (IUnknown **)&plbim);
if (SUCCEEDED(hr))
{
plbim->GetItem(GUID_TFCAT_TIP_SPEECH, &m_cpMicButton);
SafeRelease(plbim);
}
// regular stuff for activate
Assert(_tim == NULL);
_tim = ptim;
_tim->AddRef();
if (_tim->QueryInterface(IID_ITfSource, (void **)&source) == S_OK)
{
source->AdviseSink(IID_ITfThreadFocusSink, (ITfThreadFocusSink *)this, &_dwThreadFocusCookie);
source->AdviseSink(IID_ITfKeyTraceEventSink, (ITfKeyTraceEventSink *)this, &_dwKeyTraceCookie);
source->Release();
}
// force data options to get set
SetAudioOnOff(TRUE);
// Register compartment sink for TIP status
if (!(m_pCes = new CCompartmentEventSink(_CompEventSinkCallback, this)))
{
hr = E_OUTOFMEMORY;
goto Exit;
}
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SAPI_AUDIO, FALSE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_OPENCLOSE, TRUE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, FALSE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_LEARNDOC, FALSE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_CFGMENU, FALSE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE, TRUE);
#ifdef TF_DISABLE_SPEECH
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_DISABLED, FALSE);
#endif
//TABLETPC
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_STAGE, FALSE);
m_pCes->_Advise(_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, TRUE);
// Get initial stage visibility. Note - keep after above _Advise for stage change event.
DWORD dw = 0;
GetCompartmentDWORD(_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, &dw, TRUE);
m_fStageVisible = dw ? TRUE : FALSE;
// ENDTABLETPC
// profile activation sink
if (!m_pActiveLanguageProfileNotifySink)
{
if (!(m_pActiveLanguageProfileNotifySink =
new CActiveLanguageProfileNotifySink(_ActiveTipNotifySinkCallback, this)))
{
hr = E_OUTOFMEMORY;
goto Exit;
}
m_pActiveLanguageProfileNotifySink->_Advise(_tim);
}
if (!m_pSpeechUIServer &&
FAILED(CSpeechUIServer::CreateInstance(NULL,
IID_PRIV_CSPEECHUISERVER,
(void **)&m_pSpeechUIServer)))
{
hr = E_OUTOFMEMORY;
goto Exit;
}
SetCompartmentDWORD(_tid,_tim,GUID_COMPARTMENT_SPEECH_LEARNDOC,_LMASupportEnabled(),FALSE);
if (m_pSpeechUIServer)
{
m_pSpeechUIServer->SetIMX(this);
m_pSpeechUIServer->Initialize();
m_pSpeechUIServer->ShowUI(TRUE);
}
fSREnabledForLanguage = InitializeSpeechButtons();
SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
// language change notification sink
// this call better be after calling InitializeSpeechButtons because we
// want to skip calling _EnsureProfiles to get ITfLanguageProfileNotifySink
//
if (!m_pLanguageChangeNotifySink)
{
if (!(m_pLanguageChangeNotifySink =
new CLanguageProfileNotifySink(_LangChangeNotifySinkCallback, this)))
{
hr = E_OUTOFMEMORY;
goto Exit;
}
m_pLanguageChangeNotifySink->_Advise(m_cpProfileMgr);
}
// now we inherit what is previously set as a mic status
//
#ifdef SYSTEM_GLOBAL_MIC_STATUS
if (m_pCSpTask)
{
SetOnOff(m_pCSpTask->_GetInputOnOffState());
}
#else
// see if microphone is 'ON' and if so, check if we're indeed running
// we check tidLast because it is normal we get activated again with
// same client id, and it means we've kept our life across sessions
// we don't want to reject global mic status in this case, otherwise
// we'll see bugs like cicero#3386
//
if (GetOnOff() && tidLast != tid)
{
// this code has to stay before the first call to _EnsureWorkerWnd()
HWND hwnd = FindWindow(c_szWorkerWndClass, NULL);
if (!IsWindow(hwnd))
{
// no one is running us but we somehow persisted the state
// let's just kill the 'on' state here
// SetOnOff(FALSE);
}
}
#endif
// show / hide balloon following global compartment
m_fShowBalloon = GetBalloonStatus();
// thread event sink init
if ((m_timEventSink = new CThreadMgrEventSink(_DIMCallback, _ICCallback, this)) == NULL)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
else
{
m_timEventSink->_Advise(_tim);
m_timEventSink->_InitDIMs(TRUE);
}
if (SUCCEEDED(GetService(_tim, IID_ITfKeystrokeMgr_P, (IUnknown **)&pksm)))
{
if (_pkes = new CSptipKeyEventSink(_KeyEventCallback, _PreKeyEventCallback, this))
{
pksm->AdviseKeyEventSink(_tid, _pkes, FALSE);
_pkes->_Register(_tim, _tid, g_prekeyList);
// register mode button hotkeys if they are enabled.
HandleModeKeySettingChange(TRUE);
}
pksm->Release();
}
// func provider registeration
IUnknown *punk;
if (SUCCEEDED(QueryInterface(IID_IUnknown, (void **)&punk)))
{
if (SUCCEEDED(_tim->QueryInterface(IID_ITfSourceSingle, (void **)&sourceSingle)))
{
sourceSingle->AdviseSingleSink(_tid, IID_ITfFunctionProvider, punk);
sourceSingle->Release();
}
punk->Release();
}
Assert(_fDeactivated);
_fDeactivated = FALSE;
// TABLETPC
if (S_OK == IsActiveThread())
{
// init any UI
OnSetThreadFocus();
}
hr = S_OK;
Exit:
return hr;
}
//+---------------------------------------------------------------------------
//
// Deactivate
//
//----------------------------------------------------------------------------
STDAPI CSapiIMX::Deactivate()
{
ITfKeystrokeMgr *pksm = NULL;
ITfSourceSingle *sourceSingle;
ITfSource *source;
// this ensures no context feed activity is taken place
SetDICTATIONSTAT_DictOnOff(FALSE);
// finalize any pending compositions, this may be async
CleanupAllContexts(_tim, _tid, this);
// Free the system reconvertion function if it is set.
_ReleaseSystemReconvFunc( );
// delete SpButtonControl object
if ( m_pSpButtonControl )
{
delete m_pSpButtonControl;
m_pSpButtonControl = NULL;
}
// TABLETPC
if (S_OK != IsActiveThread())
{
// shutdown any UI
OnKillThreadFocus();
}
if (_tim->QueryInterface(IID_ITfSource, (void **)&source) == S_OK)
{
source->UnadviseSink(_dwThreadFocusCookie);
source->UnadviseSink(_dwKeyTraceCookie);
source->Release();
}
// unregister notify UI stuff
if (m_pSpeechUIServer && !IsDICTATIONSTAT_CommandingEnable())
{
m_pSpeechUIServer->SetIMX(NULL);
m_pSpeechUIServer->Release();
m_pSpeechUIServer = NULL;
}
#ifdef SUPPORT_INTERNAL_WIDGET
// deactivate the widget correction
if (m_cpCorrectionUI)
{
m_cpCorrectionUI->Deactivate();
m_cpCorrectionUI.Release();
}
#endif
// regular stuff for activate
ClearCandUI( );
if (SUCCEEDED(_tim->QueryInterface(IID_ITfSourceSingle, (void **)&sourceSingle)))
{
sourceSingle->UnadviseSingleSink(_tid, IID_ITfFunctionProvider);
sourceSingle->Release();
}
// thread event sink deinit
if (m_timEventSink)
{
m_timEventSink->_InitDIMs(FALSE);
m_timEventSink->_Unadvise();
SafeReleaseClear(m_timEventSink);
}
if (_pkes != NULL)
{
_pkes->_Unregister(_tim, _tid, g_prekeyList);
if ( m_fModeKeyRegistered )
{
_pkes->_Unregister(_tim, _tid, (const KESPRESERVEDKEY *)g_prekeyList_Mode);
m_fModeKeyRegistered = FALSE;
}
SafeReleaseClear(_pkes);
}
if (SUCCEEDED(GetService(_tim, IID_ITfKeystrokeMgr, (IUnknown **)&pksm)))
{
pksm->UnadviseKeyEventSink(_tid);
pksm->Release();
}
if (m_pCes)
{
m_pCes->_Unadvise();
SafeReleaseClear(m_pCes);
}
if (m_pLBarItemSink)
{
m_pLBarItemSink->_Unadvise();
SafeReleaseClear(m_pLBarItemSink);
}
if (m_pMouseSink)
{
m_pMouseSink->_Unadvise();
SafeReleaseClear(m_pMouseSink);
}
// clean up active notify sink
if (m_pActiveLanguageProfileNotifySink)
{
m_pActiveLanguageProfileNotifySink->_Unadvise();
SafeReleaseClear(m_pActiveLanguageProfileNotifySink);
}
if (m_pLanguageChangeNotifySink)
{
m_pLanguageChangeNotifySink->_Unadvise();
SafeReleaseClear(m_pLanguageChangeNotifySink);
}
if (m_hwndWorker)
{
DestroyWindow(m_hwndWorker);
m_hwndWorker = NULL;
}
DeinitializeSAPI();
SafeReleaseClear(_tim);
TFUninitLib_Thread(&_libTLS);
Assert(!_fDeactivated);
_fDeactivated = TRUE;
return S_OK;
}
HRESULT CSapiIMX::InitializeSAPI(BOOL fLangOverride)
{
HRESULT hr = S_OK;
TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is called");
if (m_pCSpTask)
{
if (!m_pCSpTask->_IsCallbackInitialized())
{
hr = m_pCSpTask->InitializeCallback();
}
TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is initialized, hr=%x\n", hr);
return hr;
}
HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
// create CSpTask instance
m_pCSpTask = new CSpTask(this);
if (m_pCSpTask)
{
// LANGID m_langid;
// check to see if profile lang matches with SR lang
m_fDictationEnabled = _DictationEnabled(&m_langid);
hr = m_pCSpTask->InitializeSAPIObjects(m_langid);
if (S_OK == hr && !_fDeactivated &&
(m_fDictationEnabled || IsDICTATIONSTAT_CommandingEnable()))
{
// set callback
hr = m_pCSpTask->InitializeCallback();
}
if (S_OK == hr)
{
hr = m_pCSpTask->_LoadGrammars();
}
if (S_OK == hr)
{
// toolbar command
m_pCSpTask->_InitToolbarCommand(fLangOverride);
}
}
if (hCur)
SetCursor(hCur);
TraceMsg(TF_SAPI_PERF, "CSapiIMX::InitializeSAPI is done!!!!! hr=%x\n", hr);
return hr;
}
HRESULT CSapiIMX::DeinitializeSAPI()
{
TraceMsg(TF_SAPI_PERF, "DeinitializeSAPI is called");
if (m_pCSpTask)
{
// toolbar command
m_pCSpTask->_UnInitToolbarCommand();
// set dication status
SetDICTATIONSTAT_DictOnOff(FALSE);
// - deinitialize SAPI
m_pCSpTask->_ReleaseSAPI();
delete m_pCSpTask;
m_pCSpTask = NULL;
}
return S_OK;
}
HRESULT CSapiIMX::_ActiveTipNotifySinkCallback(REFCLSID clsid, REFGUID guidProfile, BOOL fActivated, void *pv)
{
if (IsEqualGUID(clsid, CLSID_SapiLayr))
{
CSapiIMX *pimx = (CSapiIMX *)pv;
if (fActivated)
{
BOOL fSREnabledForLanguage = pimx->InitializeSpeechButtons();
pimx->SetDICTATIONSTAT_DictEnabled(fSREnabledForLanguage);
}
else
{
// finalize any pending compositions, this may be async
CleanupAllContexts(pimx->_tim, pimx->_tid, pimx);
// when deactivating, we have to deinitialize SAPI so that
// we can re-initialize SAPI after getting a new assembly
pimx->DeinitializeSAPI();
}
}
return S_OK;
}
HRESULT CSapiIMX::_LangChangeNotifySinkCallback(BOOL fChanged, LANGID langid, BOOL *pfAccept, void *pv)
{
CSapiIMX *pimx = (CSapiIMX *)pv;
if (fChanged)
{
pimx->m_fDictationEnabled = pimx->InitializeSpeechButtons();
pimx->SetDICTATIONSTAT_DictEnabled(pimx->m_fDictationEnabled);
if (!pimx->m_fDictationEnabled)
{
// finalize any pending compositions, this may be async
CleanupAllContexts(pimx->_tim, pimx->_tid, pimx);
if (!pimx->IsDICTATIONSTAT_CommandingEnable())
pimx->DeinitializeSAPI();
}
/* With the Global Mode state supporting, we don't want this message for languag switch handling.
else
{
if (pimx->_GetWorkerWnd())
{
TraceMsg(TF_SAPI_PERF, "Send WM_PRIV_ONSETTHREADFOCUS message");
PostMessage(pimx->_GetWorkerWnd(), WM_PRIV_ONSETTHREADFOCUS, 0, 0);
}
}
*/
}
return S_OK;
}
//
//
// ITfCreatePropertyStore implementation
//
//
STDMETHODIMP
CSapiIMX::CreatePropertyStore(
REFGUID guidProp,
ITfRange *pRange,
ULONG cb,
IStream *pStream,
ITfPropertyStore **ppStore
)
{
HRESULT hr = E_FAIL;
//
//
//
if (IsEqualGUID(guidProp, GUID_PROP_SAPIRESULTOBJECT))
{
CPropStoreRecoResultObject *pPropStore;
CComPtr<ISpRecoContext> cpRecoCtxt;
// ensure SAPI is initialized
InitializeSAPI(TRUE);
hr = m_pCSpTask->GetSAPIInterface(IID_ISpRecoContext, (void **)&cpRecoCtxt);
pPropStore = new CPropStoreRecoResultObject(this, pRange);
if (pPropStore)
{
hr = pPropStore->_InitFromIStream(pStream, cb, cpRecoCtxt);
if (SUCCEEDED(hr))
hr = pPropStore->QueryInterface(IID_ITfPropertyStore, (void **)ppStore);
pPropStore->Release();
}
}
return hr;
}
STDAPI CSapiIMX::IsStoreSerializable(REFGUID guidProp, ITfRange *pRange, ITfPropertyStore *pPropStore, BOOL *pfSerializable)
{
*pfSerializable = FALSE;
if (IsEqualGUID(guidProp, GUID_PROP_SAPIRESULTOBJECT))
{
*pfSerializable = TRUE;
}
return S_OK;
}
STDMETHODIMP CSapiIMX::GetType(GUID *pguid)
{
HRESULT hr = E_INVALIDARG;
if (pguid)
{
*pguid = CLSID_SapiLayr;
hr = S_OK;
}
return hr;
}
STDMETHODIMP CSapiIMX::GetDescription(BSTR *pbstrDesc)
{
const WCHAR c_wszNameSapiLayer[] = L"Cicero Sapi function Layer";
HRESULT hr = S_OK;
BSTR pbstr;
if (!(pbstr = SysAllocString(c_wszNameSapiLayer)))
{
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CSapiIMX::GetFunction(REFGUID rguid, REFIID riid, IUnknown **ppunk)
{
if (!ppunk)
return E_INVALIDARG;
*ppunk = NULL;
HRESULT hr = E_NOTIMPL;
if (!IsEqualGUID(rguid, GUID_NULL))
return hr;
if (IsEqualIID(riid, IID_ITfFnGetSAPIObject))
{
*ppunk = new CGetSAPIObject(this);
}
else
{
if (IsEqualGUID(riid, IID_ITfFnPlayBack))
{
*ppunk = new CSapiPlayBack(this);
}
else if (IsEqualGUID(riid, IID_ITfFnReconversion))
{
*ppunk = new CFnReconversion(this);
}
else if (IsEqualIID(riid, IID_ITfFnAbort))
{
*ppunk = new CFnAbort(this);
}
else if (IsEqualIID(riid, IID_ITfFnBalloon))
{
*ppunk = new CFnBalloon(this);
}
else if (IsEqualIID(riid, IID_ITfFnPropertyUIStatus))
{
*ppunk = new CFnPropertyUIStatus(this);
}
else
{
// This class decides if it's necessary to initialize
// SAPI to retrieve the requested interface
//
CComPtr<CGetSAPIObject> cpGetSapi;
cpGetSapi.Attach(new CGetSAPIObject(this));
//
//
//
if (cpGetSapi)
{
TfSapiObject tfSapiObj;
// this returns S_FALSE if the iid does not match
hr = cpGetSapi->IsSupported(riid, &tfSapiObj);
if (S_OK == hr)
{
// *ppunk is initialized w/ NULL in GetSAPIInterface()
// ppunk should get addref'd
hr = cpGetSapi->Get(tfSapiObj, ppunk);
}
else
hr = E_NOTIMPL;
if (hr == E_NOTIMPL)
{
// should we care?
// this indicates that the caller has requested an interface
// that we are not dealing with.
// The caller could just detect this failure and do their own stuff.
TraceMsg(TF_GENERAL, "Caller requested SAPI interface Cicero doesn't handle");
}
}
}
}
if (*ppunk)
{
hr = S_OK;
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _TextEventSinkCallback
//
//----------------------------------------------------------------------------
HRESULT CSapiIMX::_TextEventSinkCallback(UINT uCode, void *pv, void *pvData)
{
TESENDEDIT *pee = (TESENDEDIT *)pvData;
HRESULT hr = E_FAIL;
Assert(uCode == ICF_TEXTDELTA); // the only one we asked for
CSapiIMX *pimx = (CSapiIMX *)pv;
if (pimx->_fEditing)
return S_OK;
pimx->HandleTextEvent(pee->pic, pee);
hr = S_OK;
return hr;
}
void CSapiIMX::HandleTextEvent(ITfContext *pic, TESENDEDIT *pee)
{
ITfRange *pRange = NULL;
Assert(pic);
Assert(pee);
if (!m_pCSpTask)
{
return;
}
// Get the selection/IP if it's updated
BOOL fUpdated = FALSE;
if (S_OK == _GetSelectionAndStatus(pic, pee, &pRange, &fUpdated))
{
// Handle context feed
if (fUpdated)
{
_FeedIPContextToSR(pee->ecReadOnly, pic, pRange);
BOOL fEmpty = FALSE;
BOOL fSelection;
// Get current selection status.
if ( pRange != NULL )
pRange->IsEmpty(pee->ecReadOnly, &fEmpty);
fSelection = !fEmpty;
if ( fSelection != m_pCSpTask->_GetSelectionStatus( ) )
{
m_pCSpTask->_SetSelectionStatus(fSelection);
m_pCSpTask->_SetSpellingGrammarStatus(GetDICTATIONSTAT_DictOnOff());
}
}
// Handle mode bias property data
SyncWithCurrentModeBias(pee->ecReadOnly, pRange, pic);
SafeRelease(pRange);
}
// m_fIPIsUpdated just keeps if there is IP change by other tips or keyboard typing
// since last dictation. It doesn't care about the ip change caused by speech tip itself,
// those ip changes would include feedback ui inject and final text inject.
//
// Every time when a dictation or spelling phrase is recognized, this value should be
// reset to FALSE.
//
// Here m_fIPIsUpdated should not just keep the last value returned by _GetSelectionAndStatus,
// There is a scenario like, user move the ip to other place and then speak a command ( with some
// some hypothesis feedback before the command is recognized).
// In this case we should treat it as ip changed since last dictation, but the last value returned
// from _GetSelectionAndStatus could be FALSE, because _GetSelectionAndStatus treats Sptip-injected
// feedback tex as non-ipchanged.
//
// So only when m_fIPIsUpdated is FALSE, we get new value from _GetSelectionAndStatus,
// otherwise, keep it till the next dictation.
if ( m_fIPIsUpdated == FALSE )
m_fIPIsUpdated = fUpdated;
}
void CSapiIMX::_FeedIPContextToSR(TfEditCookie ecReadOnly, ITfContext *pic, ITfRange *pRange)
{
if (GetOnOff() == TRUE && _ContextFeedEnabled())
{
CDictContext *pdc = new CDictContext(pic, pRange);
if (pdc)
{
if (GetDICTATIONSTAT_DictOnOff() == TRUE &&
S_OK == pdc->InitializeContext(ecReadOnly))
{
Assert(m_pCSpTask);
m_pCSpTask->FeedDictContext(pdc);
}
else
delete pdc;
}
}
}
void CSapiIMX::_SetCurrentIPtoSR(void)
{
_RequestEditSession(ESCB_FEEDCURRENTIP, TF_ES_READ);
}
HRESULT CSapiIMX::_InternalFeedCurrentIPtoSR(TfEditCookie ecReadOnly, ITfContext *pic)
{
CComPtr<ITfRange> cpRange;
HRESULT hr = GetSelectionSimple(ecReadOnly, pic, &cpRange);
if (S_OK == hr)
{
_FeedIPContextToSR(ecReadOnly, pic, cpRange);
}
return hr;
}
BOOL CSapiIMX::HandleKey(WCHAR ch)
{
m_ulSimulatedKey = 1;
keybd_event((BYTE)ch, 0, 0, 0);
keybd_event((BYTE)ch, 0, KEYEVENTF_KEYUP, 0);
return TRUE;
}
const TCHAR c_szcplsKey[] = TEXT("software\\microsoft\\windows\\currentversion\\control panel\\cpls");
void CSapiIMX::GetSapiCplPath(TCHAR *szCplPath, int cchSizePath)
{
if (!m_szCplPath[0])
{
CMyRegKey regkey;
if (S_OK == regkey.Open(HKEY_LOCAL_MACHINE, c_szcplsKey, KEY_READ))
{
LONG lret = regkey.QueryValueCch(m_szCplPath, TEXT("SapiCpl"), ARRAYSIZE(m_szCplPath));
if (lret != ERROR_SUCCESS)
lret = regkey.QueryValueCch(m_szCplPath, TEXT("Speech"), ARRAYSIZE(m_szCplPath));
if (lret != ERROR_SUCCESS)
m_szCplPath[0] = _T('\0'); // maybe we get lucky next time
}
}
StringCchCopy(szCplPath, cchSizePath, m_szCplPath);
}
HRESULT CSapiIMX::_GetSelectionAndStatus(ITfContext *pic, TESENDEDIT *pee, ITfRange **ppRange, BOOL *pfUpdated)
{
BOOL fWriteSession;
HRESULT hr = pic->InWriteSession(_tid, &fWriteSession);
Assert(pfUpdated);
*pfUpdated = FALSE;
if (S_OK == hr)
{
// we don't want to pick up changes done by ourselves
if (!fWriteSession)
{
hr = pee->pEditRecord->GetSelectionStatus(pfUpdated);
}
else
{
// returns S_FALSE when in write session
hr = S_FALSE;
}
}
if (S_OK == hr )
{
Assert(ppRange);
hr = GetSelectionSimple(pee->ecReadOnly, pic, ppRange);
}
return hr;
}
void CSapiIMX::SyncWithCurrentModeBias(TfEditCookie ec, ITfRange *pRange, ITfContext *pic)
{
ITfReadOnlyProperty *pProp = NULL;
VARIANT var;
QuickVariantInit(&var);
if (pic->GetAppProperty(GUID_PROP_MODEBIAS, &pProp) != S_OK)
{
pProp = NULL;
goto Exit;
}
pProp->GetValue(ec, pRange, &var);
if (_gaModebias != (TfGuidAtom)var.lVal)
{
GUID guid;
_gaModebias = (TfGuidAtom)var.lVal;
GetGUIDFromGUIDATOM(&_libTLS, _gaModebias, &guid);
BOOL fActive;
if (!IsEqualGUID(guid, GUID_MODEBIAS_NONE))
{
fActive = TRUE;
}
else
{
fActive = FALSE;
}
// mode bias has to be remembered
if (m_pCSpTask)
m_pCSpTask->_SetModeBias(fActive, guid);
}
Exit:
VariantClear(&var);
SafeRelease(pProp);
}
HRESULT CSapiIMX::_HandleTrainingWiz()
{
WCHAR sz[64];
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_UI_TRAINING, sz, ARRAYSIZE(sz));
CComPtr<ISpRecognizer> cpRecoEngine;
HRESULT hr = m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpRecoEngine);
if (S_OK == hr && cpRecoEngine)
{
DWORD dwDictStatBackup = GetDictationStatBackup();
DWORD dwBefore;
if (S_OK != GetCompartmentDWORD(_tim,
GUID_COMPARTMENT_SPEECH_DISABLED,
&dwBefore,
FALSE) )
{
dwBefore = 0;
}
SetCompartmentDWORD(0, _tim, GUID_COMPARTMENT_SPEECH_DISABLED, TF_DISABLE_DICTATION, FALSE);
cpRecoEngine->DisplayUI(_GetAppMainWnd(), sz, SPDUI_UserTraining, NULL, 0);
SetCompartmentDWORD(0, _tim, GUID_COMPARTMENT_SPEECH_DISABLED, dwBefore, FALSE);
SetDictationStatAll(dwDictStatBackup);
}
return hr;
}
HRESULT CSapiIMX::_RequestEditSession(UINT idEditSession, DWORD dwFlag, ESDATA *pesData, ITfContext *picCaller, LONG_PTR *pRetData, IUnknown **ppRetUnk)
{
CSapiEditSession *pes;
CComPtr<ITfContext> cpic;
HRESULT hr = E_FAIL;
// callers can intentionally give us a NULL pic
if (picCaller == NULL)
{
GetFocusIC(&cpic);
}
else
{
cpic = picCaller;
}
if (cpic)
{
if (pes = new CSapiEditSession(this, cpic))
{
if ( pesData )
{
pes->_SetRange(pesData->pRange);
pes->_SetUnk(pesData->pUnk);
pes->_SetEditSessionData(idEditSession, pesData->pData, pesData->uByte, pesData->lData1, pesData->lData2, pesData->fBool);
}
else
pes->_SetEditSessionData(idEditSession, NULL, 0);
cpic->RequestEditSession(_tid, pes, dwFlag, &hr);
// if caller wants to get the return value from the edit session, it has to set SYNC edit session.
if ( pRetData )
*pRetData = pes->_GetRetData( );
if ( ppRetUnk )
*ppRetUnk = pes->_GetRetUnknown( );
pes->Release();
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _DIMCallback
//
//----------------------------------------------------------------------------
HRESULT CSapiIMX::_DIMCallback(UINT uCode, ITfDocumentMgr *dim, ITfDocumentMgr * pdimPrevFocus, void *pv)
{
CSapiIMX *_this = (CSapiIMX *)pv;
switch (uCode)
{
case TIM_CODE_INITDIM:
//
// Add this dim to the LearnFromDoc internal dim list with fFeed as FALSE.
TraceMsg(TF_GENERAL, "TIM_CODE_INITDIM callback is called, DIM is %x", (INT_PTR)dim);
_this->_AddDimToList(dim, FALSE);
break;
// clean up any reference to ranges
case TIM_CODE_UNINITDIM:
TraceMsg(TF_GENERAL, "TIM_CODE_UNINITDIM callback is called, DIM is %x", (INT_PTR)dim);
_this->_RemoveDimFromList(dim);
if (_this->m_pCSpTask)
_this->m_pCSpTask->CleanupDictContext();
//DIM is to destroyed, we want to stop speaking if TTS is playing
if ( _this->_IsInPlay( ) )
{
_this->_HandleEventOnPlayButton( );
}
break;
case TIM_CODE_SETFOCUS:
TraceMsg(TF_GENERAL, "TIM_CODE_SETFOCUS callback is called, DIM is %x", (INT_PTR)dim);
if ( !_this->m_fStageTip )
{
// The stage tip is a special instance of a RichEdit control that wants to stay active (i.e. enabled)
// regardless of focus elsewhere in the hosting application. This way dictaion always goes to the stage.
// This relies on a written-in-stone contract that only one Cicero enabled text input control exists in
// said application.
_this->SetDICTATIONSTAT_DictEnabled(dim ? _this->m_fDictationEnabled : FALSE);
}
// When TIM_CODE_SETFOCUS is called means there is a document focus change.
// No matter what DIM is getting focus now, we just need to close the existing
// candidate list menu.
// NOTE - for the TabletTip stage instance, do we want to close the candidate UI. This means when the user clicks in
// the area surrounding the stage RichEdit or on the titlebar, the correction menu (and widget) get dismissed.
// This means when TabletTip is dragged around with either of these visible, the correction widget or menu gets dismissed
// since the first step of the drag is a click in the titlebar. NOTE - if we disable this code here it still gets dismissed
// so there is another handler somewhere triggering that.
_this->CloseCandUI( );
if ( dim )
{
// A DIM is getting focus, when LearnFromDoc is set, we need to feed
// the existing document to the Dictation grammar.
// And we need to check if we already feed the document for this dim to the SREngine.
// if we did, we don't feed it again for the same document.
if ( _this->GetLearnFromDoc( ) == TRUE )
{
HRESULT hr = S_OK;
hr = _this->HandleLearnFromDoc( dim );
}
else
TraceMsg(TF_GENERAL, "Learn From DOC is set to FALSE");
// We want to check if this new DIM is AIMM aware or pure Cicero aware,
// so that we can determine if we want to disble TTS buttons.
ITfContext *pic;
dim->GetTop(&pic);
if ( pic )
{
_this->_SetTTSButtonStatus( pic );
// for the top ic, we hook the modebias change
// notification so that we can set up the corresponding
// grammar for the bias
//
if (_this->GetDICTATIONSTAT_DictOnOff())
_this->_SyncModeBiasWithSelection(pic);
SafeRelease(pic);
}
}
break;
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _ICCallback
//
//----------------------------------------------------------------------------
/* static */
HRESULT CSapiIMX::_ICCallback(UINT uCode, ITfContext *pic, void *pv)
{
HRESULT hr = E_FAIL;
CSapiIMX *_this = (CSapiIMX *)pv;
CICPriv *priv;
ITfContext *picTest;
switch (uCode)
{
case TIM_CODE_INITIC:
if ((priv = EnsureInputContextPriv(_this, pic)) != NULL)
{
_this->_InitICPriv(priv, pic);
priv->Release();
// We want to check if this IC is under a foucs DIM, and if it is AIMM aware or pure Cicero aware,
// so that we can determine if we want to disble TTS buttons.
ITfDocumentMgr *pFocusDIM = NULL, *pThisDIM = NULL;
_this->GetFocusDIM(&pFocusDIM);
pic->GetDocumentMgr(&pThisDIM);
if ( pFocusDIM == pThisDIM )
{
_this->_SetTTSButtonStatus( pic );
}
SafeRelease(pFocusDIM);
SafeRelease(pThisDIM);
hr = S_OK;
}
break;
case TIM_CODE_UNINITIC:
// start setfocus code
if ((priv = GetInputContextPriv(_this->_tid, pic)) != NULL)
{
_this->_DeleteICPriv(priv, pic);
priv->Release();
hr = S_OK;
}
if (_this->m_cpRangeCurIP != NULL) // should m_cpRangeCurIP be per-ic and stored in icpriv?
{
// free up m_cpRangeCurIP if it belongs to this context
if (_this->m_cpRangeCurIP->GetContext(&picTest) == S_OK)
{
if (pic == picTest)
{
_this->m_cpRangeCurIP.Release();
}
picTest->Release();
}
}
// IC is getting popped. We need to reset cicero awareness
// status based on the bottom IC. This assumes IC stack to
// be 2 at maximum.
//
if (pic)
{
CComPtr<ITfContext> cpicTop;
CComPtr<ITfDocumentMgr> cpdim;
hr = pic->GetDocumentMgr(&cpdim);
if (S_OK == hr)
{
cpdim->GetBase(&cpicTop);
}
if ( cpicTop )
{
_this->_SetTTSButtonStatus( cpicTop );
}
}
break;
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _DeleteICPriv
//
//----------------------------------------------------------------------------
void CSapiIMX::_DeleteICPriv(CICPriv *priv, ITfContext *pic)
{
if (!priv)
return;
if (priv->m_pTextEvent)
{
priv->m_pTextEvent->_Unadvise();
SafeReleaseClear(priv->m_pTextEvent);
}
// we MUST clear out the private data before cicero is free to release the ic
ClearCompartment(_tid, pic, GUID_IC_PRIVATE, FALSE);
// this is it, we won't need the private data any longer
}
//+---------------------------------------------------------------------------
//
// _InitICPriv
//
//----------------------------------------------------------------------------
void CSapiIMX::_InitICPriv(CICPriv *priv, ITfContext *pic)
{
if (!priv->m_pTextEvent)
{
if ((priv->m_pTextEvent = new CTextEventSink(CSapiIMX::_TextEventSinkCallback, this)) != NULL)
{
priv->m_pTextEvent->_Advise(pic, ICF_TEXTDELTA);
}
}
}
//+---------------------------------------------------------------------------
//
// _KillFocusRange
//
// get rid of the focusrange within the given range
//
//---------------------------------------------------------------------------+
HRESULT CSapiIMX::_KillFocusRange(TfEditCookie ec, ITfContext *pic, ITfRange *pRange, TfClientId tid)
{
HRESULT hr = E_FAIL;
IEnumITfCompositionView *pEnumComp = NULL;
ITfContextComposition *picc = NULL;
ITfCompositionView *pCompositionView;
ITfComposition *pComposition;
CLSID clsid;
CICPriv *picp;
//
// clear any sptip compositions over the range
//
if (pic->QueryInterface(IID_ITfContextComposition, (void **)&picc) != S_OK)
goto Exit;
if (picc->FindComposition(ec, pRange, &pEnumComp) != S_OK)
goto Exit;
// tid will be TF_CLIENTID_NULL when we're deactivated, in which case we
// don't want to mess with the composition count
picp = (tid == TF_CLIENTID_NULL) ? NULL : GetInputContextPriv(tid, pic);
while (pEnumComp->Next(1, &pCompositionView, NULL) == S_OK)
{
if (pCompositionView->GetOwnerClsid(&clsid) != S_OK)
goto NextComp;
// make sure we ignore other TIPs' compositions!
if (!IsEqualCLSID(clsid, CLSID_SapiLayr))
goto NextComp;
if (pCompositionView->QueryInterface(IID_ITfComposition, (void **)&pComposition) != S_OK)
goto NextComp;
// found a composition, terminate it
pComposition->EndComposition(ec);
pComposition->Release();
if (picp != NULL)
{
picp->_ReleaseComposition();
}
NextComp:
pCompositionView->Release();
}
SafeRelease(picp);
hr = S_OK;
Exit:
SafeRelease(picc);
SafeRelease(pEnumComp);
return hr;
}
//+---------------------------------------------------------------------------
//
// _SetFocusToStageIfStage
//
// Many voice commands (particularly selection and correction) do not make
// sense unless the focus is in the stage. This adjusts focus so the commands
// work as the user will expect.
//
//---------------------------------------------------------------------------+
HRESULT CSapiIMX::_SetFocusToStageIfStage(void)
{
HRESULT hr = S_OK;
if (m_fStageTip)
{
ASSERT(m_hwndStage && L"Have null HWND for stage.");
if (m_hwndStage)
{
CComPtr<IAccessible> cpAccessible;
hr = AccessibleObjectFromWindow(m_hwndStage, OBJID_WINDOW, IID_IAccessible, (void **)&cpAccessible);
if (SUCCEEDED(hr) && cpAccessible)
{
CComVariant cpVar = CHILDID_SELF;
hr = cpAccessible->accSelect(SELFLAG_TAKEFOCUS, cpVar);
}
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _SetFilteringString
//
// When non-matching event is notified, this function is called to inject
// previous filtering string to the parent document.
//
//---------------------------------------------------------------------------+
HRESULT CSapiIMX::_SetFilteringString(TfEditCookie ec, ITfCandidateUI *pCandUI, ITfContext *pic)
{
HRESULT hr = E_FAIL;
ITfRange *pRange;
BSTR bstr;
CDocStatus ds(pic);
if (ds.IsReadOnly())
return S_OK;
if (SUCCEEDED(GetSelectionSimple(ec, pic, &pRange )))
{
CComPtr<ITfProperty> cpProp;
LANGID langid = 0x0409;
// get langid from the given range
if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_LANGID, &cpProp)))
{
GetLangIdPropertyData(ec, cpProp, pRange, &langid);
}
CComPtr<ITfCandUIFnAutoFilter> cpFnFilter;
hr = _pCandUIEx->GetFunction(IID_ITfCandUIFnAutoFilter, (IUnknown **)&cpFnFilter);
if (S_OK == hr && SUCCEEDED(cpFnFilter->GetFilteringString( CANDUIFST_DETERMINED, &bstr )))
{
hr = SetTextAndProperty(&_libTLS, ec, pic, pRange, bstr, SysStringLen(bstr), langid, NULL);
SysFreeString( bstr );
}
pRange->Collapse( ec, TF_ANCHOR_END );
SetSelectionSimple(ec, pic, pRange);
// we don't want to inject undetermined string to the document anymore.
// Cicero will inject the non-matching keyboard char
// to the document right after the determined filter string.
pRange->Release();
}
return hr;
}
//----------------------------------------------------------------------------
//
// _CompEventSinkCallback (static)
//
//----------------------------------------------------------------------------
HRESULT CSapiIMX::_CompEventSinkCallback(void *pv, REFGUID rguid)
{
CSapiIMX *_this = (CSapiIMX *)pv;
BOOL fOn;
if (IsEqualGUID(rguid, GUID_COMPARTMENT_SAPI_AUDIO))
{
fOn = _this->GetAudioOnOff();
if (_this->m_pCSpTask)
_this->m_pCSpTask->_SetAudioRetainStatus(fOn);
return S_OK;
}
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_OPENCLOSE))
{
HRESULT hr = S_OK;
#ifdef SAPI_PERF_DEBUG
DWORD dw;
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_OPENCLOSE, &dw, TRUE);
TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_OPENCLOSE event : %i. \n", dw);
#endif
// TABLETPC
if ( S_OK != _this->IsActiveThread() )
{
TraceMsg(TF_GENERAL, "SPEECH_OPENCLOSE, App doesn't get Focus!");
return hr;
}
TraceMsg(TF_GENERAL, "SPEECH_OPENCLOSE, App GETs Focus!");
DWORD dwLocal, dwGlobal;
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dwLocal, FALSE);
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_GLOBALSTATE, &dwGlobal, TRUE);
dwGlobal = dwGlobal & (TF_DICTATION_ON + TF_COMMANDING_ON);
if ( (dwLocal & (TF_DICTATION_ON + TF_COMMANDING_ON)) != dwGlobal)
{
dwLocal = (dwLocal & ~(TF_DICTATION_ON + TF_COMMANDING_ON)) + dwGlobal;
SetCompartmentDWORD(_this->_tid, _this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, dwLocal, FALSE);
}
// first time...
if (!_this->m_pCSpTask)
{
//
// put "Starting Speech..." in the balloon
//
if (_this->GetOnOff() && _this->GetBalloonStatus())
{
_this->SetBalloonStatus(TRUE, TRUE); // force update
// make sure balloon is shown
_this->GetSpeechUIServer()->ShowUI(TRUE);
// Ask the speech ui server to set the SAPI initializing
// flag to the balloon so it'll do it at the first callback
//
hr = _this->GetSpeechUIServer()->SetBalloonSAPIInitFlag(TRUE);
WCHAR sz[128];
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_NUI_STARTINGSPEECH, sz, ARRAYSIZE(sz));
_this->GetSpeechUIServer()->UpdateBalloon(TF_LB_BALLOON_RECO, sz, -1);
TraceMsg(TF_SAPI_PERF, "Show Starting speech ...");
}
// TABLETPC
// BUGBUG - Do we need this now I have fixed the _HandleOpenCloseEvent to work in whichever sptip actually has focus?
if (_this->m_fStageTip)
{
// Since the stage may not have focus, the delayed mechanism above will not result
// in dictation activating in the stage since the delayed activation will happen in the
// cicero app with focus - except that the stage is visible hence the app with focus
// will simply ignore it. Not what we want when the stage is activating.
// Ignore above hresult in case of failure - this is the more important call.
hr = _this->_HandleOpenCloseEvent();
}
}
else
{
hr = _this->_HandleOpenCloseEvent();
}
// Office App uses its own global compartment GUID_OfficeSpeechMode to keep the current mode,
// so that next time the application starts, it checks this value to initalize SAPI objects
// even if Microphone is OFF.
// Since we have already used our own global compartment GUID_COMPARTMENT_SPEECH_GLOBALSTATE to
// keep the speech mode system wide, there is no need for Office to use that global compartment
// for its own usage that way.
//
// So when Microphone is OFF, we just reset the global compartment GUID_OfficeSpeechMode.
if ( !_this->GetOnOff( ) )
{
SetCompartmentDWORD(_this->_tid, _this->_tim, GUID_OfficeSpeechMode, 0, TRUE);
}
// when we have a temporary composistion such as
// CUAS level2 or AIMM level3, we don't want to
// finalize on going composition each time mic turns off
// because it also shutdown chance for correction
//
if (S_OK == hr && _this->IsFocusFullAware(_this->_tim))
{
hr = _this->_FinalizeComposition();
}
return hr;
}
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_STAGE))
{
DWORD dw = 0;
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_STAGE, &dw, FALSE);
Assert(dw && L"NULL HWND passed in GUID_COMPARTMENT_SPEECH_STAGE");
if (dw != 0)
{
_this->m_hwndStage = (HWND) dw;
_this->m_fStageTip = TRUE;
}
}
// TABLETPC
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_STAGECHANGE))
{
HRESULT hr = S_OK;
DWORD dw;
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_STAGECHANGE, &dw, TRUE);
_this->m_fStageVisible = dw ? TRUE:FALSE;
if (S_OK == _this->IsActiveThread())
{
_this->OnSetThreadFocus();
}
else
{
_this->OnKillThreadFocus();
}
}
// TABLETPC
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT))
{
_this->m_fDictationEnabled = _this->GetDICTATIONSTAT_DictEnabled();
#ifdef SAPI_PERF_DEBUG
DWORD dw;
GetCompartmentDWORD(_this->_tim, GUID_COMPARTMENT_SPEECH_DICTATIONSTAT, &dw, FALSE);
TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT is set in SAPIIMX, dw=%x", dw);
#endif
HRESULT hr;
// TABLETPC
hr = _this->IsActiveThread();
if ( hr == S_OK )
{
BOOL fDictOn, fCmdOn;
BOOL fDisable;
fOn = _this->GetOnOff();
fDisable = _this->Get_SPEECH_DISABLED_Disabled();
fDictOn = fOn && _this->GetDICTATIONSTAT_DictOnOff() && _this->GetDICTATIONSTAT_DictEnabled( ) && !fDisable && !_this->Get_SPEECH_DISABLED_DictationDisabled();
fCmdOn = fOn && _this->GetDICTATIONSTAT_CommandingOnOff( ) && !fDisable && !_this->Get_SPEECH_DISABLED_CommandingDisabled();
if ( _this->m_pCSpTask )
{
hr = _this->m_pCSpTask->_SetDictRecoCtxtState(fDictOn);
if ( hr == S_OK )
hr = _this->m_pCSpTask->_SetCmdRecoCtxtState(fCmdOn);
if ((fDictOn || fCmdOn ) && _this->m_pSpeechUIServer)
{
WCHAR sz[128];
sz[0] = '\0';
if (fDictOn)
{
CicLoadStringWrapW(g_hInst, IDS_NUI_DICTATION_TEXT, sz, ARRAYSIZE(sz));
hr = _this->m_pSpeechUIServer->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
TraceMsg(TF_SAPI_PERF, "Show \"Dictation\"");
}
else if ( fCmdOn )
{
CicLoadStringWrapW(g_hInst, IDS_NUI_COMMANDING_TEXT, sz, ARRAYSIZE(sz));
hr = _this->m_pSpeechUIServer->UpdateBalloon(TF_LB_BALLOON_RECO, sz , -1);
TraceMsg(TF_SAPI_PERF, "Show \"Voice command\"");
}
}
if (fDictOn)
{
hr = _this->HandleLearnFromDoc( );
if ( S_OK == hr )
_this->_SetCurrentIPtoSR();
}
}
if (S_OK == hr)
{
hr = _this->EraseFeedbackUI();
if (S_OK == hr)
hr = _this->_FinalizeComposition();
}
TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT exit normally");
}
else
TraceMsg(TF_SAPI_PERF, "GUID_COMPARTMENT_SPEECH_DICTATIONSTAT exits when the app doesn't get focus!");
return hr;
}
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_LEARNDOC))
{
_this->UpdateLearnDocState( );
return S_OK;
}
else if (IsEqualGUID(rguid,GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE) )
{
TraceMsg(TF_GENERAL, "GUID_COMPARTMENT_SPEECH_PROPERTY_CHANGE is set!");
// Renew all the property values from the registry.
_this->_RenewAllPropDataFromReg( );
// Specially handle some of property changes.
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Support_LMA) )
SetCompartmentDWORD(_this->_GetId( ), _this->_tim, GUID_COMPARTMENT_SPEECH_LEARNDOC, _this->_LMASupportEnabled( ), FALSE);
// Specially handle mode button setting changes
BOOL fModeButtonChanged;
fModeButtonChanged = _this->_IsPropItemChangedSinceLastRenew(PropId_Mode_Button) ||
_this->_IsPropItemChangedSinceLastRenew(PropId_Dictation_Key) ||
_this->_IsPropItemChangedSinceLastRenew(PropId_Command_Key);
_this->HandleModeKeySettingChange( fModeButtonChanged );
// For command category items, it will update grammars's status.
// Update the grammar's status.
CSpTask *psp;
_this->GetSpeechTask(&psp);
if ( psp )
{
DWORD dwActiveMode = ACTIVE_IN_BOTH_MODES; // indicates which mode will change the grammar status.
BOOL bDictCmdChanged = _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_DictMode);
if ( _this->_AllDictCmdsDisabled( ) )
{
// All the commands are disabled in dication mode.
psp->_ActivateCmdInDictMode(FALSE);
//Needs to activate spelling grammar in dictation mode.
psp->_ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
// Needs to activate "Force Num" grammar in dication strong mode.
psp->_ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
// Needs to activate language bar grammar in dictation strong mode for mode switching commands.
psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
// Only need to change grammar status in command mode.
dwActiveMode = ACTIVE_IN_COMMAND_MODE;
}
else
{
// if this was changed since latst renew.
if ( bDictCmdChanged )
{
psp->_ActiveCategoryCmds(DC_CC_SelectCorrect, _this->_SelectCorrectCmdEnabled( ), ACTIVE_IN_DICTATION_MODE);
psp-> _ActiveCategoryCmds(DC_CC_Navigation, _this->_NavigationCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_Casing, _this->_CasingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_Editing, _this->_EditingCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_Keyboard, _this->_KeyboardCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_TTS, _this->_TTSCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), ACTIVE_IN_DICTATION_MODE );
psp->_ActiveCategoryCmds(DC_CC_Num_Mode, TRUE, ACTIVE_IN_DICTATION_MODE);
psp->_ActiveCategoryCmds(DC_CC_Spelling, TRUE, ACTIVE_IN_DICTATION_MODE);
if ( _this->_SelectCorrectCmdEnabled( ) || _this->_NavigationCmdEnabled( ) )
{
psp->_UpdateSelectGramTextBufWhenStatusChanged( );
}
dwActiveMode = ACTIVE_IN_COMMAND_MODE;
}
}
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Select_Correct) )
psp->_ActiveCategoryCmds(DC_CC_SelectCorrect, _this->_SelectCorrectCmdEnabled( ), dwActiveMode);
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Navigation) )
psp-> _ActiveCategoryCmds(DC_CC_Navigation, _this->_NavigationCmdEnabled( ), dwActiveMode );
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Casing) )
psp->_ActiveCategoryCmds(DC_CC_Casing, _this->_CasingCmdEnabled( ), dwActiveMode );
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Editing) )
psp->_ActiveCategoryCmds(DC_CC_Editing, _this->_EditingCmdEnabled( ), dwActiveMode );
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Keyboard) )
psp->_ActiveCategoryCmds(DC_CC_Keyboard, _this->_KeyboardCmdEnabled( ), dwActiveMode );
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_TTS) )
psp->_ActiveCategoryCmds(DC_CC_TTS, _this->_TTSCmdEnabled( ), dwActiveMode );
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Language_Bar) )
psp->_ActiveCategoryCmds(DC_CC_LangBar, _this->_LanguageBarCmdEnabled( ), dwActiveMode );
// Check to see if we need to fill text to selection grammar.
if ( _this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Select_Correct) ||
_this->_IsPropItemChangedSinceLastRenew(PropId_Cmd_Navigation) )
{
BOOL bUpdateText;
bUpdateText = _this->_SelectCorrectCmdEnabled( ) || _this->_NavigationCmdEnabled( );
if ( bUpdateText )
{
psp->_UpdateSelectGramTextBufWhenStatusChanged( );
}
}
psp->Release( );
}
return S_OK;
}
#ifdef TF_DISABLE_SPEECH
else if (IsEqualGUID(rguid, GUID_COMPARTMENT_SPEECH_DISABLED))
{
BOOL fDictationDisabled = _this->Get_SPEECH_DISABLED_DictationDisabled() ? TRUE : FALSE;
BOOL fCommandingDisabled = _this->Get_SPEECH_DISABLED_CommandingDisabled() ? TRUE : FALSE;
if (fDictationDisabled)
_this->SetDICTATIONSTAT_DictOnOff(FALSE);
if (fCommandingDisabled)
_this->SetDICTATIONSTAT_CommandingOnOff(FALSE);
return S_OK;
}
#endif
return S_FALSE;
}
HRESULT CSapiIMX::_HandleOpenCloseEvent(MIC_STATUS ms)
{
HRESULT hr = S_OK;
BOOL fOn;
TraceMsg(TF_SAPI_PERF, "_HandleOpenCloseEvent is called, ms=%d", (int)ms);
if (ms == MICSTAT_NA)
{
fOn = GetOnOff();
}
else
{
fOn = (ms == MICSTAT_ON) ? TRUE : FALSE;
}
if (fOn)
{
// if no one so far set dictation status, we assume
// there's no C&C button so we can synchronize dictation
// status with mic
//
InitializeSAPI(TRUE);
if (m_fDictationEnabled == TRUE)
{
//
// if the caller wants to set the mic status (!= NA)
// we also want to make sure dictation status follow that
//
_SetCurrentIPtoSR();
// whenever dictation is turned on, we need to sync
// with the current modebias
//
CComPtr<ITfContext> cpic;
if (GetFocusIC(&cpic))
{
_gaModebias = 0;
_SyncModeBiasWithSelection(cpic);
}
}
}
if (m_pCSpTask)
{
m_pCSpTask->_SetInputOnOffState(fOn);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// _SysLBarCallback
//
//----------------------------------------------------------------------------
HRESULT CSapiIMX::_SysLBarCallback(UINT uCode, void *pv, ITfMenu *pMenu, UINT wID)
{
CSapiIMX *pew = (CSapiIMX *)pv;
HRESULT hr = S_OK;
if (uCode == IDSLB_INITMENU)
{
WCHAR sz[128];
BOOL fOn = pew->GetOnOff();
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_MIC_OPTIONS, sz, ARRAYSIZE(sz));
LangBarInsertMenu(pMenu, IDM_MIC_OPTIONS, sz);
#ifdef TEST_SHARED_ENGINE
LangBarInsertMenu(pMenu, IDM_MIC_SHAREDENGINE, L"Use shared engine", pew->m_fSharedReco);
LangBarInsertMenu(pMenu, IDM_MIC_INPROCENGINE, L"Use inproc engine", !pew->m_fSharedReco);
#endif
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_MIC_TRAINING, sz, ARRAYSIZE(sz));
LangBarInsertMenu(pMenu, IDM_MIC_TRAINING, sz);
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_MIC_ADDDELETE, sz, ARRAYSIZE(sz));
LangBarInsertMenu(pMenu, IDM_MIC_ADDDELETE, sz);
// insert sub menu for user profile stuff
ITfMenu *pSubMenu = NULL;
sz[0] = '\0';
CicLoadStringWrapW(g_hInst, IDS_MIC_CURRENTUSER, sz, ARRAYSIZE(sz));
hr = LangBarInsertSubMenu(pMenu, sz, &pSubMenu);
if (S_OK == hr)
{
CComPtr<IEnumSpObjectTokens> cpEnum;
CComPtr<ISpRecognizer> cpEngine;
ISpObjectToken *pUserProfile = NULL;
CSpDynamicString dstrDefaultUser;
// ensure SAPI is initialized
hr = pew->InitializeSAPI(TRUE);
if (S_OK == hr)
{
// get the current default user
hr = pew->m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpEngine);
}
if (S_OK == hr)
{
hr = cpEngine->GetRecoProfile(&pUserProfile);
}
if (S_OK == hr)
{
hr = SpGetDescription(pUserProfile, &dstrDefaultUser);
SafeRelease(pUserProfile);
}
if (S_OK == hr)
{
hr = SpEnumTokens (SPCAT_RECOPROFILES, NULL, NULL, &cpEnum);
}
if (S_OK == hr)
{
int idUser = 0;
while (cpEnum->Next(1, &pUserProfile, NULL) == S_OK)
{
// dstr frees itself
CSpDynamicString dstrUser;
hr = SpGetDescription(pUserProfile, &dstrUser);
if (S_OK == hr)
{
BOOL fDefaultUser = (wcscmp(dstrUser, dstrDefaultUser) == 0);
Assert(idUser < IDM_MIC_USEREND);
LangBarInsertMenu(pSubMenu, IDM_MIC_USERSTART + idUser++, dstrUser, fDefaultUser);
}
SafeRelease(pUserProfile);
}
}
pSubMenu->Release();
}
}
else if (uCode == IDSLB_ONMENUSELECT)
{
if ( wID == IDM_MIC_ONOFF )
{
// toggle mic
pew->SetOnOff(!pew->GetOnOff());
}
// Invoke SAPI UI stuff...
else if (wID == IDM_MIC_TRAINING)
{
hr = pew->_HandleTrainingWiz();
}
else if (wID == IDM_MIC_ADDDELETE)
{
// A editsession callback will handle this requirement first
// if the edit session fails, we will just display the UI
// without any initial words.
hr = pew->_RequestEditSession(ESCB_HANDLE_ADDDELETE_WORD, TF_ES_READ);
if ( FAILED(hr) )
hr = pew->DisplayAddDeleteUI( NULL, 0 );
}
else if (wID == IDM_MIC_OPTIONS)
{
PostMessage(pew->_GetWorkerWnd(), WM_PRIV_OPTIONS, 0, 0);
}
else if (wID >= IDM_MIC_USERSTART && wID < IDM_MIC_USEREND)
{
CComPtr<IEnumSpObjectTokens> cpEnum;
CComPtr<ISpObjectToken> cpProfile;
// change the current user
//
// this is still a hack, until we get an OnEndMenu event for LangBarItemSink
// for now I just assume SAPI enumerates profiles in same order always
//
// what we should really do is to set up an arry to associate IDs with
// user profiles and clean them up when OnEndMenu comes to us
//
if (S_OK == hr)
{
hr = SpEnumTokens (SPCAT_RECOPROFILES, NULL, NULL, &cpEnum);
}
if (S_OK == hr)
{
ULONG ulidUser = wID - IDM_MIC_USERSTART;
ULONG ulFetched;
CPtrArray<ISpObjectToken> rgpProfile;
rgpProfile.Append(ulidUser+1);
// trasform 0 base index to num of profile
hr = cpEnum->Next(ulidUser+1, rgpProfile.GetPtr(0), &ulFetched);
if (S_OK == hr && ulFetched == ulidUser+1)
{
// get the profile which is selected
cpProfile = rgpProfile.Get(ulidUser);
// clean up
for(ULONG i = 0; i <= ulidUser ; i++)
{
rgpProfile.Get(i)->Release();
}
}
}
if (S_OK == hr && cpProfile)
{
hr = SpSetDefaultTokenForCategoryId(SPCAT_RECOPROFILES, cpProfile);
if ( S_OK == hr )
{
CComPtr<ISpRecognizer> cpEngine;
hr = pew->m_pCSpTask->GetSAPIInterface(IID_ISpRecognizer, (void **)&cpEngine);
if (S_OK == hr)
{
SPRECOSTATE State;
if (S_OK == cpEngine->GetRecoState(&State))
{
cpEngine->SetRecoState(SPRST_INACTIVE);
hr = cpEngine->SetRecoProfile(cpProfile);
cpEngine->SetRecoState(State);
}
}
}
}
}
#ifdef TEST_SHARED_ENGINE
else if (wID == IDM_MIC_SHAREDENGINE || wID == IDM_MIC_INPROCENGINE)
{
pew->m_fSharedReco = wID == IDM_MIC_SHAREDENGINE ? TRUE : FALSE;
pew->_ReinitializeSAPI();
}
#endif
}
return hr;
}
void CSapiIMX::_ReinitializeSAPI(void)
{
TraceMsg(TF_SAPI_PERF, "_ReinitializeSAPI is called");
DeinitializeSAPI();
InitializeSAPI(TRUE);
}
//+---------------------------------------------------------------------------
//
// OnCompositionTerminated
//
// Cicero calls this method when one of our compositions is terminated.
//----------------------------------------------------------------------------
STDAPI CSapiIMX::OnCompositionTerminated(TfEditCookie ec, ITfComposition *pComposition)
{
ITfRange *pRange = NULL;
ITfContext *pic = NULL;
ITfContext *picTest;
CICPriv *picp;
HRESULT hr;
TraceMsg(TF_GENERAL, "OnCompositionTerminated is Called");
hr = E_FAIL;
if (pComposition->GetRange(&pRange) != S_OK)
goto Exit;
if (pRange->GetContext(&pic) != S_OK)
goto Exit;
if (_fDeactivated)
{
// CleanupConsider: benwest: I don't think this can happen anymore...
hr = MakeResultString(ec, pic, pRange, TF_CLIENTID_NULL, NULL);
}
else
{
// Close candidate ui if it is up.
CloseCandUI( );
// take note we're done composing
if (picp = GetInputContextPriv(_tid, pic))
{
picp->_ReleaseComposition();
picp->Release();
}
if (!m_fStartingComposition)
{
hr = MakeResultString(ec, pic, pRange, _tid, m_pCSpTask);
}
else
{
// just avoid terminating recognition when we are just about
// to start composition.
hr = S_OK;
}
}
// free up m_cpRangeCurIP if it belongs to this context
if (m_cpRangeCurIP != NULL &&
m_cpRangeCurIP->GetContext(&picTest) == S_OK)
{
if (pic == picTest)
{
m_cpRangeCurIP.Release();
}
picTest->Release();
}
// unadvise mouse sink
if (m_pMouseSink)
{
m_pMouseSink->_Unadvise();
SafeReleaseClear(m_pMouseSink);
}
Exit:
SafeRelease(pRange);
SafeRelease(pic);
return hr;
}
//+---------------------------------------------------------------------------
//
// _FindComposition
//
//----------------------------------------------------------------------------
/* static */
BOOL CSapiIMX::_FindComposition(TfEditCookie ec, ITfContextComposition *picc, ITfRange *pRange, ITfCompositionView **ppCompositionView)
{
ITfCompositionView *pCompositionView;
IEnumITfCompositionView *pEnum;
ITfRange *pRangeView = NULL;
BOOL fFoundComposition;
LONG l;
CLSID clsid;
HRESULT hr;
if (picc->FindComposition(ec, pRange, &pEnum) != S_OK)
{
Assert(0);
return FALSE;
}
fFoundComposition = FALSE;
while (!fFoundComposition && pEnum->Next(1, &pCompositionView, NULL) == S_OK)
{
hr = pCompositionView->GetOwnerClsid(&clsid);
Assert(hr == S_OK);
// make sure we ignore other TIPs' compositions!
if (!IsEqualCLSID(clsid, CLSID_SapiLayr))
goto NextRange;
hr = pCompositionView->GetRange(&pRangeView);
Assert(hr == S_OK);
if (pRange->CompareStart(ec, pRangeView, TF_ANCHOR_START, &l) == S_OK &&
l >= 0 &&
pRange->CompareEnd(ec, pRangeView, TF_ANCHOR_END, &l) == S_OK &&
l <= 0)
{
// our test range is within this composition range
fFoundComposition = TRUE;
}
NextRange:
SafeRelease(pRangeView);
if (fFoundComposition && ppCompositionView != NULL)
{
*ppCompositionView = pCompositionView;
}
else
{
pCompositionView->Release();
}
}
pEnum->Release();
return fFoundComposition;
}
//+---------------------------------------------------------------------------
//
// _CheckStartComposition
//
//----------------------------------------------------------------------------
void CSapiIMX::_CheckStartComposition(TfEditCookie ec, ITfRange *pRange)
{
ITfContext *pic;
ITfContextComposition *picc;
ITfComposition *pComposition;
CICPriv *picp;
HRESULT hr;
if (pRange->GetContext(&pic) != S_OK)
return;
hr = pic->QueryInterface(IID_ITfContextComposition, (void **)&picc);
Assert(hr == S_OK);
// is pRange already included in a composition range?
if (_FindComposition(ec, picc, pRange, NULL))
goto Exit; // there's already a composition, we're golden
// need to create a new composition, or at least try
m_fStartingComposition = TRUE;
if (picc->StartComposition(ec, pRange, this, &pComposition) == S_OK)
{
if (pComposition != NULL) // NULL if the app rejects the composition
{
// take note we're composing
if (picp = GetInputContextPriv(_tid, pic))
{
picp->_AddRefComposition();
picp->Release();
}
// create mouse sink here for unfinalized composition buffer
if (!IsFocusFullAware(_tim) && !m_pMouseSink)
{
m_pMouseSink = new CMouseSink(_MouseSinkCallback, this);
if (m_pMouseSink)
{
CComPtr<ITfRange> cpRange;
hr = pComposition->GetRange(&cpRange);
if (S_OK == hr)
{
hr = m_pMouseSink->_Advise(cpRange, pic);
}
// mouse not pressed, no selection first
m_fMouseDown = FALSE;
m_ichMouseSel = 0;
}
}
// we already set up the sink, so we'll use ITfContextComposition::FindComposition
// to get this guy back when we want to terminate it
// Cicero will hold a ref to the object until someone terminates it
pComposition->Release();
}
}
m_fStartingComposition = FALSE;
Exit:
pic->Release();
picc->Release();
}
//+---------------------------------------------------------------------------
//
// IsInterestedInContext
//
//----------------------------------------------------------------------------
HRESULT CSapiIMX::IsInterestedInContext(ITfContext *pic, BOOL *pfInterested)
{
CICPriv *picp;
*pfInterested = FALSE;
if (picp = GetInputContextPriv(_tid, pic))
{
// we only need access to ic's with active compositions
*pfInterested = (picp->_GetCompositionCount() > 0);
picp->Release();
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CleanupContext
//
// This method is a callback for the library helper CleanupAllContexts.
// We have to be very careful here because we may be called _after_ this tip
// has been deactivated, if the app couldn't grant a lock right away.
//----------------------------------------------------------------------------
HRESULT CSapiIMX::CleanupContext(TfEditCookie ecWrite, ITfContext *pic)
{
// all sptip cares about is finalizing compositions
CleanupAllCompositions(ecWrite, pic, CLSID_SapiLayr, _CleanupCompositionsCallback, this);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// _CleanupCompositionsCallback
//
//----------------------------------------------------------------------------
/* static */
void CSapiIMX::_CleanupCompositionsCallback(TfEditCookie ecWrite, ITfRange *rangeComposition, void *pvPrivate)
{
ITfContext *pic;
CICPriv *picp;
CSapiIMX *_this = (CSapiIMX *)pvPrivate;
if (rangeComposition->GetContext(&pic) != S_OK)
return;
if (_this->_fDeactivated)
{
// this is a cleanup callback. _tid, m_pCSpTask should already have been cleaned up
_this->MakeResultString(ecWrite, pic, rangeComposition, TF_CLIENTID_NULL, NULL);
}
else
{
// during a profile switch we will still be active and need to clear the composition count on this ic
if (picp = GetInputContextPriv(_this->_tid, pic))
{
// clear the composition count for this ic
picp->_ReleaseComposition();
picp->Release();
}
_this->MakeResultString(ecWrite, pic, rangeComposition, _this->_tid, _this->m_pCSpTask);
}
pic->Release();
}
//+---------------------------------------------------------------------------
//
// _IsDoubleClick
//
// returns TRUE only if the last position is same and lbutton down happens
// within the time defined for double click
//
//----------------------------------------------------------------------------
BOOL CSapiIMX::_IsDoubleClick(ULONG uEdge, ULONG uQuadrant, DWORD dwBtnStatus)
{
if (dwBtnStatus & MK_LBUTTON)
{
LONG lTime=GetMessageTime();
if (!m_fMouseDown && m_uLastEdge == uEdge && m_uLastQuadrant == uQuadrant)
{
if (lTime > m_lTimeLastClk && lTime < m_lTimeLastClk + 500) // use 500 ms for now
{
return TRUE;
}
}
m_uLastEdge = uEdge;
m_uLastQuadrant = uQuadrant;
m_lTimeLastClk = lTime;
}
return FALSE;
}
//+---------------------------------------------------------------------------
//
// _MouseSinkCallback
//
// synopsis: set the current IP on the composition range based on
// uEdge parameter. We don't probably care too much about
// eQuadrant for speech composition
//
//----------------------------------------------------------------------------
/* static */
HRESULT CSapiIMX::_MouseSinkCallback(ULONG uEdge, ULONG uQuadrant, DWORD dwBtnStatus, BOOL *pfEaten, void *pv)
{
CSapiIMX *_this = (CSapiIMX *)pv;
Assert(pv);
BOOL fDoubleClick = _this->_IsDoubleClick(uEdge, uQuadrant, dwBtnStatus);
ESDATA esData = {0};
esData.lData1 = (LONG_PTR)uEdge;
esData.lData2 = (LONG_PTR)dwBtnStatus;
esData.fBool = fDoubleClick;
if (pfEaten)
*pfEaten = TRUE;
return _this->_RequestEditSession(ESCB_HANDLE_MOUSESINK, TF_ES_READWRITE, &esData);
}
HRESULT CSapiIMX::_HandleMouseSink(TfEditCookie ec, ULONG uEdge, ULONG uBtnStatus, BOOL fDblClick)
{
CComPtr<ITfDocumentMgr> cpDim;
CComPtr<ITfContext> cpic;
CComPtr<ITfContextComposition> cpicc;
CComPtr<IEnumITfCompositionView> cpEnumComp;
CComPtr<ITfCompositionView> cpCompositionView;
CComPtr<ITfRange> cpRangeComp;
CComPtr<ITfRange> cpRangeSel;
// if the btn up comes, the next time we'll destroy the selection
// nothing we need to do in this turn
BOOL fLeftBtn = (uBtnStatus & MK_LBUTTON) > 0 ? TRUE : FALSE;
if (!fLeftBtn)
{
m_ichMouseSel = 0;
m_fMouseDown = FALSE;
return S_OK;
}
// Close candidate ui if it is up.
CloseCandUI( );
HRESULT hr = GetFocusDIM(&cpDim);
if(S_OK == hr)
{
hr= cpDim->GetBase(&cpic);
}
if (S_OK == hr)
{
hr = cpic->QueryInterface(IID_ITfContextComposition, (void **)&cpicc);
}
if (S_OK == hr)
{
hr = cpicc->EnumCompositions(&cpEnumComp);
}
if (S_OK == hr)
{
while ((hr = cpEnumComp->Next(1, &cpCompositionView, NULL)) == S_OK)
{
hr = cpCompositionView->GetRange(&cpRangeComp);
if (S_OK == hr)
break;
// prepare for the next turn
cpCompositionView.Release();
}
}
if (S_OK == hr)
{
if (fDblClick)
{
WCHAR wsz[256] = {0}; // the buffer is 256 chars max
ULONG cch = 0;
CComPtr<ITfRange> cpRangeWord;
// obtain the text within the entire composition
hr = cpRangeComp->Clone(&cpRangeWord);
if (S_OK == hr)
{
hr = cpRangeWord->GetText(ec, 0, wsz, 255, &cch);
}
// get the left side edge char position, looking at delimiters
if (S_OK == hr)
{
WCHAR *psz = &wsz[uEdge];
while (psz > wsz)
{
if (!iswalpha(*psz))
{
psz++;
break;
}
psz--;
}
// re-posisition ich
m_ichMouseSel = psz - wsz;
// get the right side word boundary, also based on delimiters
psz = &wsz[uEdge];
while( psz < &wsz[cch] )
{
if (!iswalpha(*psz))
{
break;
}
psz++;
}
// reposition uEdge
uEdge = psz - wsz;
}
// pretend lbutton was previously down to get the same effect of
// dragging selection
//
m_fMouseDown = TRUE;
}
}
if (S_OK == hr)
{
hr = cpRangeComp->Clone(&cpRangeSel);
}
if (S_OK == hr)
{
if(m_fMouseDown)
{
// if the mouse is down the last time and still down this time
// it means it was dragged like this _v_>>>>_v_ or _v_<<<<_v_
// we'll have to make a selection accordingly
// 1) place the IP to the previous position
long cch;
hr = cpRangeSel->ShiftStart(ec, m_ichMouseSel, &cch, NULL);
if (S_OK == hr)
{
// 2) prepare for extension
hr = cpRangeSel->Collapse( ec, TF_ANCHOR_START);
}
}
}
if (S_OK == hr)
{
long ich = (long) (uEdge);
long cch;
// 3) see if there's a prev selection and if there is,
// calculate the dir and width of selection
// note that we've already had ich set to the pos at above 1) & 2)
long iich = 0;
if (m_fMouseDown)
{
iich = ich - m_ichMouseSel;
}
if (iich > 0) // sel towards the end
{
hr = cpRangeSel->ShiftEnd(ec, iich, &cch, NULL);
}
else if (iich < 0) // sel towards the start
{
hr = cpRangeSel->ShiftStart(ec, iich, &cch, NULL);
}
else // no width sel == an IP
{
hr = cpRangeSel->ShiftStart(ec, ich, &cch, NULL);
if (S_OK == hr) // collapse it only when there's no selection
{
hr = cpRangeSel->Collapse( ec, TF_ANCHOR_START);
}
}
// preserve the ip position so we can make a selection later
// a tricky thing is you have to remember the pos where you
// have started to "drag" not the pos you just updated
// so we need this only for the first time we started selection
if (!m_fMouseDown)
m_ichMouseSel = ich;
}
if (S_OK == hr)
{
BOOL fSetSelection = TRUE;
CComPtr<ITfRange> cpRangeCur;
HRESULT tmpHr = S_OK;
tmpHr = GetSelectionSimple(ec, cpic, &cpRangeCur);
if (SUCCEEDED(tmpHr) && cpRangeCur)
{
LONG l = 0;
if (cpRangeCur->CompareStart(ec, cpRangeSel, TF_ANCHOR_START, &l) == S_OK && l == 0 &&
cpRangeCur->CompareEnd(ec, cpRangeSel, TF_ANCHOR_END, &l) == S_OK && l == 0)
{
fSetSelection = FALSE;
}
}
if (fSetSelection)
{
CComPtr<ITfProperty> cpProp;
hr = cpic->GetProperty(GUID_PROP_ATTRIBUTE, &cpProp);
if (S_OK == hr)
{
SetGUIDPropertyData(&_libTLS, ec, cpProp, cpRangeCur, GUID_NULL);
SetGUIDPropertyData(&_libTLS, ec, cpProp, cpRangeSel, GUID_ATTR_SAPI_SELECTION);
}
hr = SetSelectionSimple(ec, cpic, cpRangeSel);
}
}
m_fMouseDown = fLeftBtn;
return hr;
}