// // compose.cpp // #include "private.h" #include "compose.h" #include "ic.h" #include "range.h" #include "globals.h" #include "immxutil.h" #include "sunka.h" ////////////////////////////////////////////////////////////////////////////// // // CEnumCompositionView // ////////////////////////////////////////////////////////////////////////////// class CEnumCompositionView : public IEnumITfCompositionView, public CEnumUnknown, public CComObjectRootImmx { public: CEnumCompositionView() { Dbg_MemSetThisNameID(TEXT("CEnumCompositionView")); } BOOL _Init(CComposition *pFirst, CComposition *pHalt); BEGIN_COM_MAP_IMMX(CEnumCompositionView) COM_INTERFACE_ENTRY(IEnumITfCompositionView) END_COM_MAP_IMMX() IMMX_OBJECT_IUNKNOWN_FOR_ATL() DECLARE_SUNKA_ENUM(IEnumITfCompositionView, CEnumCompositionView, ITfCompositionView) private: DBG_ID_DECLARE; }; DBG_ID_INSTANCE(CEnumCompositionView); //+--------------------------------------------------------------------------- // // _Init // //---------------------------------------------------------------------------- BOOL CEnumCompositionView::_Init(CComposition *pFirst, CComposition *pHalt) { CComposition *pComposition; ULONG i; ULONG cViews; Assert(pFirst != NULL || pHalt == NULL); cViews = 0; // get count for (pComposition = pFirst; pComposition != pHalt; pComposition = pComposition->_GetNext()) { cViews++; } if ((_prgUnk = SUA_Alloc(cViews)) == NULL) return FALSE; _iCur = 0; _prgUnk->cRef = 1; _prgUnk->cUnk = cViews; for (i=0, pComposition = pFirst; pComposition != pHalt; i++, pComposition = pComposition->_GetNext()) { _prgUnk->rgUnk[i] = (ITfCompositionView *)pComposition; _prgUnk->rgUnk[i]->AddRef(); } return TRUE; } ////////////////////////////////////////////////////////////////////////////// // // CComposition // ////////////////////////////////////////////////////////////////////////////// /* 3ab2f54c-5357-4759-82c1-bbfe73f44dcc */ const IID IID_PRIV_CCOMPOSITION = { 0x3ab2f54c, 0x5357, 0x4759, {0x82, 0xc1, 0xbb, 0xfe, 0x73, 0xf4, 0x4d, 0xcc} }; inline CComposition *GetCComposition_NA(IUnknown *punk) { CComposition *pComposition; if (punk->QueryInterface(IID_PRIV_CCOMPOSITION, (void **)&pComposition) != S_OK || pComposition == NULL) return NULL; pComposition->Release(); return pComposition; } DBG_ID_INSTANCE(CComposition); //+--------------------------------------------------------------------------- // // _Init // //---------------------------------------------------------------------------- BOOL CComposition::_Init(TfClientId tid, CInputContext *pic, IAnchor *paStart, IAnchor *paEnd, ITfCompositionSink *pSink) { Assert(_paStart == NULL); Assert(_paEnd == NULL); if (paStart->Clone(&_paStart) != S_OK || _paStart == NULL) { _paStart = NULL; goto ExitError; } if (paEnd->Clone(&_paEnd) != S_OK || _paEnd == NULL) { _paEnd = NULL; goto ExitError; } _tid = tid; _pic = pic; _pic->AddRef(); _pSink = pSink; if (_pSink) { _pSink->AddRef(); } return TRUE; ExitError: SafeReleaseClear(_paStart); SafeReleaseClear(_paEnd); return FALSE; } //+--------------------------------------------------------------------------- // // _Uninit // //---------------------------------------------------------------------------- void CComposition::_Uninit() { SafeReleaseClear(_pSink); SafeReleaseClear(_pic); SafeReleaseClear(_paStart); SafeReleaseClear(_paEnd); } //+--------------------------------------------------------------------------- // // GetOwnerClsid // //---------------------------------------------------------------------------- STDAPI CComposition::GetOwnerClsid(CLSID *pclsid) { if (pclsid == NULL) return E_INVALIDARG; if (_IsTerminated()) { memset(pclsid, 0, sizeof(*pclsid)); return E_UNEXPECTED; } return (MyGetGUID(_tid, pclsid) == S_OK ? S_OK : E_FAIL); } //+--------------------------------------------------------------------------- // // GetRange // //---------------------------------------------------------------------------- STDAPI CComposition::GetRange(ITfRange **ppRange) { CRange *range; if (ppRange == NULL) return E_INVALIDARG; *ppRange = NULL; if (_IsTerminated()) return E_UNEXPECTED; if ((range = new CRange) == NULL) return E_OUTOFMEMORY; if (!range->_InitWithDefaultGravity(_pic, COPY_ANCHORS, _paStart, _paEnd)) { range->Release(); return E_FAIL; } *ppRange = (ITfRangeAnchor *)range; return S_OK; } //+--------------------------------------------------------------------------- // // ShiftStart // //---------------------------------------------------------------------------- STDAPI CComposition::ShiftStart(TfEditCookie ec, ITfRange *pNewStart) { CRange *rangeNewStart; CRange *range; IAnchor *paStartNew; IAnchor *paClearStart; IAnchor *paClearEnd; if (_IsTerminated()) return E_UNEXPECTED; if (!_pic->_IsValidEditCookie(ec, TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; } if ((rangeNewStart = GetCRange_NA(pNewStart)) == NULL) return E_INVALIDARG; if (!VerifySameContext(_pic, rangeNewStart)) return E_INVALIDARG; paStartNew = rangeNewStart->_GetStart(); if (CompareAnchors(paStartNew, _paStart) <= 0) { paClearStart = paStartNew; paClearEnd = _paStart; // Set GUID_PROP_COMPOSING _SetComposing(ec, paClearStart, paClearEnd); } else { paClearStart = _paStart; paClearEnd = paStartNew; // check for crossed anchors if (CompareAnchors(_paEnd, paStartNew) < 0) return E_INVALIDARG; // clear GUID_PROP_COMPOSING _ClearComposing(ec, paClearStart, paClearEnd); } if (_pic->_GetOwnerCompositionSink() != NULL) { // notify the app if (range = new CRange) { // make sure the end anchor is positioned correctly if (range->_InitWithDefaultGravity(_pic, COPY_ANCHORS, paStartNew, _paEnd)) { _pic->_GetOwnerCompositionSink()->OnUpdateComposition(this, (ITfRangeAnchor *)range); } range->Release(); } } if (_paStart->ShiftTo(paStartNew) != S_OK) return E_FAIL; return S_OK; } //+--------------------------------------------------------------------------- // // ShiftEnd // //---------------------------------------------------------------------------- STDAPI CComposition::ShiftEnd(TfEditCookie ec, ITfRange *pNewEnd) { CRange *rangeNewEnd; CRange *range; IAnchor *paEndNew; IAnchor *paClearStart; IAnchor *paClearEnd; if (_IsTerminated()) return E_UNEXPECTED; if (!_pic->_IsValidEditCookie(ec, TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; } if ((rangeNewEnd = GetCRange_NA(pNewEnd)) == NULL) return E_INVALIDARG; if (!VerifySameContext(_pic, rangeNewEnd)) return E_INVALIDARG; paEndNew = rangeNewEnd->_GetEnd(); if (CompareAnchors(paEndNew, _paEnd) >= 0) { paClearStart = _paEnd; paClearEnd = paEndNew; // Set GUID_PROP_COMPOSING _SetComposing(ec, paClearStart, paClearEnd); } else { paClearStart = paEndNew; paClearEnd = _paEnd; // check for crossed anchors if (CompareAnchors(_paStart, paEndNew) > 0) return E_INVALIDARG; // clear GUID_PROP_COMPOSING _ClearComposing(ec, paClearStart, paClearEnd); } // notify the app if (_pic->_GetOwnerCompositionSink() != NULL) { if (range = new CRange) { // make sure the end anchor is positioned correctly if (range->_InitWithDefaultGravity(_pic, COPY_ANCHORS, _paStart, paEndNew)) { _pic->_GetOwnerCompositionSink()->OnUpdateComposition(this, (ITfRangeAnchor *)range); } range->Release(); } } if (_paEnd->ShiftTo(paEndNew) != S_OK) return E_FAIL; return S_OK; } //+--------------------------------------------------------------------------- // // EndComposition // // Called by the TIP. //---------------------------------------------------------------------------- STDAPI CComposition::EndComposition(TfEditCookie ec) { if (_IsTerminated()) return E_UNEXPECTED; if (!_pic->_IsValidEditCookie(ec, TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; } if (_tid != _pic->_GetClientInEditSession(ec)) { Assert(0); // caller doesn't own the composition return E_UNEXPECTED; } if (!_pic->_EnterCompositionOp()) return E_UNEXPECTED; // reentrant with another write op // notify the app if (_pic->_GetOwnerCompositionSink() != NULL) { _pic->_GetOwnerCompositionSink()->OnEndComposition(this); } // take this guy off the list of compositions if (_RemoveFromCompositionList(_pic->_GetCompositionListPtr())) { // clear GUID_PROP_COMPOSING _ClearComposing(ec, _paStart, _paEnd); } else { Assert(0); // shouldn't get here } _pic->_LeaveCompositionOp(); _Uninit(); return S_OK; } //+--------------------------------------------------------------------------- // // _Terminate // // Called by Cicero or the app. Caller should already have removed this // composition from _pCompositionList to catch reentrancy during notifications. //---------------------------------------------------------------------------- void CComposition::_Terminate(TfEditCookie ec) { // notify the tip _SendOnTerminated(ec, _tid); // #507778 OnCompositionTerminated() clear _pic by CComposition::_Uninit(). if (_pic) { // notify the app if (_pic->_GetOwnerCompositionSink() != NULL) { _pic->_GetOwnerCompositionSink()->OnEndComposition(this); } } // clear GUID_PROP_COMPOSING _ClearComposing(ec, _paStart, _paEnd); // kill this composition! _Uninit(); } //+--------------------------------------------------------------------------- // // _SendOnTerminated // //---------------------------------------------------------------------------- void CComposition::_SendOnTerminated(TfEditCookie ec, TfClientId tidForEditSession) { TfClientId tidTmp; // _pSink is NULL for default SetText compositions if (_pSink == NULL) return; if (tidForEditSession == _pic->_GetClientInEditSession(ec)) { // we can skip all the exceptional stuff if all the edits // will belong to the current lock holder // this happens when a tip calls StartComposition for a // second composition and cicero needs to term the first _pSink->OnCompositionTerminated(ec, this); } else { // let everyone know about changes so far // the tip we're about to call may need this info _pic->_NotifyEndEdit(); // play some games: this is an exceptional case where we may be allowing a // reentrant edit sess. Need to hack the ec to reflect the composition owner. tidTmp = _pic->_SetRawClientInEditSession(tidForEditSession); // notify the tip _pSink->OnCompositionTerminated(ec, this); // #507778 OnCompositionTerminated() clear _pic by CComposition::_Uninit(). if (! _pic) return; // let everyone know about changes the terminator made _pic->_NotifyEndEdit(); // put things back the way we found them _pic->_SetRawClientInEditSession(tidTmp); } } //+--------------------------------------------------------------------------- // // _AddToCompositionList // //---------------------------------------------------------------------------- void CComposition::_AddToCompositionList(CComposition **ppCompositionList) { _next = *ppCompositionList; *ppCompositionList = this; AddRef(); } //+--------------------------------------------------------------------------- // // _RemoveFromCompositionList // //---------------------------------------------------------------------------- BOOL CComposition::_RemoveFromCompositionList(CComposition **ppCompositionList) { CComposition *pComposition; // I don't expect many compositions, so this method uses a simple // scan. We could do something more elaborate for perf if necessary. while (pComposition = *ppCompositionList) { if (pComposition == this) { *ppCompositionList = _next; Release(); // safe because caller already holds ref return TRUE; } ppCompositionList = &pComposition->_next; } return FALSE; } //+--------------------------------------------------------------------------- // // _AddToCompositionList // //---------------------------------------------------------------------------- /* static */ IRC CComposition::_IsRangeCovered(CInputContext *pic, TfClientId tid, IAnchor *paStart, IAnchor *paEnd, CComposition **ppComposition /* not AddRef'd! */) { CComposition *pComposition; IRC irc = IRC_NO_OWNEDCOMPOSITIONS; *ppComposition = NULL; for (pComposition = pic->_GetCompositionList(); pComposition != NULL; pComposition = pComposition->_next) { if (pComposition->_tid == tid) { irc = IRC_OUTSIDE; if (CompareAnchors(paStart, pComposition->_paStart) >= 0 && CompareAnchors(paEnd, pComposition->_paEnd) <= 0) { *ppComposition = pComposition; irc = IRC_COVERED; break; } } } return irc; } //+--------------------------------------------------------------------------- // // _ClearComposing // //---------------------------------------------------------------------------- void CComposition::_ClearComposing(TfEditCookie ec, IAnchor *paStart, IAnchor *paEnd) { CProperty *property; Assert(!_IsTerminated()); // #507778 OnCompositionTerminated() clear _pic by CComposition::_Uninit(). if (! _pic) return; if (_pic->_GetProperty(GUID_PROP_COMPOSING, &property) != S_OK) return; property->_ClearInternal(ec, paStart, paEnd); property->Release(); } //+--------------------------------------------------------------------------- // // _SetComposing // //---------------------------------------------------------------------------- void CComposition::_SetComposing(TfEditCookie ec, IAnchor *paStart, IAnchor *paEnd) { CProperty *property; if (IsEqualAnchor(paStart, paEnd)) return; if (_pic->_GetProperty(GUID_PROP_COMPOSING, &property) == S_OK) { VARIANT var; var.vt = VT_I4; var.lVal = TRUE; property->_SetDataInternal(ec, paStart, paEnd, &var); property->Release(); } } ////////////////////////////////////////////////////////////////////////////// // // CInputContext // ////////////////////////////////////////////////////////////////////////////// //+--------------------------------------------------------------------------- // // StartComposition // //---------------------------------------------------------------------------- STDAPI CInputContext::StartComposition(TfEditCookie ec, ITfRange *pCompositionRange, ITfCompositionSink *pSink, ITfComposition **ppComposition) { CRange *range; CComposition *pComposition; HRESULT hr; if (ppComposition == NULL) return E_INVALIDARG; *ppComposition = NULL; if (pCompositionRange == NULL || pSink == NULL) return E_INVALIDARG; if ((range = GetCRange_NA(pCompositionRange)) == NULL) return E_INVALIDARG; if (!VerifySameContext(this, range)) return E_INVALIDARG; if (!_IsConnected()) return TF_E_DISCONNECTED; if (!_IsValidEditCookie(ec, TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; } hr = _StartComposition(ec, range->_GetStart(), range->_GetEnd(), pSink, &pComposition); *ppComposition = pComposition; return hr; } //+--------------------------------------------------------------------------- // // _StartComposition // // Internal, allow pSink to be NULL, skips verification tests. //---------------------------------------------------------------------------- HRESULT CInputContext::_StartComposition(TfEditCookie ec, IAnchor *paStart, IAnchor *paEnd, ITfCompositionSink *pSink, CComposition **ppComposition) { BOOL fOk; CComposition *pComposition; CComposition *pCompositionRef; CProperty *property; VARIANT var; HRESULT hr; *ppComposition = NULL; if (!_EnterCompositionOp()) return E_UNEXPECTED; // reentrant with another write op hr = S_OK; if ((pComposition = new CComposition) == NULL) { hr = E_OUTOFMEMORY; goto Exit; } if (!pComposition->_Init(_GetClientInEditSession(ec), this, paStart, paEnd, pSink)) { hr = E_FAIL; goto Exit; } // // AIMM1.2 expect multiple compositon object. SetText() without creating // its own composition object should not clear out TIP's composition. // // cicero 1.0 ------- // all of our clients only allow a single composition. Let's enforce this behavior // to protect cicero 1.0 tips in the future. // kill any existing composition before starting a new one: // if (_pCompositionList != NULL) // { // pCompositionRef = _pCompositionList; // pCompositionRef->AddRef(); // _TerminateCompositionWithLock(pCompositionRef, ec); // pCompositionRef->Release(); // Assert(_pCompositionList == NULL); // } // cicero 1.0 ------- // if (_pOwnerComposeSink == NULL) // app may not care about compositions { fOk = TRUE; } else { if (_pOwnerComposeSink->OnStartComposition(pComposition, &fOk) != S_OK) { hr = E_FAIL; goto Exit; } if (!fOk) { if (_pCompositionList == NULL) goto Exit; // no current compositions, nothing else to try // terminate current composition and try again pCompositionRef = _pCompositionList; // only ref might be in list, so protect the obj pCompositionRef->AddRef(); _TerminateCompositionWithLock(pCompositionRef, ec); pCompositionRef->Release(); if (_pOwnerComposeSink->OnStartComposition(pComposition, &fOk) != S_OK) { hr = E_FAIL; goto Exit; } if (!fOk) goto Exit; // we give up } } // set composition property over existing text if (!IsEqualAnchor(paStart, paEnd) && _GetProperty(GUID_PROP_COMPOSING, &property) == S_OK) { var.vt = VT_I4; var.lVal = TRUE; property->_SetDataInternal(ec, paStart, paEnd, &var); property->Release(); } pComposition->_AddToCompositionList(&_pCompositionList); *ppComposition = pComposition; Exit: if (hr != S_OK || !fOk) { SafeRelease(pComposition); } _LeaveCompositionOp(); return hr; } //+--------------------------------------------------------------------------- // // EnumCompositions // //---------------------------------------------------------------------------- STDAPI CInputContext::EnumCompositions(IEnumITfCompositionView **ppEnum) { CEnumCompositionView *pEnum; if (ppEnum == NULL) return E_INVALIDARG; *ppEnum = NULL; if (!_IsConnected()) return TF_E_DISCONNECTED; if ((pEnum = new CEnumCompositionView) == NULL) return E_OUTOFMEMORY; if (!pEnum->_Init(_pCompositionList, NULL)) { pEnum->Release(); return E_FAIL; } *ppEnum = pEnum; return S_OK; } //+--------------------------------------------------------------------------- // // FindComposition // //---------------------------------------------------------------------------- STDAPI CInputContext::FindComposition(TfEditCookie ec, ITfRange *pTestRange, IEnumITfCompositionView **ppEnum) { CComposition *pFirstComp; CComposition *pHaltComp; CRange *rangeTest; CEnumCompositionView *pEnum; if (ppEnum == NULL) return E_INVALIDARG; *ppEnum = NULL; if (!_IsConnected()) return TF_E_DISCONNECTED; if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; } if (pTestRange == NULL) { return EnumCompositions(ppEnum); } if ((rangeTest = GetCRange_NA(pTestRange)) == NULL) return E_INVALIDARG; if (!VerifySameContext(this, rangeTest)) return E_INVALIDARG; // search thru the list, finding anything covered by the range pFirstComp = NULL; for (pHaltComp = _pCompositionList; pHaltComp != NULL; pHaltComp = pHaltComp->_GetNext()) { if (CompareAnchors(rangeTest->_GetEnd(), pHaltComp->_GetStart()) < 0) break; if (pFirstComp == NULL) { if (CompareAnchors(rangeTest->_GetStart(), pHaltComp->_GetEnd()) <= 0) { pFirstComp = pHaltComp; } } } if (pFirstComp == NULL) { // the enum _Init assumes pFirstComp == NULL -> pHaltComp == NULL pHaltComp = NULL; } if ((pEnum = new CEnumCompositionView) == NULL) return E_OUTOFMEMORY; if (!pEnum->_Init(pFirstComp, pHaltComp)) { pEnum->Release(); return E_FAIL; } *ppEnum = pEnum; return S_OK; } //+--------------------------------------------------------------------------- // // TakeOwnership // //---------------------------------------------------------------------------- STDAPI CInputContext::TakeOwnership(TfEditCookie ec, ITfCompositionView *pComposition, ITfCompositionSink *pSink, ITfComposition **ppComposition) { if (ppComposition == NULL) return E_INVALIDARG; *ppComposition = NULL; #ifndef UNTESTED_UNUSED Assert(0); // no one should be calling this return E_NOTIMPL; #else CComposition *composition; TfClientId tidPrev; if (pComposition == NULL || pSink == NULL) return E_INVALIDARG; if ((composition = GetCComposition_NA(pComposition)) == NULL) return E_INVALIDARG; if (composition->_IsTerminated()) return E_INVALIDARG; // it's dead! if (!_IsConnected()) return TF_E_DISCONNECTED; if (!_IsValidEditCookie(ec, TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; } if (!_EnterCompositionOp()) return E_UNEXPECTED; // reentrant with another write op // switch the owner tidPrev = composition->_SetOwner(_GetClientInEditSession(ec)); // let the old owner know something happened composition->_SendOnTerminated(ec, tidPrev); // switch the sink composition->_SetSink(pSink); _LeaveCompositionOp(); return S_OK; #endif // UNTESTED_UNUSED } //+--------------------------------------------------------------------------- // // TerminateComposition // //---------------------------------------------------------------------------- STDAPI CInputContext::TerminateComposition(ITfCompositionView *pComposition) { HRESULT hr; if (!_IsConnected()) return TF_E_DISCONNECTED; // don't let this happen while we hold a lock // the usual scenario: word freaks out and tries to cancel the composition inside a SetText call // let's give them an error code to help debug if (_IsInEditSession() && _GetTIPOwner() != _tidInEditSession) { Assert(0); // someone's trying to abort a composition without a lock, or they don't own the ic return TF_E_NOLOCK; // meaning the caller doesn't hold the lock } if (pComposition == NULL && _pCompositionList == NULL) return S_OK; // no compositions to terminate, we check later, but check here so we don't fail on read-only docs and for perf if (!_EnterCompositionOp()) return E_UNEXPECTED; // reentrant with another write op // need to ask for a lock (call originates with app) if (_DoPseudoSyncEditSession(TF_ES_READWRITE, PSEUDO_ESCB_TERMCOMPOSITION, pComposition, &hr) != S_OK || hr != S_OK) { Assert(0); hr = E_FAIL; } _LeaveCompositionOp(); return hr; } //+--------------------------------------------------------------------------- // // _TerminateCompositionWithLock // //---------------------------------------------------------------------------- HRESULT CInputContext::_TerminateCompositionWithLock(ITfCompositionView *pComposition, TfEditCookie ec) { CComposition *composition; Assert(ec != TF_INVALID_EDIT_COOKIE); if (pComposition == NULL && _pCompositionList == NULL) return S_OK; // no compositions to terminate while (TRUE) { if (pComposition == NULL) { composition = _pCompositionList; composition->AddRef(); } else { if ((composition = GetCComposition_NA(pComposition)) == NULL) return E_INVALIDARG; if (composition->_IsTerminated()) return E_INVALIDARG; } composition->_Terminate(ec); if (!composition->_RemoveFromCompositionList(&_pCompositionList)) { // how did this guy get off the list w/o termination? Assert(0); // should never get here return E_FAIL; } if (pComposition != NULL) break; composition->Release(); if (_pCompositionList == NULL) break; } return S_OK; } //+--------------------------------------------------------------------------- // // _AbortCompositions // // Called on an ic pop. TIPs do not get a notification because we cannot // guarantee a lock. //---------------------------------------------------------------------------- void CInputContext::_AbortCompositions() { CComposition *pComposition; while (_pCompositionList != NULL) { // notify the app if (_GetOwnerCompositionSink() != NULL) { _GetOwnerCompositionSink()->OnEndComposition(_pCompositionList); } // we won't notify the tip because he can't get a lock here // but there's enough info later to cleanup any state in the ic pop notify _pCompositionList->_Die(); pComposition = _pCompositionList->_GetNext(); _pCompositionList->Release(); _pCompositionList = pComposition; } }