/************************************************************************** THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Copyright 2001 Microsoft Corporation. All Rights Reserved. **************************************************************************/ /************************************************************************** File: TextStor.cpp Description: ITextStoreACP Implementation **************************************************************************/ /************************************************************************** #include statements **************************************************************************/ #include "TSFEdit.h" #include "DataObj.h" #include "Globals.h" #include /************************************************************************** global variables **************************************************************************/ /************************************************************************** CTSFEditWnd::AdviseSink() **************************************************************************/ STDMETHODIMP CTSFEditWnd::AdviseSink(REFIID riid, IUnknown *pUnknown, DWORD dwMask) { OutputDebugString(TEXT("CTSFEditWnd::AdviseSink \n")); HRESULT hr; IUnknown *punkID; //Get the "real" IUnknown pointer. This needs to be done for comparison purposes. hr = pUnknown->QueryInterface(IID_IUnknown, (LPVOID*)&punkID); if(FAILED(hr)) { return hr; } hr = E_INVALIDARG; //see if this advise sink already exists if(punkID == m_AdviseSink.punkID) { //this is the same advise sink, so just update the advise mask m_AdviseSink.dwMask = dwMask; hr = S_OK; } else if(NULL != m_AdviseSink.punkID) { //only one advise sink is allowed at a time hr = CONNECT_E_ADVISELIMIT; } else if(IsEqualIID(riid, IID_ITextStoreACPSink)) { //set the advise mask m_AdviseSink.dwMask = dwMask; /* Set the IUnknown pointer. This is used for comparison in UnadviseSink and future calls to this method. */ m_AdviseSink.punkID = punkID; //AddRef this because it will get released below and it needs to be kept punkID->AddRef(); //get the ITextStoreACPSink interface pUnknown->QueryInterface(IID_ITextStoreACPSink, (LPVOID*)&m_AdviseSink.pTextStoreACPSink); //get the ITextStoreACPServices interface pUnknown->QueryInterface(IID_ITextStoreACPServices, (LPVOID*)&m_pServices); hr = S_OK; } //this isn't needed anymore punkID->Release(); return hr; } /************************************************************************** CTSFEditWnd::UnadviseSink() **************************************************************************/ STDMETHODIMP CTSFEditWnd::UnadviseSink(IUnknown *pUnknown) { OutputDebugString(TEXT("CTSFEditWnd::UnadviseSink \n")); HRESULT hr; IUnknown *punkID; /* Get the "real" IUnknown pointer. This needs to be done for comparison purposes. */ hr = pUnknown->QueryInterface(IID_IUnknown, (LPVOID*)&punkID); if(FAILED(hr)) { return hr; } //find the advise sink if(punkID == m_AdviseSink.punkID) { //remove the advise sink from the list _ClearAdviseSink(&m_AdviseSink); if(m_pServices) { m_pServices->Release(); m_pServices = NULL; } hr = S_OK; } else { hr = CONNECT_E_NOCONNECTION; } punkID->Release(); return hr; } /************************************************************************** CTSFEditWnd::RequestLock() **************************************************************************/ STDMETHODIMP CTSFEditWnd::RequestLock(DWORD dwLockFlags, HRESULT *phrSession) { OutputDebugString(TEXT("CTSFEditWnd::RequestLock \n")); if(NULL == m_AdviseSink.pTextStoreACPSink) { return E_UNEXPECTED; } if(NULL == phrSession) { return E_INVALIDARG; } *phrSession = E_FAIL; if(m_fLocked) { //the document is locked if(dwLockFlags & TS_LF_SYNC) { /* The caller wants an immediate lock, but this cannot be granted because the document is already locked. */ *phrSession = TS_E_SYNCHRONOUS; return S_OK; } else { //the request is asynchronous /* The only type of asynchronous lock request this application supports while the document is locked is to upgrade from a read lock to a read/write lock. This scenario is referred to as a lock upgrade request. */ if(((m_dwLockType & TS_LF_READWRITE) == TS_LF_READ) && ((dwLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE)) { m_fPendingLockUpgrade = TRUE; *phrSession = TS_S_ASYNC; return S_OK; } } return E_FAIL; } //lock the document _LockDocument(dwLockFlags); //call OnLockGranted *phrSession = m_AdviseSink.pTextStoreACPSink->OnLockGranted(dwLockFlags); //unlock the document _UnlockDocument(); return S_OK; } /************************************************************************** CTSFEditWnd::GetStatus() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetStatus(TS_STATUS *pdcs) { OutputDebugString(TEXT("CTSFEditWnd::GetStatus \n")); if(NULL == pdcs) { return E_INVALIDARG; } /* Can be zero or: TS_SD_READONLY // if set, document is read only; writes will fail TS_SD_LOADING // if set, document is loading, expect additional inserts */ pdcs->dwDynamicFlags = 0; /* Can be zero or: TS_SS_DISJOINTSEL // if set, the document supports multiple selections TS_SS_REGIONS // if clear, the document will never contain multiple regions TS_SS_TRANSITORY // if set, the document is expected to have a short lifespan TS_SS_NOHIDDENTEXT // if set, the document will never contain hidden text (for perf) */ pdcs->dwStaticFlags = TS_SS_REGIONS; return S_OK; } /************************************************************************** CTSFEditWnd::QueryInsert() **************************************************************************/ STDMETHODIMP CTSFEditWnd::QueryInsert( LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG *pacpResultStart, LONG *pacpResultEnd) { OutputDebugString(TEXT("CTSFEditWnd::QueryInsert\n")); LONG lTextLength; lTextLength = GetWindowTextLength(m_hwndEdit); //make sure the parameters are within range of the document if( (acpTestStart > acpTestEnd) || (acpTestEnd > lTextLength)) { return E_INVALIDARG; } //set the start point to the given start point *pacpResultStart = acpTestStart; //set the end point to the given end point *pacpResultEnd = acpTestEnd; return S_OK; } /************************************************************************** CTSFEditWnd::_TestInsert() This method is similar to QueryInsert except this method assumes the insertion will actually happen, so the document length would get expanded to fit the inserted text. QueryInsert doesn't allow the ranges to go outside of the existing text. **************************************************************************/ STDMETHODIMP CTSFEditWnd::_TestInsert( LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG *pacpResultStart, LONG *pacpResultEnd) { //make sure the parameters are within range of the document if(acpTestStart > acpTestEnd) { return E_INVALIDARG; } //set the start point after the insertion *pacpResultStart = acpTestStart; //set the end point after the insertion *pacpResultEnd = acpTestStart + cch; return S_OK; } /************************************************************************** CTSFEditWnd::GetSelection() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetSelection( ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP *pSelection, ULONG *pcFetched) { OutputDebugString(TEXT("CTSFEditWnd::GetSelection \n")); //verify pSelection if(NULL == pSelection) { return E_INVALIDARG; } //verify pcFetched if(NULL == pcFetched) { return E_INVALIDARG; } *pcFetched = 0; //does the caller have a lock if(!_IsLocked(TS_LF_READ)) { //the caller doesn't have a lock return TS_E_NOLOCK; } //check the requested index if(TF_DEFAULT_SELECTION == ulIndex) { ulIndex = 0; } else if (ulIndex > 1) { /* The index is too high. This app only supports one selection. */ return E_INVALIDARG; } _GetCurrentSelection(); //find out which end of the selection the caret (insertion point) is POINT pt; LRESULT lPos; GetCaretPos(&pt); lPos = ::SendMessage(m_hwndEdit, EM_POSFROMCHAR, m_acpStart, 0); //if the caret position is the same as the start character, then the selection end is the start of the selection m_ActiveSelEnd = ((pt.x == LOWORD(lPos) && pt.y == HIWORD(lPos)) ? TS_AE_START : TS_AE_END); pSelection[0].acpStart = m_acpStart; pSelection[0].acpEnd = m_acpEnd; pSelection[0].style.fInterimChar = m_fInterimChar; if(m_fInterimChar) { /* fInterimChar will be set when an intermediate character has been set. One example of when this will happen is when an IME is being used to enter characters and a character has been set, but the IME is still active. */ pSelection[0].style.ase = TS_AE_NONE; } else { pSelection[0].style.ase = m_ActiveSelEnd; } *pcFetched = 1; return S_OK; } /************************************************************************** CTSFEditWnd::SetSelection() **************************************************************************/ STDMETHODIMP CTSFEditWnd::SetSelection( ULONG ulCount, const TS_SELECTION_ACP *pSelection) { OutputDebugString(TEXT("CTSFEditWnd::SetSelection \n")); //verify pSelection if(NULL == pSelection) { return E_INVALIDARG; } if(ulCount > 1) { //this implementaiton only supports a single selection return E_INVALIDARG; } //does the caller have a lock if(!_IsLocked(TS_LF_READWRITE)) { //the caller doesn't have a lock return TS_E_NOLOCK; } m_acpStart = pSelection[0].acpStart; m_acpEnd = pSelection[0].acpEnd; m_fInterimChar = pSelection[0].style.fInterimChar; if(m_fInterimChar) { /* fInterimChar will be set when an intermediate character has been set. One example of when this will happen is when an IME is being used to enter characters and a character has been set, but the IME is still active. */ m_ActiveSelEnd = TS_AE_NONE; } else { m_ActiveSelEnd = pSelection[0].style.ase; } //if the selection end is at the start of the selection, reverse the parameters LONG lStart = m_acpStart; LONG lEnd = m_acpEnd; if(TS_AE_START == m_ActiveSelEnd) { lStart = m_acpEnd; lEnd = m_acpStart; } m_fNotify = FALSE; ::SendMessage(m_hwndEdit, EM_SETSEL, lStart, lEnd); m_fNotify = TRUE; return S_OK; } /************************************************************************** CTSFEditWnd::GetText() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetText( LONG acpStart, LONG acpEnd, WCHAR *pchPlain, ULONG cchPlainReq, ULONG *pcchPlainOut, TS_RUNINFO *prgRunInfo, ULONG ulRunInfoReq, ULONG *pulRunInfoOut, LONG *pacpNext) { OutputDebugString(TEXT("CTSFEditWnd::GetText\n")); //does the caller have a lock if(!_IsLocked(TS_LF_READ)) { //the caller doesn't have a lock return TS_E_NOLOCK; } BOOL fDoText = cchPlainReq > 0; BOOL fDoRunInfo = ulRunInfoReq > 0; LONG cchTotal; HRESULT hr = E_FAIL; if(pcchPlainOut) { *pcchPlainOut = 0; } if(fDoRunInfo) { *pulRunInfoOut = 0; } if(pacpNext) { *pacpNext = acpStart; } //get all of the text LPWSTR pwszText; hr = _GetText(&pwszText, &cchTotal); if(FAILED(hr)) { return hr; } //validate the start pos if((acpStart < 0) || (acpStart > cchTotal)) { hr = TS_E_INVALIDPOS; } else { //are we at the end of the document if(acpStart == cchTotal) { hr = S_OK; } else { ULONG cchReq; /* acpEnd will be -1 if all of the text up to the end is being requested. */ if(acpEnd >= acpStart) { cchReq = acpEnd - acpStart; } else { cchReq = cchTotal - acpStart; } if(fDoText) { if(cchReq > cchPlainReq) { cchReq = cchPlainReq; } //extract the specified text range LPWSTR pwszStart = pwszText + acpStart; if(pchPlain && cchPlainReq) { //the text output is not NULL terminated CopyMemory(pchPlain, pwszStart, cchReq * sizeof(WCHAR)); } } //it is possible that only the length of the text is being requested if(pcchPlainOut) { *pcchPlainOut = cchReq; } if(fDoRunInfo) { /* Runs are used to separate text characters from formatting characters. In this example, sequences inside and including the <> are treated as control sequences and are not displayed. Plain text = "Text formatting." Actual text = "Text formatting." If all of this text were requested, the run sequence would look like this: prgRunInfo[0].type = TS_RT_PLAIN; //"Text " prgRunInfo[0].uCount = 5; prgRunInfo[1].type = TS_RT_HIDDEN; // prgRunInfo[1].uCount = 6; prgRunInfo[2].type = TS_RT_PLAIN; //"formatting" prgRunInfo[2].uCount = 10; prgRunInfo[3].type = TS_RT_HIDDEN; // prgRunInfo[3].uCount = 8; prgRunInfo[4].type = TS_RT_PLAIN; //"." prgRunInfo[4].uCount = 1; TS_RT_OPAQUE is used to indicate characters or character sequences that are in the document, but are used privately by the application and do not map to text. Runs of text tagged with TS_RT_OPAQUE should NOT be included in the pchPlain or cchPlainOut [out] parameters. */ /* This implementation is plain text, so the text only consists of one run. If there were multiple runs, it would be an error to have consecuative runs of the same type. */ *pulRunInfoOut = 1; prgRunInfo[0].type = TS_RT_PLAIN; prgRunInfo[0].uCount = cchReq; } if(pacpNext) { *pacpNext = acpStart + cchReq; } hr = S_OK; } } GlobalFree(pwszText); return hr; } /************************************************************************** CTSFEditWnd::SetText() **************************************************************************/ STDMETHODIMP CTSFEditWnd::SetText( DWORD dwFlags, LONG acpStart, LONG acpEnd, const WCHAR *pchText, ULONG cch, TS_TEXTCHANGE *pChange) { OutputDebugString(TEXT("CTSFEditWnd::SetText \n")); HRESULT hr; /* dwFlags can be: TS_ST_CORRECTION */ if(dwFlags & TS_ST_CORRECTION) { OutputDebugString(TEXT("\tTS_ST_CORRECTION\n")); } //set the selection to the specified range TS_SELECTION_ACP tsa; tsa.acpStart = acpStart; tsa.acpEnd = acpEnd; tsa.style.ase = TS_AE_START; tsa.style.fInterimChar = FALSE; hr = SetSelection(1, &tsa); if(SUCCEEDED(hr)) { //call InsertTextAtSelection hr = InsertTextAtSelection(TS_IAS_NOQUERY, pchText, cch, NULL, NULL, pChange); } return hr; } /************************************************************************** CTSFEditWnd::GetFormattedText() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetFormattedText( LONG acpStart, LONG acpEnd, IDataObject **ppDataObject) { OutputDebugString(TEXT("CTSFEditWnd::GetFormattedText \n")); if(NULL == ppDataObject) { return E_INVALIDARG; } *ppDataObject = NULL; //does the caller have a lock if(!_IsLocked(TS_LF_READ)) { //the caller doesn't have a lock return TS_E_NOLOCK; } HRESULT hr; CTSFDataObject *pdo = new CTSFDataObject; if(NULL != pdo) { //get the text ULONG cchOut; if(-1 == acpEnd) { //get the length of all of the text hr = GetText(acpStart, acpEnd, NULL, 0, &cchOut, NULL, 0, NULL, NULL); } else { cchOut = acpEnd - acpStart; hr = S_OK; } if(SUCCEEDED(hr)) { LPWSTR pwszTemp = (LPWSTR)GlobalAlloc(GPTR, (cchOut + 1) * sizeof(WCHAR)); if(NULL != pwszTemp) { hr = GetText(acpStart, acpEnd, pwszTemp, cchOut, &cchOut, NULL, 0, NULL, NULL); pwszTemp[cchOut] = '\0'; if(SUCCEEDED(hr)) { //set the text in the data object hr = pdo->_SetText(pwszTemp); if(SUCCEEDED(hr)) { //get the IID_IDataObject interface hr = pdo->QueryInterface(IID_IDataObject, (LPVOID*)ppDataObject); } } } else { hr = E_OUTOFMEMORY; } } //release the interface pdo->Release(); } else { hr = E_OUTOFMEMORY; } return hr; } /************************************************************************** CTSFEditWnd::GetEmbedded() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetEmbedded( LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown **ppunk) { OutputDebugString(TEXT("CTSFEditWnd::GetEmbedded \n")); //this implementation doesn't support embedded objects return E_NOTIMPL; } /************************************************************************** CTSFEditWnd::QueryInsertEmbedded() **************************************************************************/ STDMETHODIMP CTSFEditWnd::QueryInsertEmbedded( const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable) { OutputDebugString(TEXT("CTSFEditWnd::QueryInsertEmbedded \n")); //this implementation doesn't support embedded objects *pfInsertable = FALSE; return S_OK; } /************************************************************************** CTSFEditWnd::InsertEmbedded() **************************************************************************/ STDMETHODIMP CTSFEditWnd::InsertEmbedded( DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject *pDataObject, TS_TEXTCHANGE *pChange) { OutputDebugString(TEXT("CTSFEditWnd::InsertEmbedded \n")); //this implementation doesn't support embedded objects return E_NOTIMPL; } /************************************************************************** CTSFEditWnd::RequestSupportedAttrs() **************************************************************************/ STDMETHODIMP CTSFEditWnd::RequestSupportedAttrs( DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs) { OutputDebugString(TEXT("CTSFEditWnd::RequestSupportedAttrs \n")); _ClearRequestedAttributes(); int i; for(i = 0; i < NUM_SUPPORTED_ATTRS; i++) { ULONG x; for(x = 0; x < cFilterAttrs; x++) { if(IsEqualGUID(*m_rgAttributes[i].attrid, paFilterAttrs[x])) { m_rgAttributes[i].dwFlags = ATTR_FLAG_REQUESTED; if(dwFlags & TS_ATTR_FIND_WANT_VALUE) { m_rgAttributes[i].dwFlags |= ATTR_FLAG_DEFAULT; } else { //just copy the default value into the regular value VariantCopy(&m_rgAttributes[i].varValue, &m_rgAttributes[i].varDefaultValue); } } } } return S_OK; } /************************************************************************** CTSFEditWnd::RequestAttrsAtPosition() **************************************************************************/ STDMETHODIMP CTSFEditWnd::RequestAttrsAtPosition( LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags) { OutputDebugString(TEXT("CTSFEditWnd::RequestAttrsAtPosition \n")); int cch = GetWindowTextLength(m_hwndEdit); if(acpPos < 0 || acpPos > cch) { return TS_E_INVALIDPOS; } _ClearRequestedAttributes(); /* This app doesn't maintain per-character attributes, so just return the default attributes. */ int i; for(i = 0; i < NUM_SUPPORTED_ATTRS; i++) { ULONG x; for(x = 0; x < cFilterAttrs; x++) { if(IsEqualGUID(*m_rgAttributes[i].attrid, paFilterAttrs[x])) { m_rgAttributes[i].dwFlags = ATTR_FLAG_REQUESTED; if(dwFlags & TS_ATTR_FIND_WANT_VALUE) { m_rgAttributes[i].dwFlags |= ATTR_FLAG_DEFAULT; } else { //just copy the default value into the regular value VariantCopy(&m_rgAttributes[i].varValue, &m_rgAttributes[i].varDefaultValue); } } } } return S_OK; } /************************************************************************** CTSFEditWnd::RequestAttrsTransitioningAtPosition() **************************************************************************/ STDMETHODIMP CTSFEditWnd::RequestAttrsTransitioningAtPosition( LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags) { OutputDebugString(TEXT("CTSFEditWnd::RequestAttrsTransitioningAtPosition \n")); return E_NOTIMPL; } /************************************************************************** CTSFEditWnd::FindNextAttrTransition() **************************************************************************/ STDMETHODIMP CTSFEditWnd::FindNextAttrTransition( LONG acpStart, LONG acpHalt, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags, LONG *pacpNext, BOOL *pfFound, LONG *plFoundOffset) { OutputDebugString(TEXT("CTSFEditWnd::FindNextAttrTransition \n")); return E_NOTIMPL; } /************************************************************************** CTSFEditWnd::RetrieveRequestedAttrs() **************************************************************************/ STDMETHODIMP CTSFEditWnd::RetrieveRequestedAttrs( ULONG ulCount, TS_ATTRVAL *paAttrVals, ULONG *pcFetched) { OutputDebugString(TEXT("CTSFEditWnd::RetrieveRequestedAttrs \n")); ULONG uFetched = 0; int i; for(i = 0; i < NUM_SUPPORTED_ATTRS && ulCount; i++) { if(m_rgAttributes[i].dwFlags & ATTR_FLAG_REQUESTED) { paAttrVals->varValue.vt = VT_EMPTY; //copy the attribute ID CopyMemory(&paAttrVals->idAttr, m_rgAttributes[i].attrid, sizeof(GUID)); //this app doesn't support overlapped attributes paAttrVals->dwOverlapId = 0; if (m_rgAttributes[i].dwFlags & ATTR_FLAG_DEFAULT) { VariantCopy(&paAttrVals->varValue, &m_rgAttributes[i].varDefaultValue); } else { VariantCopy(&paAttrVals->varValue, &m_rgAttributes[i].varValue); } paAttrVals++; uFetched++; ulCount--; //remove the item from the requested state VariantClear(&m_rgAttributes[i].varValue); m_rgAttributes[i].dwFlags = ATTR_FLAG_NONE; } } *pcFetched = uFetched; return S_OK; } /************************************************************************** CTSFEditWnd::GetEndACP() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetEndACP(LONG *pacp) { OutputDebugString(TEXT("CTSFEditWnd::GetEndACP \n")); //does the caller have a lock if(!_IsLocked(TS_LF_READWRITE)) { //the caller doesn't have a lock return TS_E_NOLOCK; } if(NULL == pacp) { return E_INVALIDARG; } _GetCurrentSelection(); *pacp = m_acpEnd; return S_OK; } /************************************************************************** CTSFEditWnd::GetActiveView() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetActiveView(TsViewCookie *pvcView) { OutputDebugString(TEXT("CTSFEditWnd::GetActiveView \n")); //this app only supports one view, so this can be constant *pvcView = EDIT_VIEW_COOKIE; return S_OK; } /************************************************************************** CTSFEditWnd::GetACPFromPoint() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetACPFromPoint( TsViewCookie vcView, const POINT *pt, DWORD dwFlags, LONG *pacp) { OutputDebugString(TEXT("CTSFEditWnd::GetACPFromPoint \n")); return E_NOTIMPL; } /************************************************************************** CTSFEditWnd::GetTextExt() If the text spans multiple lines, the result is the rectangle that contains all of the requested characters. **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetTextExt( TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped) { OutputDebugString(TEXT("CTSFEditWnd::GetTextExt \n")); if(NULL == prc || NULL == pfClipped) { return E_INVALIDARG; } *pfClipped = FALSE; ZeroMemory(prc, sizeof(RECT)); if(EDIT_VIEW_COOKIE != vcView) { return E_INVALIDARG; } //does the caller have a lock if(!_IsLocked(TS_LF_READ)) { //the caller doesn't have a lock return TS_E_NOLOCK; } //is this an empty request? if(acpStart == acpEnd) { return E_INVALIDARG; } LONG lTextLength; LONG lTemp; RECT rc; DWORD dwStart; DWORD dwEnd; HDC hdc; HFONT hfont; TEXTMETRIC tm; LONG lLineHeight; LPWSTR pwszText; HRESULT hr; hr = _GetText(&pwszText); if(FAILED(hr)) { return hr; } lTextLength = (LONG)SendMessage(m_hwndEdit, WM_GETTEXTLENGTH, 0, 0); //are the start and end reversed? if(acpStart > acpEnd) { lTemp = acpStart; acpStart = acpEnd; acpEnd = lTemp; } //request to the end of the text? if(-1 == acpEnd) { acpEnd = lTextLength - 1; } hdc = GetDC(m_hwndEdit); hfont = (HFONT)SendMessage(m_hwndEdit, WM_GETFONT, 0, 0); hfont = (HFONT)SelectObject(hdc, hfont); //get the position of the start character dwStart = (DWORD)SendMessage(m_hwndEdit, EM_POSFROMCHAR, acpStart, 0); rc.left = LOWORD(dwStart); rc.top = HIWORD(dwStart); //get the position of the last character /* The character offset passed to this method is inclusive. For example, if the first character is being requested, acpStart will be 0 and acpEnd will be 1. If the last character is requested, acpEnd will not equal a valid character, so EM_POSFROMCHAR fails. If the next character is on another line, EM_POSFROMCHAR won't return a valid value. To work around this, get the position of the beginning of the end character, calculate the width of the end character and add the width to the rectangle. */ acpEnd--; dwEnd = (DWORD)SendMessage(m_hwndEdit, EM_POSFROMCHAR, acpEnd, 0); //calculate the width of the last character SIZE size; GetTextExtentPoint32(hdc, pwszText + acpEnd, 1, &size); rc.right = LOWORD(dwEnd) + size.cx; rc.bottom = HIWORD(dwEnd); //calculate the line height GetTextMetrics(hdc, &tm); lLineHeight = tm.tmHeight; SelectObject(hdc, hfont); ReleaseDC(m_hwndEdit, hdc); /* If the text range spans multiple lines, expand the rectangle to include all of the requested text. */ if(rc.bottom > rc.top) { DWORD dwMargins; RECT rcEdit; GetClientRect(m_hwndEdit, &rcEdit); dwMargins = (DWORD)SendMessage(m_hwndEdit, EM_GETMARGINS, 0, 0); //set the left point of the rectangle to the left margin of the edit control rc.left = LOWORD(dwMargins); //set the right member to the width of the edit control less both the right margin rc.right = rc.right - HIWORD(dwMargins); } //add the line height to the bottom of the rectangle rc.bottom += lLineHeight; *prc = rc; //if any part of the text rectangle is not visible, set *pfClipped to TRUE GetClientRect(m_hwndEdit, &rc); if( (prc->left < rc.left) || (prc->top < rc.top) || (prc->right > rc.right) || (prc->bottom > rc.bottom)) { *pfClipped = TRUE; } //convert the rectangle to screen coordinates MapWindowPoints(m_hwndEdit, NULL, (LPPOINT)prc, 2); GlobalFree(pwszText); return S_OK; } /************************************************************************** CTSFEditWnd::GetScreenExt() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetScreenExt(TsViewCookie vcView, RECT *prc) { OutputDebugString(TEXT("CTSFEditWnd::GetScreenExt \n")); if(NULL == prc) { return E_INVALIDARG; } ZeroMemory(prc, sizeof(RECT)); if(EDIT_VIEW_COOKIE != vcView) { return E_INVALIDARG; } //no lock is necessary for this method. GetClientRect(m_hwndEdit, prc); MapWindowPoints(m_hwndEdit, NULL, (LPPOINT)prc, 2); return S_OK; } /************************************************************************** CTSFEditWnd::GetWnd() **************************************************************************/ STDMETHODIMP CTSFEditWnd::GetWnd(TsViewCookie vcView, HWND *phwnd) { OutputDebugString(TEXT("CTSFEditWnd::GetWnd \n")); if(EDIT_VIEW_COOKIE == vcView) { *phwnd = _GetWindow(); return S_OK; } return E_INVALIDARG; } /************************************************************************** CTSFEditWnd::InsertTextAtSelection() **************************************************************************/ STDMETHODIMP CTSFEditWnd::InsertTextAtSelection( DWORD dwFlags, const WCHAR *pwszText, ULONG cch, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { OutputDebugString(TEXT("CTSFEditWnd::InsertTextAtSelection \n")); LONG lTemp; //does the caller have a lock if(!_IsLocked(TS_LF_READWRITE)) { //the caller doesn't have a lock return TS_E_NOLOCK; } //verify pwszText if(NULL == pwszText) { return E_INVALIDARG; } //verify pacpStart if(NULL == pacpStart) { pacpStart = &lTemp; } //verify pacpEnd if(NULL == pacpEnd) { pacpEnd = &lTemp; } LONG acpStart; LONG acpOldEnd; LONG acpNewEnd; _GetCurrentSelection(); acpOldEnd = m_acpEnd; _TestInsert(m_acpStart, m_acpEnd, cch, &acpStart, &acpNewEnd); if(dwFlags & TS_IAS_QUERYONLY) { *pacpStart = acpStart; *pacpEnd = acpOldEnd; return S_OK; } LPWSTR pwszCopy; pwszCopy = (LPWSTR)GlobalAlloc(GMEM_FIXED, (cch + 1) * sizeof(WCHAR)); if(NULL == pwszCopy) { return E_OUTOFMEMORY; } //pwszText will most likely not be NULL terminated CopyMemory(pwszCopy, pwszText, cch * sizeof(WCHAR)); pwszCopy[cch] = 0; //don't notify TSF of text and selection changes when in response to a TSF action m_fNotify = FALSE; //insert the text ::SendMessage(m_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)pwszCopy); //set the selection ::SendMessage(m_hwndEdit, EM_SETSEL, acpStart, acpNewEnd); m_fNotify = TRUE; _GetCurrentSelection(); if(!(dwFlags & TS_IAS_NOQUERY)) { *pacpStart = acpStart; *pacpEnd = acpNewEnd; } //set the TS_TEXTCHANGE members pChange->acpStart = acpStart; pChange->acpOldEnd = acpOldEnd; pChange->acpNewEnd = acpNewEnd; GlobalFree(pwszCopy); //defer the layout change notification until the document is unlocked m_fLayoutChanged = TRUE; return S_OK; } /************************************************************************** CTSFEditWnd::InsertEmbeddedAtSelection() **************************************************************************/ STDMETHODIMP CTSFEditWnd::InsertEmbeddedAtSelection( DWORD dwFlags, IDataObject *pDataObject, LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange) { OutputDebugString(TEXT("CTSFEditWnd::InsertEmbeddedAtSelection \n")); return E_NOTIMPL; }