// // tsi.cpp // // CTextStoreImpl // #include "private.h" #include "tsi.h" #include "immxutil.h" #include "tsdo.h" #include "tsattrs.h" #include "ic.h" #include "rprop.h" #define TSI_TOKEN 0x01010101 DBG_ID_INSTANCE(CTextStoreImpl); /* 012313d4-b1e7-476a-bf88-173a316572fb */ extern const IID IID_PRIV_CTSI = { 0x012313d4, 0xb1e7, 0x476a, {0xbf, 0x88, 0x17, 0x3a, 0x31, 0x65, 0x72, 0xfb} }; //+--------------------------------------------------------------------------- // // ctor // //---------------------------------------------------------------------------- CTextStoreImpl::CTextStoreImpl(CInputContext *pic) { Dbg_MemSetThisNameID(TEXT("CTextStoreImpl")); Assert(_fPendingWriteReq == FALSE); Assert(_dwlt == 0); _pic = pic; } //+--------------------------------------------------------------------------- // // dtor // //---------------------------------------------------------------------------- CTextStoreImpl::~CTextStoreImpl() { cicMemFree(_pch); } //+--------------------------------------------------------------------------- // // AdviseSink // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::AdviseSink(REFIID riid, IUnknown *punk, DWORD dwMask) { if (_ptss != NULL) { Assert(0); // cicero shouldn't do this return CONNECT_E_ADVISELIMIT; } if (FAILED(punk->QueryInterface(IID_ITextStoreACPSink, (void **)&_ptss))) return E_UNEXPECTED; return S_OK; } //+--------------------------------------------------------------------------- // // UnadviseSink // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::UnadviseSink(IUnknown *punk) { Assert(_ptss == punk); // we're dealing with cicero, this should always hold SafeReleaseClear(_ptss); return S_OK; } //+--------------------------------------------------------------------------- // // GetSelection // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP *pSelection, ULONG *pcFetched) { if (pcFetched == NULL) return E_INVALIDARG; *pcFetched = 0; if (ulIndex > 1 && ulIndex != TS_DEFAULT_SELECTION) return E_INVALIDARG; // index too high if (ulCount == 0 || ulIndex == 1) return S_OK; if (pSelection == NULL) return E_INVALIDARG; pSelection[0] = _Sel; *pcFetched = 1; return S_OK; } //+--------------------------------------------------------------------------- // // SetSelection // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::SetSelection(ULONG ulCount, const TS_SELECTION_ACP *pSelection) { Assert(ulCount > 0); // should have been caught by caller Assert(pSelection != NULL); // should have been caught by caller Assert(pSelection[0].acpStart >= 0); Assert(pSelection[0].acpEnd >= pSelection[0].acpStart); if (ulCount > 1) return E_FAIL; // don't support disjoint sel if (pSelection[0].acpEnd > _cch) return TS_E_INVALIDPOS; _Sel = pSelection[0]; return S_OK; } //+--------------------------------------------------------------------------- // // GetText // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetText(LONG acpStart, LONG acpEnd, WCHAR *pchPlain, ULONG cchPlainReq, ULONG *pcchPlainOut, TS_RUNINFO *prgRunInfo, ULONG ulRunInfoReq, ULONG *pulRunInfoOut, LONG *pacpNext) { ULONG cch; *pcchPlainOut = 0; *pulRunInfoOut = 0; *pacpNext = acpStart; if (acpStart < 0 || acpStart > _cch) return TS_E_INVALIDPOS; // get a count of acp chars requested cch = (acpEnd >= acpStart) ? acpEnd - acpStart : _cch - acpStart; // since we're plain text, we can also simply clip by the plaintext buffer len if (cchPlainReq > 0) // if they don't want plain text we won't clip! { cch = min(cch, cchPlainReq); } // check for eod if (acpStart + cch > (ULONG)_cch) { cch = _cch - acpStart; } if (ulRunInfoReq > 0 && cch > 0) { *pulRunInfoOut = 1; prgRunInfo[0].uCount = cch; prgRunInfo[0].type = TS_RT_PLAIN; } if (cchPlainReq > 0) { // we're a plain text buffer, so we always copy all the requested chars *pcchPlainOut = cch; memcpy(pchPlain, _pch + acpStart, cch*sizeof(WCHAR)); } *pacpNext = acpStart + cch; return S_OK; } //+--------------------------------------------------------------------------- // // SetText // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, const WCHAR *pchText, ULONG cch, TS_TEXTCHANGE *pChange) { int iSizeRange; int cchAdjust; TS_STATUS tss; WCHAR *pch = NULL; // Since we know our only caller will be cicero, we can assert rather than // returning failure codes. Assert(acpStart >= 0); Assert(acpStart <= acpEnd); if (acpEnd > _cch) return TS_E_INVALIDPOS; if (_owner != NULL && _owner->GetStatus(&tss) == S_OK && (tss.dwDynamicFlags & TS_SD_READONLY)) { return TS_E_READONLY; } // // Check mapped app property for TSATTRID_Text_ReadOnly. // CProperty *pProp; BOOL fReadOnly = FALSE; if (SUCCEEDED(_pic->GetMappedAppProperty(TSATTRID_Text_ReadOnly, &pProp))) { ITfRangeACP *range; if (SUCCEEDED(_pic->CreateRange(acpStart, acpEnd, &range))) { IEnumTfRanges *pEnumRanges; if (SUCCEEDED(pProp->EnumRanges(BACKDOOR_EDIT_COOKIE, &pEnumRanges, range))) { ITfRange *rangeTmp; while (pEnumRanges->Next(1, &rangeTmp, NULL) == S_OK) { VARIANT var; if (pProp->GetValue(BACKDOOR_EDIT_COOKIE, rangeTmp, &var) == S_OK) { if (var.lVal != 0) { fReadOnly = TRUE; break; } } rangeTmp->Release(); } pEnumRanges->Release(); } range->Release(); } pProp->Release(); } if (fReadOnly) { return TS_E_READONLY; } // this will all be rewritten for the gap buffer, so keep it simple for now. // delete the ranage, then insert the new text. iSizeRange = acpEnd - acpStart; cchAdjust = (LONG)cch - iSizeRange; if (cchAdjust > 0) { // if we need to alloc more memory, try now, to handle failure gracefully if ((pch = (_pch == NULL) ? (WCHAR *)cicMemAlloc((_cch + cchAdjust)*sizeof(WCHAR)) : (WCHAR *)cicMemReAlloc(_pch, (_cch + cchAdjust)*sizeof(WCHAR))) == NULL) { return E_OUTOFMEMORY; } // we're all set _pch = pch; } // // shift existing text to the right of the range // memmove(_pch + acpStart + cch, _pch + acpStart + iSizeRange, (_cch - iSizeRange - acpStart)*sizeof(WCHAR)); // // now fill in the gap // if (pchText != NULL) { memcpy(_pch + acpStart, pchText, cch*sizeof(WCHAR)); } // // update our buffer size // _cch += cchAdjust; Assert(_cch >= 0); // if we shrank, try to realloc a smaller buffer (otherwise we alloc'd above) if (cchAdjust < 0) { if (_cch == 0) { cicMemFree(_pch); _pch = NULL; } else if (pch = (WCHAR *)cicMemReAlloc(_pch, _cch*sizeof(WCHAR))) { _pch = pch; } } // handle the out params pChange->acpStart = acpStart; pChange->acpOldEnd = acpEnd; pChange->acpNewEnd = acpEnd + cchAdjust; // // update the selection // _Sel.acpStart = AdjustAnchor(acpStart, acpEnd, cch, _Sel.acpStart, FALSE); _Sel.acpEnd = AdjustAnchor(acpStart, acpEnd, cch, _Sel.acpEnd, TRUE); Assert(_Sel.acpStart >= 0); Assert(_Sel.acpStart <= _Sel.acpEnd); Assert(_Sel.acpEnd <= _cch); // never need to call OnTextChange because we have only one adaptor // and this class never calls SetText internally // do the OnDelta //_ptss->OnTextChange(acpStart, acpEnd, acpStart + cch); return S_OK; } //+--------------------------------------------------------------------------- // // GetFormattedText // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetFormattedText(LONG acpStart, LONG acpEnd, IDataObject **ppDataObject) { CTFDataObject *pcdo; Assert(acpStart >= 0 && acpEnd <= _cch); Assert(acpStart <= acpEnd); Assert(ppDataObject != NULL); *ppDataObject = NULL; pcdo = new CTFDataObject; if (pcdo == NULL) return E_OUTOFMEMORY; if (FAILED(pcdo->_SetData(&_pch[acpStart], acpEnd - acpStart))) { Assert(0); pcdo->Release(); return E_FAIL; } return S_OK; } //+--------------------------------------------------------------------------- // // GetEmbedded // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown **ppunk) { return E_NOTIMPL; } //+--------------------------------------------------------------------------- // // InsertEmbedded // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject *pDataObject, TS_TEXTCHANGE *pChange) { ULONG cch; FORMATETC fe; STGMEDIUM sm; WCHAR *pch; HRESULT hr; Assert(acpStart <= acpEnd); Assert((dwFlags & ~TS_IE_CORRECTION) == 0); Assert(pDataObject != NULL); Assert(pChange != NULL); memset(pChange, 0, sizeof(*pChange)); fe.cfFormat = CF_UNICODETEXT; fe.ptd = NULL; fe.dwAspect = DVASPECT_CONTENT; fe.lindex = -1; fe.tymed = TYMED_HGLOBAL; if (FAILED(pDataObject->GetData(&fe, &sm))) return TS_E_FORMAT; if (sm.hGlobal == NULL) return E_FAIL; pch = (WCHAR *)GlobalLock(sm.hGlobal); cch = wcslen(pch); hr = SetText(dwFlags, acpStart, acpEnd, pch, cch, pChange); GlobalUnlock(sm.hGlobal); ReleaseStgMedium(&sm); return hr; } //+--------------------------------------------------------------------------- // // RequestLock // //---------------------------------------------------------------------------- #define TS_LF_WRITE (TS_LF_READWRITE & ~TS_LF_READ) STDAPI CTextStoreImpl::RequestLock(DWORD dwLockFlags, HRESULT *phrSession) { Assert(phrSession != NULL); // caller should have caught this if (_dwlt != 0) { *phrSession = E_UNEXPECTED; // this is a reentrant call // only one case is legal if ((_dwlt & TS_LF_WRITE) || !(dwLockFlags & TS_LF_WRITE) || (dwLockFlags & TS_LF_SYNC)) { Assert(0); // bogus reentrant lock req! return E_UNEXPECTED; } _fPendingWriteReq = TRUE; *phrSession = TS_S_ASYNC; return S_OK; } _dwlt = dwLockFlags; *phrSession = _ptss->OnLockGranted(dwLockFlags); if (_fPendingWriteReq) { _dwlt = TS_LF_READWRITE; _fPendingWriteReq = FALSE; if (_ptss != NULL) // might be NULL if we're disconnected during the OnLockGranted above { _ptss->OnLockGranted(TS_LF_READWRITE); } } _dwlt = 0; return S_OK; } //+--------------------------------------------------------------------------- // // GetStatus // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetStatus(TS_STATUS *pdcs) { HRESULT hr; if (_owner != NULL) { hr = _owner->GetStatus(pdcs); // only let the owner ctl certain bits if (hr == S_OK) { pdcs->dwDynamicFlags &= (TF_SD_READONLY | TF_SD_LOADING); pdcs->dwStaticFlags &= (TF_SS_TRANSITORY); } else { memset(pdcs, 0, sizeof(*pdcs)); } } else { hr = S_OK; memset(pdcs, 0, sizeof(*pdcs)); } return hr; } //+--------------------------------------------------------------------------- // // QueryInsert // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG *pacpResultStart, LONG *pacpResultEnd) { Assert(acpTestStart >= 0); Assert(acpTestStart <= acpTestEnd); Assert(acpTestEnd <= _cch); // default text store does not support overtype, and the selection is always replaced *pacpResultStart = acpTestStart; *pacpResultEnd = acpTestEnd; return S_OK; } //+--------------------------------------------------------------------------- // // Unlock // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetEndACP(LONG *pacp) { *pacp = _cch; return S_OK; } //+--------------------------------------------------------------------------- // // GetACPFromPoint // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetACPFromPoint(TsViewCookie vcView, const POINT *pt, DWORD dwFlags, LONG *pacp) { Assert(vcView == TSI_ACTIVE_VIEW_COOKIE); // default tsi only has a single view if (_owner == NULL) return E_FAIL; // who ever owns the ic hasn't bothered to give us a callback.... return _owner->GetACPFromPoint(pt, dwFlags, pacp); } //+--------------------------------------------------------------------------- // // GetScreenExt // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetScreenExt(TsViewCookie vcView, RECT *prc) { Assert(vcView == TSI_ACTIVE_VIEW_COOKIE); // default tsi only has a single view if (_owner == NULL) return E_FAIL; // who ever owns the ic hasn't bothered to give us a callback.... return _owner->GetScreenExt(prc); } //+--------------------------------------------------------------------------- // // GetTextExt // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped) { Assert(vcView == TSI_ACTIVE_VIEW_COOKIE); // default tsi only has a single view if (_owner == NULL) return E_FAIL; // who ever owns the ic hasn't bothered to give us a callback.... return _owner->GetTextExt(acpStart, acpEnd, prc, pfClipped); } //+--------------------------------------------------------------------------- // // GetWnd // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetWnd(TsViewCookie vcView, HWND *phwnd) { Assert(vcView == TSI_ACTIVE_VIEW_COOKIE); // default tsi only has a single view Assert(phwnd != NULL); // should have caught this in the ic *phwnd = NULL; if (_owner == NULL) return E_FAIL; // who ever owns the ic hasn't bothered to give us a callback.... return _owner->GetWnd(phwnd); } //+--------------------------------------------------------------------------- // // _LoadAttr // //---------------------------------------------------------------------------- HRESULT CTextStoreImpl::_LoadAttr(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs) { ULONG i; HRESULT hr; ClearAttrStore(); if (dwFlags & TS_ATTR_FIND_WANT_VALUE) { if (_owner == NULL) return E_FAIL; } for (i=0; iGetAttribute(paFilterAttrs[i], &var) != S_OK) { ClearAttrStore(); return E_FAIL; } } else { // Issue: benwest: I think these should be init'd to VT_EMPTY if caller doesn't specify TS_ATTR_FIND_WANT_VALUE if (IsEqualGUID(paFilterAttrs[i], GUID_PROP_MODEBIAS)) { var.vt = VT_I4; var.lVal = TF_INVALID_GUIDATOM; } else if (IsEqualGUID(paFilterAttrs[i], TSATTRID_Text_Orientation)) { var.vt = VT_I4; var.lVal = 0; } else if (IsEqualGUID(paFilterAttrs[i], TSATTRID_Text_VerticalWriting)) { var.vt = VT_BOOL; var.lVal = 0; } } if (var.vt != VT_EMPTY) { TSI_ATTRSTORE *pas = _rgAttrStore.Append(1); if (!pas) { hr = E_OUTOFMEMORY; goto Exit; } pas->attrid = paFilterAttrs[i]; pas->var = var; } } hr = S_OK; Exit: return hr; } //+--------------------------------------------------------------------------- // // RequestSupportedAttrs // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs) { // note the return value is technically a default value, but since we have a value for every location // this will never need to be used return _LoadAttr(dwFlags, cFilterAttrs, paFilterAttrs); } //+--------------------------------------------------------------------------- // // RequestAttrsAtPosition // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags) { return _LoadAttr(TS_ATTR_FIND_WANT_VALUE, cFilterAttrs, paFilterAttrs); } //+--------------------------------------------------------------------------- // // RequestAttrsTransitioningAtPosition // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::RequestAttrsTransitioningAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags) { ClearAttrStore(); return S_OK; } //+--------------------------------------------------------------------------- // // FindNextAttrTransition // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::FindNextAttrTransition(LONG acpStart, LONG acpHaltPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags, LONG *pacpNext, BOOL *pfFound, LONG *plFoundOffset) { // our attrs never transition *pacpNext = acpStart; *pfFound = FALSE; plFoundOffset = 0; return S_OK; } //+--------------------------------------------------------------------------- // // RetrieveRequestedAttrs // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL *paAttrVals, ULONG *pcFetched) { ULONG i = 0; while((i < ulCount) && ((int)i < _rgAttrStore.Count())) { TSI_ATTRSTORE *pas = _rgAttrStore.GetPtr(i); paAttrVals->idAttr = pas->attrid; paAttrVals->dwOverlapId = 0; QuickVariantInit(&paAttrVals->varValue); paAttrVals->varValue = pas->var; paAttrVals++; i++; } *pcFetched = i; ClearAttrStore(); return S_OK; } //+--------------------------------------------------------------------------- // // QueryService // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::QueryService(REFGUID guidService, REFIID riid, void **ppv) { if (ppv == NULL) return E_INVALIDARG; *ppv = NULL; if (!IsEqualGUID(guidService, GUID_SERVICE_TF) || !IsEqualIID(riid, IID_PRIV_CTSI)) { // SVC_E_NOSERVICE is proper return code for wrong service.... // but it's not defined anywhere. So use E_NOINTERFACE for both // cases as trident is rumored to do return E_NOINTERFACE; } *ppv = this; AddRef(); return S_OK; } //+--------------------------------------------------------------------------- // // GetActiveView // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::GetActiveView(TsViewCookie *pvcView) { // each CEditWnd has only a single view, so this can be constant. *pvcView = TSI_ACTIVE_VIEW_COOKIE; return S_OK; } //+--------------------------------------------------------------------------- // // AdviseMouseSink // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::AdviseMouseSink(ITfRangeACP *range, ITfMouseSink *pSink, DWORD *pdwCookie) { ITfMouseTrackerACP *pTracker; HRESULT hr; Assert(range != NULL); Assert(pSink != NULL); Assert(pdwCookie != NULL); *pdwCookie = 0; if (_owner == NULL) return E_FAIL; if (_owner->QueryInterface(IID_ITfMouseTrackerACP, (void **)&pTracker) != S_OK) return E_NOTIMPL; hr = pTracker->AdviseMouseSink(range, pSink, pdwCookie); pTracker->Release(); return hr; } //+--------------------------------------------------------------------------- // // UnadviseMouseSink // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::UnadviseMouseSink(DWORD dwCookie) { ITfMouseTrackerACP *pTracker; HRESULT hr; if (_owner == NULL) return E_FAIL; if (_owner->QueryInterface(IID_ITfMouseTrackerACP, (void **)&pTracker) != S_OK) return E_NOTIMPL; hr = pTracker->UnadviseMouseSink(dwCookie); pTracker->Release(); return hr; } //+--------------------------------------------------------------------------- // // QueryInsertEmbedded // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::QueryInsertEmbedded(const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable) { Assert(pfInsertable != NULL); // cicero should have caught this *pfInsertable = FALSE; // only accept unicode text if (pguidService == NULL && pFormatEtc != NULL && pFormatEtc->cfFormat == CF_UNICODETEXT && pFormatEtc->dwAspect == DVASPECT_CONTENT && pFormatEtc->lindex == -1 && pFormatEtc->tymed == TYMED_HGLOBAL) { *pfInsertable = TRUE; } return S_OK; } //+--------------------------------------------------------------------------- // // InsertTextAtSelection // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::InsertTextAtSelection(DWORD dwFlags, const WCHAR *pchText, ULONG cch, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { HRESULT hr; Assert((dwFlags & TS_IAS_QUERYONLY) || pchText != NULL); // caller should have already caught this Assert((dwFlags & TS_IAS_QUERYONLY) || cch > 0); // caller should have already caught this Assert(pacpStart != NULL && pacpEnd != NULL); // caller should have already caught this Assert((dwFlags & (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)) != (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)); // caller should have already caught this if (dwFlags & TS_IAS_QUERYONLY) goto Exit; *pacpStart = -1; *pacpEnd = -1; hr = SetText(0, _Sel.acpStart, _Sel.acpEnd, pchText, cch, pChange); if (hr != S_OK) return hr; Exit: // since this is cheap, always set the insert span even if caller sets TS_IAS_NOQUERY *pacpStart = _Sel.acpStart; *pacpEnd = _Sel.acpEnd; return S_OK; } //+--------------------------------------------------------------------------- // // InsertEmbeddedAtSelection // //---------------------------------------------------------------------------- STDAPI CTextStoreImpl::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject *pDataObject, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { HRESULT hr; Assert((dwFlags & TS_IAS_QUERYONLY) || pDataObject != NULL); // caller should have already caught this Assert(pacpStart != NULL && pacpEnd != NULL); // caller should have already caught this Assert((dwFlags & (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)) != (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)); // caller should have already caught this if (dwFlags & TS_IAS_QUERYONLY) goto Exit; *pacpStart = -1; *pacpEnd = -1; hr = InsertEmbedded(0, _Sel.acpStart, _Sel.acpEnd, pDataObject, pChange); if (hr != S_OK) return hr; Exit: // since this is cheap, always set the insert span even if caller sets TS_IAS_QUERYONLY *pacpStart = _Sel.acpStart; *pacpEnd = _Sel.acpEnd; return S_OK; }