2025-11-28 00:35:46 +09:00

1370 lines
37 KiB
C++

/**************************************************************************
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 <tsattrs.h>
/**************************************************************************
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 <B><I>formatting</I></B>."
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; //<B><I>
prgRunInfo[1].uCount = 6;
prgRunInfo[2].type = TS_RT_PLAIN; //"formatting"
prgRunInfo[2].uCount = 10;
prgRunInfo[3].type = TS_RT_HIDDEN; //</B></I>
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;
}