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
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;
|
|
}
|