mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2308 lines
71 KiB
2308 lines
71 KiB
// DocWrapImpl.cpp : Implementation of CDocWrap
|
|
#include "stdafx.h"
|
|
#include "MSAAText.h"
|
|
#include "DocWrap.h"
|
|
|
|
#include <list_dl.h>
|
|
#include "VersionInfo.h"
|
|
|
|
#include <msctf.h>
|
|
#include <msctfp.h>
|
|
#define INITGUID
|
|
#include <msctfx.h>
|
|
|
|
#include <tStr.h>
|
|
|
|
#include <fwd_macros.h> // currently in the DocModel\inc dir... adjust appropriately if porting...
|
|
|
|
|
|
/*
|
|
* IDoc - the variant of the ITextStoreACP we're using (ACP/Anchor)
|
|
* ISink - the corresponding Sink interface (ACP/Anchor)
|
|
*
|
|
* ICicDoc - the Cicero doc interface, which extends IDoc
|
|
* ICicSink - the Cicero sink interface, which extends ISink
|
|
*
|
|
* CDocWrap - the document wrapper class, implements ICicDoc (which includes IDoc)
|
|
* CSinkWrap - the sink wrapper class, implements ICicSink (which includes ISink)
|
|
*
|
|
*/
|
|
|
|
|
|
class BasicDocTraitsAnchor
|
|
{
|
|
public:
|
|
typedef struct ITextStoreAnchor IDoc;
|
|
typedef struct ITextStoreAnchorSink ISink;
|
|
|
|
typedef struct ITextStoreAnchor ICicDoc;
|
|
typedef struct ITextStoreAnchorServices ICicSink;
|
|
|
|
typedef class CDocWrapAnchor CDocWrap;
|
|
typedef class CSinkWrapAnchor CSinkWrap;
|
|
|
|
typedef struct ITextStoreAnchorEx IDocEx;
|
|
typedef struct ITextStoreSinkAnchorEx ISinkEx;
|
|
|
|
typedef IAnchor * PosType;
|
|
};
|
|
|
|
|
|
class BasicDocTraitsACP
|
|
{
|
|
public:
|
|
typedef struct ITextStoreACP IDoc;
|
|
typedef struct ITextStoreACPSink ISink;
|
|
|
|
typedef struct ITextStoreACP ICicDoc;
|
|
typedef struct ITextStoreACPServices ICicSink;
|
|
|
|
typedef class CDocWrapACP CDocWrap;
|
|
typedef class CSinkWrapACP CSinkWrap;
|
|
|
|
|
|
typedef struct ITextStoreACPEx IDocEx;
|
|
typedef struct ITextStoreACPSinkEx ISinkEx;
|
|
|
|
typedef LONG PosType;
|
|
};
|
|
|
|
|
|
// DocWrapExcept contains exception wrapper classes for
|
|
// some of the above interfaces...
|
|
#include "DocWrapExcept.h"
|
|
|
|
|
|
// Add the appropriate exception wrappers to the set of
|
|
// doc traits.
|
|
|
|
class DocTraitsAnchor: public BasicDocTraitsAnchor
|
|
{
|
|
public:
|
|
|
|
typedef SEHWrapPtr_TextStoreAnchor IDocSEHWrap;
|
|
};
|
|
|
|
|
|
class DocTraitsACP: public BasicDocTraitsACP
|
|
{
|
|
public:
|
|
|
|
typedef SEHWrapPtr_TextStoreACP IDocSEHWrap;
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// Create an instance of a local non-externally-createable class.
|
|
// Just a wrapper for CComObject::CreateInstance, but also AddRef's so that
|
|
// the returned object has a ref of 1.
|
|
template< class C >
|
|
HRESULT CreateLocalInstance( C ** p )
|
|
{
|
|
CComObject< C > * p2 = NULL;
|
|
|
|
HRESULT hr = CComObject< C >::CreateInstance( & p2 );
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TraceErrorHR( hr, TEXT("CreateLocalInstance") );
|
|
return hr;
|
|
}
|
|
|
|
if( p2 == NULL || hr != S_OK )
|
|
{
|
|
TraceErrorHR( hr, TEXT("CreateLocalInstance returned NULL") );
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
p2->AddRef();
|
|
*p = p2;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
// Check hr and condition - return appropriate error if not S_OK and ! cond.
|
|
#define CHECK_HR_RETURN( hr, cond ) /**/ \
|
|
if( (hr) != S_OK ) \
|
|
return FAILED( (hr) ) ? (hr) : E_UNEXPECTED; \
|
|
if( (hr) == S_OK && ! ( cond ) ) \
|
|
return E_UNEXPECTED;
|
|
|
|
|
|
|
|
template < class T >
|
|
inline
|
|
void SafeReleaseClear( T * & ptr )
|
|
{
|
|
if( ptr )
|
|
{
|
|
ptr->Release();
|
|
ptr = NULL;
|
|
}
|
|
}
|
|
|
|
class CPrivateAddRef
|
|
{
|
|
public:
|
|
CPrivateAddRef( long &rc ) : m_refcount( rc ) { m_refcount++; }
|
|
~CPrivateAddRef() { m_refcount--; }
|
|
private:
|
|
long &m_refcount;
|
|
};
|
|
|
|
|
|
class _declspec(uuid("{54D5D291-D8D7-4870-ADE1-331D86FD9430}")) IWrapMgr: public IUnknown
|
|
{
|
|
public:
|
|
virtual void STDMETHODCALLTYPE SetDoc( IUnknown * pDoc ) = 0;
|
|
virtual HRESULT STDMETHODCALLTYPE CreateWrappedDoc( IUnknown ** ppDoc ) = 0;
|
|
};
|
|
|
|
|
|
|
|
|
|
template < class DocTraits >
|
|
class ATL_NO_VTABLE CWrapMgr :
|
|
public CComObjectRootEx< CComSingleThreadModel >,
|
|
public IWrapMgr
|
|
{
|
|
public:
|
|
|
|
DECLARE_PROTECT_FINAL_CONSTRUCT()
|
|
|
|
BEGIN_COM_MAP( CWrapMgr< DocTraits > )
|
|
COM_INTERFACE_ENTRY( IWrapMgr )
|
|
END_COM_MAP()
|
|
|
|
private:
|
|
|
|
// Ptr to the original document.
|
|
// Each DocWrapper also has a copy of this ptr - but only we have a refcount on it.
|
|
// DocTraits::IDoc * m_pDoc;
|
|
DocTraits::IDocSEHWrap m_pDoc;
|
|
|
|
// Used to remember who requested a sync lock...
|
|
DocTraits::CDocWrap * m_pLockRequestedBy;
|
|
|
|
BOOL m_fInSyncLockRequest;
|
|
|
|
// Used to remember currently requested lock (ro vs rw)
|
|
DWORD m_dwPendingLock;
|
|
|
|
// Our sink - called by the doc
|
|
DocTraits::CSinkWrap * m_pSinkWrap;
|
|
|
|
// Current sink event mask - all client sink masks or'd together.
|
|
DWORD m_dwSinkMask;
|
|
BOOL m_fSinkActive;
|
|
|
|
public:
|
|
|
|
|
|
// List of DocWraps (one per client)
|
|
List_dl< DocTraits::CDocWrap > m_DocWraps;
|
|
|
|
LONG m_lIterationRefCount;
|
|
|
|
//
|
|
// Ctor, Dtor...
|
|
//
|
|
|
|
CWrapMgr()
|
|
: m_pDoc( NULL ),
|
|
m_pSinkWrap( NULL ),
|
|
m_dwSinkMask( 0 ),
|
|
m_fSinkActive( FALSE ),
|
|
m_pLockRequestedBy( NULL ),
|
|
m_fInSyncLockRequest( FALSE ),
|
|
m_dwPendingLock( 0 ),
|
|
m_lIterationRefCount( 0 )
|
|
|
|
{
|
|
// Done.
|
|
TraceInfo( TEXT("WrapMgr ctor") );
|
|
}
|
|
|
|
|
|
|
|
~CWrapMgr()
|
|
{
|
|
TraceInfo( TEXT("WrapMgr dtor") );
|
|
|
|
AssertMsg( m_pDoc, TEXT("CWrapMgr::SetDoc never called?") );
|
|
|
|
m_pDoc->Release();
|
|
|
|
Assert( m_DocWraps.empty() );
|
|
Assert( m_pSinkWrap == NULL );
|
|
Assert( m_dwSinkMask == 0 );
|
|
}
|
|
|
|
|
|
//
|
|
// IWrapMgr interface - used by the DocWrap holder to give us a doc and ask for wrappers for it...
|
|
//
|
|
|
|
void STDMETHODCALLTYPE SetDoc( IUnknown * pDoc )
|
|
{
|
|
_SetDoc( static_cast< DocTraits::IDoc * >( pDoc ) );
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE CreateWrappedDoc( IUnknown ** ppDoc )
|
|
{
|
|
return _CreateWrappedDoc( reinterpret_cast< DocTraits::IDoc ** >( ppDoc ) );
|
|
}
|
|
|
|
void RemoveDeadDocs()
|
|
{
|
|
for( Iter_dl < DocTraits::CDocWrap > i ( m_DocWraps ) ; ! i.AtEnd() ; )
|
|
{
|
|
Iter_dl < DocTraits::CDocWrap > dead = i;
|
|
|
|
i++;
|
|
|
|
if (dead->m_bUnadvised && m_lIterationRefCount == 0)
|
|
m_DocWraps.remove( dead );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Called by DocWrap, to tell us when it's been released, and to get us (the wrapper manager)
|
|
// to handle a call that affects other wrappers on the same doc - advise/unadvise and lock calls.
|
|
//
|
|
|
|
void DocWrap_NotifyDisconnect( DocTraits::CDocWrap * pFrom )
|
|
{
|
|
// A DocWrap has been released by a client and is going away...
|
|
|
|
// TODO - if using locks, check pFrom for lock, if it has it, release, broadcast relase.
|
|
// - how can this scenario occur?
|
|
// - doc is released in callback. Weird, but valid?
|
|
|
|
}
|
|
|
|
HRESULT DocWrap_HandleRequestLock( DocTraits::CDocWrap * pFrom, DWORD dwLockFlags, HRESULT * phrSession )
|
|
{
|
|
// Other clients may request a lock while it's being held by someone else, but the
|
|
// current holder should not request a lock.
|
|
// (This can happen when client1 is holding a lock, and issues an editing operation.
|
|
// The wrapper broadcasts a OnTextChange event to other clients, and they may request locks.)
|
|
AssertMsg( m_pLockRequestedBy != pFrom, TEXT("Lock owner re-request held lock? (Reentrancy?)") );
|
|
|
|
if( dwLockFlags == 0 ||
|
|
dwLockFlags & ~ ( TS_LF_SYNC | TS_LF_READ | TS_LF_READWRITE ) ) // check that only these bits present
|
|
{
|
|
AssertMsg( FALSE, TEXT("Bad lock flags") );
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
|
|
if( dwLockFlags & TS_LF_SYNC )
|
|
{
|
|
// Can't process a SYNC call while someone else is holding the lock...
|
|
if( m_pLockRequestedBy )
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Sync lock - can just pass through - need to set up m_pLockRequestedBy so that the
|
|
// sink can pass it onto the correct client.
|
|
m_pLockRequestedBy = pFrom;
|
|
m_fInSyncLockRequest = TRUE;
|
|
HRESULT hr = m_pDoc->RequestLock( dwLockFlags, phrSession );
|
|
m_fInSyncLockRequest = FALSE;
|
|
m_pLockRequestedBy = NULL;
|
|
return hr;
|
|
} // sync lock
|
|
else
|
|
{
|
|
// if async lock, update/upgrade the wrap's pending lock mask if necessary...
|
|
// This test (which assumes that dwLockFlags != 0) upgrades to r/w if necessary.
|
|
|
|
// TODO - should this update only be done conditionally if RequestLock succeeds?
|
|
// (or no upgrade necessary?)
|
|
Assert( dwLockFlags != 0 );
|
|
if( pFrom->m_dwPendingLock != TS_LF_READWRITE )
|
|
pFrom->m_dwPendingLock = dwLockFlags;
|
|
|
|
if( m_pLockRequestedBy )
|
|
{
|
|
// someone else is currently holding the lock.
|
|
// All we have to do is update the doc's PendingLock flags (see above) - they will
|
|
// be picked up and handled by the loop in Handle_OnLockGranted when the current
|
|
// lock holder returns.
|
|
// Nothing else to do here.
|
|
|
|
// But send it on the doc anyway if it's the same person
|
|
if ( m_pLockRequestedBy == pFrom )
|
|
{
|
|
return m_pDoc->RequestLock( m_dwPendingLock, phrSession );
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
// We don't have a lock yet.
|
|
|
|
// Check our current request, if any, and if necessary, request a write,
|
|
// even if we've already requested a read.
|
|
|
|
|
|
// Update combined mask if necessary...
|
|
DWORD dwNewLock = m_dwPendingLock;
|
|
// Calculate required lock...
|
|
if( dwNewLock != TS_LF_READWRITE )
|
|
dwNewLock = dwLockFlags;
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// Do we need to request a new lock/upgrade?
|
|
if( dwNewLock != m_dwPendingLock )
|
|
{
|
|
// May get an immediate response even for an async request, so need to set this up
|
|
// event if not a sync request...
|
|
m_pLockRequestedBy = pFrom;
|
|
|
|
DWORD OldPendingLock = m_dwPendingLock;
|
|
m_dwPendingLock = dwNewLock;
|
|
|
|
HRESULT hrOut = E_FAIL;
|
|
hr = m_pDoc->RequestLock( m_dwPendingLock, & hrOut );
|
|
|
|
m_pLockRequestedBy = NULL;
|
|
|
|
if( hr != S_OK )
|
|
{
|
|
// After all that, the request failed...
|
|
// Revert to previous pending lock...
|
|
m_dwPendingLock = OldPendingLock;
|
|
|
|
// fall out...
|
|
}
|
|
else
|
|
{
|
|
// Regardless of fail/success, copy the outgoing hr.
|
|
// Clearing of the pending flags is done in OnLockgranted, not here.
|
|
|
|
// Shouldn't get this for an async request...
|
|
Assert( hrOut != TS_E_SYNCHRONOUS );
|
|
|
|
*phrSession = hrOut;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Our existing pending lock covers this request - success.
|
|
*phrSession = TS_S_ASYNC;
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
} // async lock
|
|
}
|
|
|
|
|
|
HRESULT DocWrap_UpdateSubscription()
|
|
{
|
|
Assert( ( m_dwSinkMask & ~ TS_AS_ALL_SINKS ) == 0 );
|
|
// If there are no active sinks, then SinkMask should be 0.
|
|
Assert( m_fSinkActive || m_dwSinkMask == 0 );
|
|
|
|
// Work out required mask - by or'ing masks of all doc's sinks...
|
|
DWORD NewMask = 0;
|
|
BOOL NewfSinkActive = FALSE;
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( m_DocWraps ) ; ! i.AtEnd() ; i++ )
|
|
{
|
|
if( i->m_Sink.m_pSink != NULL )
|
|
{
|
|
Assert( ( i->m_Sink.m_dwMask & ~ TS_AS_ALL_SINKS ) == 0 );
|
|
NewMask |= i->m_Sink.m_dwMask;
|
|
NewfSinkActive = TRUE;
|
|
}
|
|
}
|
|
|
|
Assert( ( NewMask & ~ TS_AS_ALL_SINKS ) == 0 );
|
|
// If there are no active sinks, then NewMask should be 0.
|
|
Assert( NewfSinkActive || NewMask == 0 );
|
|
|
|
|
|
// Tricky bit:
|
|
// NewMask==0 does not mean that there's no sinks - some may have
|
|
// dwMask == 0 to receive LockGranted calls.
|
|
// So to check if the status has changed, we need to take whether
|
|
// there are any active sinks (not just the masks) into account.
|
|
|
|
if( NewfSinkActive == m_fSinkActive && NewMask == m_dwSinkMask )
|
|
{
|
|
// No change - nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
// Three possibilities:
|
|
// (a) free to unregister,
|
|
// (b) need to register
|
|
// (c) need to update existing registration.
|
|
// We handle (b) and (c) as the same case.
|
|
|
|
if( ! NewfSinkActive )
|
|
{
|
|
// No sinks - can unregister...
|
|
|
|
m_fSinkActive = FALSE;
|
|
m_dwSinkMask = 0;
|
|
|
|
if( ! m_pSinkWrap )
|
|
{
|
|
Assert( FALSE );
|
|
// Odd - where did our sink wrap go - we didn't unregister it yet, so it should
|
|
// still be around...
|
|
// (Possible that server pre-emptively released us - so not an error.)
|
|
}
|
|
else
|
|
{
|
|
// Don't do anything else with m_pSinkWrap - the doc should release it.
|
|
HRESULT hr = m_pDoc->UnadviseSink( static_cast< DocTraits::ICicSink * >( m_pSinkWrap ) );
|
|
// shouldn't fail...
|
|
Assert( hr == S_OK );
|
|
|
|
// Doc should have released the sink (which would have called us back to NULL out
|
|
// m_pSinkWrap...)
|
|
Assert( m_pSinkWrap == NULL );
|
|
}
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
// Update existing / add new...
|
|
|
|
// If we already had an existing sink, there should be a sinkwrap...
|
|
// (unless doc let it go prematurely...)
|
|
Assert( ! m_fSinkActive || m_pSinkWrap );
|
|
|
|
BOOL NeedRelease = FALSE;
|
|
if( ! m_pSinkWrap )
|
|
{
|
|
HRESULT hr = CreateLocalInstance( & m_pSinkWrap );
|
|
if( hr != S_OK )
|
|
return hr;
|
|
|
|
m_pSinkWrap->Init( this, & m_DocWraps );
|
|
|
|
// After doing the Advise, release our ref on the sink, so that only
|
|
// the doc holds a ref and controls its liftetime.
|
|
// We still keep a pointer, but it's a "weak reference" - if the
|
|
// sink goes away (because the doc releases its reference), the sink
|
|
// notifies us so we can NULL-out the ptr. (see SinkWrap_NotifyDisconnect)
|
|
NeedRelease = TRUE;
|
|
}
|
|
|
|
|
|
|
|
// Always try advising with the Cicero sink first - if that
|
|
// doesn't work, fall back to the ITextStoreACP one.
|
|
//
|
|
// (Use of static_cast is necessary here to avoid ambiguity over
|
|
// which IUnknown we convert to - the ICicSink one, or the
|
|
// IServiceProvider one. We want the ICicSink one, to match the
|
|
// IID passed in.)
|
|
|
|
HRESULT hr = m_pDoc->AdviseSink( __uuidof( DocTraits::ICicSink ), static_cast< DocTraits::ICicSink * >( m_pSinkWrap ), NewMask );
|
|
if( hr != S_OK )
|
|
{
|
|
hr = m_pDoc->AdviseSink( __uuidof( DocTraits::ISink ), static_cast< DocTraits::ISink * >( m_pSinkWrap ), NewMask );
|
|
}
|
|
|
|
if( NeedRelease )
|
|
{
|
|
m_pSinkWrap->Release();
|
|
}
|
|
|
|
if( hr == S_OK )
|
|
{
|
|
m_fSinkActive = TRUE;
|
|
m_dwSinkMask = NewMask;
|
|
}
|
|
else
|
|
{
|
|
AssertMsg( FALSE, TEXT("AdviseSink failed") );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
|
|
void DocWrap_NotifyRevoke()
|
|
{
|
|
|
|
// Send OnDisconnect to any SinkEx sinks,
|
|
CPrivateAddRef MyAddRef(m_lIterationRefCount);
|
|
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( m_DocWraps ) ; ! i.AtEnd() ; i++ )
|
|
{
|
|
// Is this the SinkExt sink?
|
|
if( i->m_Sink.m_iid == __uuidof( DocTraits::ISinkEx ) )
|
|
{
|
|
DocTraits::ISinkEx * pSink = static_cast< DocTraits::ISinkEx * >( i->m_Sink.m_pSink );
|
|
|
|
if ( pSink )
|
|
pSink->OnDisconnect();
|
|
}
|
|
}
|
|
|
|
for( Iter_dl < DocTraits::CDocWrap > j ( m_DocWraps ) ; ! j.AtEnd() ; )
|
|
{
|
|
Iter_dl < DocTraits::CDocWrap > DocToDelete = j;
|
|
j++;
|
|
|
|
m_DocWraps.remove( DocToDelete );
|
|
DocToDelete->Release();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Called by SinkWrap to let us know when its been released...
|
|
//
|
|
|
|
void SinkWrap_NotifyDisconnect()
|
|
{
|
|
// The sink has been released by the application - it's now deleteing itself,
|
|
// so we NULL-out our weak reference to it. (If we need another one in future,
|
|
// we'll create a new one.)
|
|
|
|
// Clear our sinks to reflect this...
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( m_DocWraps ) ; ! i.AtEnd() ; i++ )
|
|
{
|
|
i->m_Sink.ClearAndRelease();
|
|
}
|
|
|
|
m_pSinkWrap = NULL;
|
|
}
|
|
|
|
HRESULT SinkWrap_HandleOnLockGranted ( DWORD dwLockFlags )
|
|
{
|
|
// Is this servicing a sync or async request?
|
|
Assert( ( dwLockFlags & ~ ( TS_LF_SYNC | TS_LF_READ | TS_LF_READWRITE ) ) == 0 );
|
|
|
|
CPrivateAddRef MyAddRef(m_lIterationRefCount);
|
|
|
|
if( m_fInSyncLockRequest )
|
|
{
|
|
// Sync lock - just pass it straight through to whoever requested it...
|
|
Assert( dwLockFlags & TS_LF_SYNC );
|
|
Assert( m_pLockRequestedBy && m_pLockRequestedBy->m_Sink.m_pSink );
|
|
|
|
if( ! m_pLockRequestedBy || ! m_pLockRequestedBy->m_Sink.m_pSink )
|
|
return E_UNEXPECTED;
|
|
|
|
DocTraits::ISink * pSink = static_cast< DocTraits::ISink * > ( m_pLockRequestedBy->m_Sink.m_pSink );
|
|
|
|
HRESULT hr = pSink->OnLockGranted( dwLockFlags );
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
// Async lock - hand it out to whoever wanted it...
|
|
Assert( ! ( dwLockFlags & TS_LF_SYNC ) );
|
|
|
|
// If we're waiting for a r/w lock, the lock granted should be r/w too...
|
|
Assert( ! ( m_dwPendingLock & TS_LF_READWRITE ) || ( dwLockFlags & TS_LF_READWRITE ) );
|
|
|
|
|
|
// Clear the pending lock, since we're now servicing them...
|
|
m_dwPendingLock = 0;
|
|
|
|
// Keep looking through the docs, servicing locks. We loop because some docs may
|
|
// request locks while another holds the lock, so we have to come back an service them.
|
|
// When we loop through all docs without seeing any locks, then we know all locks
|
|
// have been serviced.
|
|
//
|
|
// If this is a read lock, then we can only service read locks now - we'll have to
|
|
// ask for a separate write lock later if we need one.
|
|
BOOL fNeedWriteLock = FALSE;
|
|
for( ; ; )
|
|
{
|
|
BOOL fWorkDone = FALSE;
|
|
|
|
// Forward to all clients who had requested a lock...
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( m_DocWraps ) ; ! i.AtEnd() ; i++ )
|
|
{
|
|
// Is the mask we've been granted sufficient for the client's request (if any)?
|
|
DWORD ReqdLock = i->m_dwPendingLock;
|
|
if( ReqdLock )
|
|
{
|
|
if( ( ReqdLock | dwLockFlags ) == dwLockFlags )
|
|
{
|
|
// tell the doc wrapper that it is in OnLockGranted and what kind of lock it has
|
|
i->m_dwGrantedLock = ReqdLock;
|
|
|
|
// Clear the mask...
|
|
i->m_dwPendingLock = 0;
|
|
|
|
DocTraits::ISink * pSink = static_cast< DocTraits::ISink * > ( i->m_Sink.m_pSink );
|
|
|
|
// How about...
|
|
// (a) also call Next before callback.
|
|
// (b) store current value of iter in mgr - in doc's release, it can check, and bump if necessary;
|
|
pSink->OnLockGranted( ReqdLock );
|
|
|
|
// tell the doc wrapper that it is no longer in OnLockGranted
|
|
i->m_dwGrantedLock = 0;
|
|
|
|
fWorkDone = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// This client wants a write lock, but we've only got a read lock...
|
|
fNeedWriteLock = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't need to handle any lock requests the last time around the loop,
|
|
// then our work is done. (If we did handle any lock requests, we should go back
|
|
// and re-check all docs, in case one of them requested a lock while one of the
|
|
// locks we serviced was holding it.)
|
|
if( ! fWorkDone )
|
|
break;
|
|
}
|
|
|
|
|
|
if( fNeedWriteLock )
|
|
{
|
|
// TODO - need to find a way to handle this. Can we call the doc's RequestLock again now?
|
|
AssertMsg( FALSE, TEXT("Write lock requested while holding read lock - not implemented yet") );
|
|
}
|
|
|
|
// All done!
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
//
|
|
// Internal functions...
|
|
//
|
|
|
|
|
|
void _SetDoc( DocTraits::IDoc * pDoc )
|
|
{
|
|
AssertMsg( ! m_pDoc, TEXT("CWrapMgr::SetDoc should be called once when m_pDoc is NULL") );
|
|
m_pDoc = pDoc;
|
|
m_pDoc->AddRef();
|
|
}
|
|
|
|
|
|
|
|
HRESULT _CreateWrappedDoc( DocTraits::IDoc ** ppDoc )
|
|
{
|
|
*ppDoc = NULL;
|
|
|
|
// Create a doc wrapper...
|
|
DocTraits::CDocWrap * pCDocWrap;
|
|
HRESULT hr = CreateLocalInstance( & pCDocWrap );
|
|
CHECK_HR_RETURN( hr, pCDocWrap != NULL );
|
|
|
|
pCDocWrap->Init( this, m_pDoc );
|
|
|
|
// Add to our list...
|
|
m_DocWraps.AddToHead( pCDocWrap );
|
|
|
|
// And return it...
|
|
*ppDoc = pCDocWrap;
|
|
return S_OK;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct SinkInfo
|
|
{
|
|
IUnknown * m_pSink;
|
|
IID m_iid;
|
|
DWORD m_dwMask;
|
|
IUnknown * m_pCanonicalUnk; // IUnknown used for identity comparisons
|
|
|
|
|
|
|
|
void Validate()
|
|
{
|
|
#ifdef DEBUG
|
|
if( m_pSink )
|
|
{
|
|
Assert( m_pCanonicalUnk != NULL );
|
|
// Check that mask contains only valid bits
|
|
Assert( ( m_dwMask & ~ TS_AS_ALL_SINKS ) == 0 );
|
|
}
|
|
else
|
|
{
|
|
Assert( m_pCanonicalUnk == NULL );
|
|
Assert( m_dwMask == 0 );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
SinkInfo()
|
|
: m_pSink( NULL ),
|
|
m_pCanonicalUnk( NULL ),
|
|
m_dwMask( 0 )
|
|
{
|
|
Validate();
|
|
// Done.
|
|
}
|
|
|
|
~SinkInfo()
|
|
{
|
|
Validate();
|
|
AssertMsg( m_pSink == NULL && m_pCanonicalUnk == NULL, TEXT("Sink not cleared" ) );
|
|
}
|
|
|
|
void Set( IUnknown * pSink, REFIID iid, DWORD dwMask, IUnknown * pCanonicalUnk )
|
|
{
|
|
Validate();
|
|
|
|
AssertMsg( m_pSink == NULL, TEXT("Set() sink that's already in use" ) );
|
|
|
|
m_pSink = pSink;
|
|
m_pSink->AddRef();
|
|
m_iid = iid;
|
|
m_dwMask = dwMask;
|
|
m_pCanonicalUnk = pCanonicalUnk;
|
|
m_pCanonicalUnk->AddRef();
|
|
|
|
Validate();
|
|
}
|
|
|
|
void UpdateMask( DWORD dwMask )
|
|
{
|
|
Validate();
|
|
|
|
AssertMsg( m_pSink != NULL, TEXT("UpdateMask() on empty sink") );
|
|
|
|
m_dwMask = dwMask;
|
|
|
|
Validate();
|
|
}
|
|
|
|
void ClearAndRelease()
|
|
{
|
|
Validate();
|
|
|
|
if( m_pSink )
|
|
{
|
|
m_pSink->Release();
|
|
m_pSink = NULL;
|
|
m_pCanonicalUnk->Release();
|
|
m_pCanonicalUnk = NULL;
|
|
m_dwMask = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fwd. decl for the sink-wrap class, needed since we grant it frienship in the
|
|
// DocWrap class...
|
|
template< class DocTraits >
|
|
class CSinkWrapBase;
|
|
|
|
|
|
|
|
//
|
|
// CDocWrapBase
|
|
//
|
|
// - Base from which Anchor and ACP document wrappers are derived.
|
|
//
|
|
// This class contains ACP/Anchor-neutral wrapping code - anything that is
|
|
// ACP/Anchor-specific is handled in the derived ..ACP or ...Anchor class
|
|
// instead.
|
|
//
|
|
// This class derives from the full Cicero doc interface (DocTraits::ICicDoc -
|
|
// which is a typedef for ITfTextStore[Anchor]), which in turn includes the
|
|
// ITextStoreACP doc interface. Currently the Cicero interface doesn't add any
|
|
// additional methods.
|
|
//
|
|
// This class is also on a list of document wrappers - so we're derived from
|
|
// Link_dl. (The list is managed by the wrapper manager.) The list will be a
|
|
// list of derived classes, so the type of the link is of the derived class
|
|
// (DocTraits::CDocWrap - which is a typedef for CDocWrapACP/Anchor), instead
|
|
// of being based on this class.
|
|
// (At the moment we don't actually use any of the derived-class functionality,
|
|
// but may do so in future.)
|
|
|
|
// {B5DCFDAF-FBAD-4ef6-A5F8-E7CC0833A3B1}
|
|
static const GUID DOCWRAP_IMPLID = { 0xb5dcfdaf, 0xfbad, 0x4ef6, { 0xa5, 0xf8, 0xe7, 0xcc, 0x8, 0x33, 0xa3, 0xb1 } };
|
|
|
|
template< class _DocTraits >
|
|
class ATL_NO_VTABLE CDocWrapBase :
|
|
public CComObjectRootEx< CComSingleThreadModel >,
|
|
public Link_dl< _DocTraits::CDocWrap >,
|
|
public _DocTraits::ICicDoc,
|
|
|
|
public _DocTraits::IDocEx,
|
|
|
|
public IClonableWrapper,
|
|
public IInternalDocWrap,
|
|
public ICoCreateLocally,
|
|
public CVersionInfo,
|
|
public IServiceProvider
|
|
{
|
|
public:
|
|
|
|
// This typedef makes the DocTraits type visible in this and in the
|
|
// Anchor/ACP-specific derived classes. (Otherwise, as a template
|
|
// parameter in this class, it would not be available to them.)
|
|
typedef _DocTraits DocTraits;
|
|
|
|
DECLARE_PROTECT_FINAL_CONSTRUCT()
|
|
|
|
BEGIN_COM_MAP( CDocWrapBase< DocTraits > )
|
|
COM_INTERFACE_ENTRY( DocTraits::IDoc )
|
|
COM_INTERFACE_ENTRY( DocTraits::ICicDoc )
|
|
COM_INTERFACE_ENTRY( DocTraits::IDocEx )
|
|
COM_INTERFACE_ENTRY( IClonableWrapper )
|
|
COM_INTERFACE_ENTRY( IInternalDocWrap )
|
|
COM_INTERFACE_ENTRY( ICoCreateLocally )
|
|
COM_INTERFACE_ENTRY( IVersionInfo )
|
|
COM_INTERFACE_ENTRY( IServiceProvider )
|
|
END_COM_MAP()
|
|
|
|
|
|
|
|
private:
|
|
|
|
// WrapMgr uses the Link_dl base when adding this to its list.
|
|
friend CWrapMgr< DocTraits >;
|
|
|
|
// Used by WrapMgr to track what type of lock was requested.
|
|
DWORD m_dwPendingLock;
|
|
|
|
// Used by WrapMgr to track what type of lock granted.
|
|
DWORD m_dwGrantedLock;
|
|
|
|
// SinkWrapBase - and its derived Anchor/ACP-specific class - uses the list
|
|
// and the members of SinkInfo when broadcasting
|
|
friend CSinkWrapBase< DocTraits >;
|
|
friend DocTraits::CSinkWrap;
|
|
|
|
|
|
protected:
|
|
|
|
// Each doc can have a corresponding sink:
|
|
SinkInfo m_Sink;
|
|
|
|
// Link back to the wrapper manager - so we can tell it when we're going
|
|
// away. We also get it to handle calls which affect other wrappers on the
|
|
// same document - especially sinks and locks.
|
|
CWrapMgr< DocTraits > * m_pMgr;
|
|
|
|
// Used by derived classes to forward calls to doc...
|
|
// DocTraits::IDoc * m_pDoc;
|
|
DocTraits::IDocSEHWrap m_pDoc;
|
|
|
|
// TEMP BUGBUG - used to access the attribute extentions for the moment...
|
|
DocTraits::IDocEx * m_pDocEx;
|
|
|
|
bool m_bUnadvised;
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE VerifyLock( DWORD dwLockFlags)
|
|
{
|
|
IMETHOD( VerifyLock );
|
|
|
|
if ( m_dwGrantedLock )
|
|
{
|
|
// We have a lock, make sure it's the right kind
|
|
if ( (dwLockFlags & TS_LF_READWRITE) == m_dwGrantedLock || (dwLockFlags & TS_LF_READWRITE) == TS_LF_READ )
|
|
return S_OK;
|
|
}
|
|
|
|
TraceDebug( TEXT("Lock rejected") );
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
// This macro just forwards the call directly to the doc...
|
|
#define DocWrap_FORWARD( fname, c, params ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params ) \
|
|
{\
|
|
IMETHOD( fname );\
|
|
return m_pDoc-> fname AS_CALL( c, params ) ;\
|
|
}
|
|
|
|
// This macro just forwards the call directly to the doc after checking for the correct lock
|
|
#define DocWrap_FORWARD_READLOCK( fname, c, params ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params ) \
|
|
{\
|
|
IMETHOD( fname );\
|
|
if ( VerifyLock( TS_LF_READ ) == S_FALSE )\
|
|
return TS_E_NOLOCK;\
|
|
return m_pDoc-> fname AS_CALL( c, params ) ;\
|
|
}
|
|
|
|
|
|
#define DocWrap_FORWARDEXT( fname, c, params ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params ) \
|
|
{\
|
|
IMETHOD( fname );\
|
|
if( ! m_pDocEx )\
|
|
return E_NOTIMPL;\
|
|
return m_pDocEx-> fname AS_CALL( c, params ) ;\
|
|
}
|
|
|
|
|
|
// This slightly more complicated one (!) forwards to the doc,
|
|
// and if the call succeeds, then broadcasts to all sinks except the one
|
|
// for this document.
|
|
// So if one client does a SetText, that SetText goes through, and
|
|
// all other clietns with callbacks for the TS_AS_TEXT_CHANGE event will
|
|
// also get an OnTextChange event.
|
|
#define DocWrap_FORWARD_AND_SINK( fname, c, params, mask, callsink ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params )\
|
|
{\
|
|
IMETHOD( fname );\
|
|
Assert( m_pMgr );\
|
|
if ( VerifyLock( TS_LF_READWRITE ) == S_FALSE )\
|
|
return TS_E_NOLOCK;\
|
|
m_pMgr->RemoveDeadDocs();\
|
|
CPrivateAddRef MyAddRef(m_pMgr->m_lIterationRefCount);\
|
|
HRESULT hr = m_pDoc-> fname AS_CALL( c, params );\
|
|
if( hr != S_OK )\
|
|
{\
|
|
TraceDebugHR( hr, TEXT("failed") );\
|
|
return hr;\
|
|
}\
|
|
for( Iter_dl < DocTraits::CDocWrap > i ( m_pMgr->m_DocWraps ) ; ! i.AtEnd() ; i++ )\
|
|
{\
|
|
DocTraits::ISink * pSink = static_cast< DocTraits::ISink * >( i->m_Sink.m_pSink );\
|
|
DWORD dwMask = i->m_Sink.m_dwMask;\
|
|
DocTraits::CDocWrap * pTheDoc = i;\
|
|
if( pTheDoc != this && pSink && ( dwMask & mask ) )\
|
|
{\
|
|
callsink ;\
|
|
}\
|
|
}\
|
|
return S_OK ;\
|
|
}
|
|
|
|
public:
|
|
|
|
|
|
//
|
|
// Ctor, Dtor, and initialization...
|
|
//
|
|
|
|
CDocWrapBase()
|
|
: m_pDoc( NULL ),
|
|
m_pMgr( NULL ),
|
|
m_dwPendingLock( 0 ),
|
|
m_dwGrantedLock( 0 ),
|
|
m_pDocEx( NULL ),
|
|
m_bUnadvised( false )
|
|
{
|
|
|
|
}
|
|
|
|
~CDocWrapBase()
|
|
{
|
|
AssertMsg( m_pMgr != NULL, TEXT("CDocWrapBase::Init never got called?") );
|
|
|
|
// Clear up sink...
|
|
if( m_Sink.m_pSink )
|
|
{
|
|
m_Sink.ClearAndRelease();
|
|
|
|
// Manager will unadvise, if we were the last to go...
|
|
m_pMgr->DocWrap_UpdateSubscription();
|
|
}
|
|
|
|
m_pMgr->DocWrap_NotifyDisconnect( static_cast< DocTraits::CDocWrap * >( this ) );
|
|
m_pMgr->Release();
|
|
|
|
if( m_pDocEx )
|
|
m_pDocEx->Release();
|
|
}
|
|
|
|
void Init( CWrapMgr< DocTraits > * pMgr, DocTraits::IDoc * pDoc )
|
|
{
|
|
AssertMsg( m_pMgr == NULL, TEXT("CDocWrapBase::Init should only be called once when m_pMgr is NULL") );
|
|
m_pMgr = pMgr;
|
|
m_pMgr->AddRef();
|
|
|
|
m_pDoc = pDoc;
|
|
AddRef(); // Keep our own ref count so it dosn't go away until we remove from the list
|
|
|
|
CVersionInfo::Add( DOCWRAP_IMPLID, 1, 0, L"Microsoft MSAA Wrapper 1.0", L"na", NULL);
|
|
|
|
m_pDoc->QueryInterface( __uuidof( DocTraits::IDocEx ), (void **) & m_pDocEx );
|
|
}
|
|
|
|
|
|
//
|
|
// Implementation of ACP/Anchor-neutral methods...
|
|
// These ones require special handling...
|
|
//
|
|
|
|
HRESULT STDMETHODCALLTYPE AdviseSink( REFIID riid, IUnknown *punk, DWORD dwMask )
|
|
{
|
|
IMETHOD( AdviseSink );
|
|
|
|
Assert( m_pMgr );
|
|
|
|
if( punk == NULL )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Accept the following sinks:
|
|
// * Original ITextStoreACPSink,
|
|
// * Cicero sink (ITextStoreACP + Cicero extentions)
|
|
// * ITextStoreACPSinkEx sink (ITextStoreACP + OnDisconnect method)
|
|
if( riid != __uuidof( DocTraits::ISink )
|
|
&& riid != __uuidof( DocTraits::ICicSink )
|
|
&& riid != __uuidof( DocTraits::ISinkEx ) )
|
|
{
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
// check mask contains only valid bits
|
|
if( dwMask & ~ ( TS_AS_ALL_SINKS ) )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Get canonical unknown (for interface comparing...)
|
|
IUnknown * pCanonicalUnk = NULL;
|
|
HRESULT hr = punk->QueryInterface( IID_IUnknown, (void **) & pCanonicalUnk );
|
|
if( hr != S_OK || pCanonicalUnk == NULL )
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// If this is first, set it...
|
|
if( m_Sink.m_pSink == NULL )
|
|
{
|
|
// Allow the doc to work out the update cumulative mask and re-advise the doc if necessary...
|
|
m_Sink.Set( punk, riid, dwMask, pCanonicalUnk );
|
|
|
|
// Manager will scan through all sink masks, and re-Advise if necessary.
|
|
hr = m_pMgr->DocWrap_UpdateSubscription();
|
|
|
|
if( hr != S_OK )
|
|
{
|
|
// advising didn't work, or something else went wrong - revert back to empty sink...
|
|
m_Sink.ClearAndRelease();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not the first time - check if we're updating the existing mask...
|
|
if( pCanonicalUnk != m_Sink.m_pCanonicalUnk )
|
|
{
|
|
// Attempt to register a different sink - error...
|
|
hr = CONNECT_E_ADVISELIMIT;
|
|
}
|
|
else
|
|
{
|
|
// Remember the old mask - if the update doesn't work, revert back to this.
|
|
DWORD OldMask = m_Sink.m_dwMask;
|
|
m_Sink.UpdateMask( dwMask );
|
|
|
|
// Manager will scan through all dwMasks, and re-Advise if necessary.
|
|
hr = m_pMgr->DocWrap_UpdateSubscription();
|
|
|
|
if( hr != S_OK )
|
|
m_Sink.UpdateMask( OldMask );
|
|
}
|
|
}
|
|
|
|
pCanonicalUnk->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE UnadviseSink( IUnknown *punk )
|
|
{
|
|
IMETHOD( UnadviseSink );
|
|
|
|
Assert( m_pMgr );
|
|
|
|
if( punk == NULL )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Get canonical unknown (for interface comparing...)
|
|
IUnknown * pCanonicalUnk = NULL;
|
|
HRESULT hr = punk->QueryInterface( IID_IUnknown, (void **) & pCanonicalUnk );
|
|
|
|
if( hr != S_OK || pCanonicalUnk == NULL )
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
if( pCanonicalUnk != m_Sink.m_pCanonicalUnk )
|
|
{
|
|
// Sink doesn't match!
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_Sink.ClearAndRelease();
|
|
|
|
// Manager will scan through all dwMasks, and re-Advise if necessary.
|
|
hr = m_pMgr->DocWrap_UpdateSubscription();
|
|
|
|
// Not much we can do if the update fails - but even if it does fail,
|
|
// we want to disconnect this sink, and not fail the Unadvise.
|
|
Assert( hr == S_OK );
|
|
|
|
pCanonicalUnk->Release();
|
|
|
|
m_bUnadvised = true;
|
|
|
|
return S_OK; // NOT hr, since this should always succeed.
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE RequestLock( DWORD dwLockFlags, HRESULT * phrSession )
|
|
{
|
|
IMETHOD( RequestLock );
|
|
|
|
Assert( m_pMgr );
|
|
// The cast here is safe, since we'll only be used as a derived class
|
|
DocTraits::CDocWrap * pThisAsDerived = static_cast< DocTraits::CDocWrap * >( this );
|
|
|
|
return m_pMgr->DocWrap_HandleRequestLock( pThisAsDerived, dwLockFlags, phrSession );
|
|
}
|
|
|
|
|
|
//
|
|
// These Anchor/ACP-neutral methods can just be forwarded directly to the real doc...
|
|
//
|
|
|
|
DocWrap_FORWARD( GetStatus, 1, ( TS_STATUS *, pdcs ) )
|
|
|
|
DocWrap_FORWARD_READLOCK( QueryInsert, 5, ( DocTraits::PosType, InsertStart,
|
|
DocTraits::PosType, InsertEnd,
|
|
ULONG, cch,
|
|
DocTraits::PosType *, ppaInsertStart,
|
|
DocTraits::PosType *, ppaInsertEnd ) )
|
|
|
|
DocWrap_FORWARD_READLOCK( QueryInsertEmbedded, 3, ( const GUID *, pguidService,
|
|
const FORMATETC *, pFormatEtc,
|
|
BOOL *, pfInsertable ) )
|
|
|
|
DocWrap_FORWARD( GetScreenExt, 2, ( TsViewCookie, vcView,
|
|
RECT *, prc ) )
|
|
|
|
DocWrap_FORWARD( GetWnd, 2, ( TsViewCookie, vcView,
|
|
HWND *, phwnd ) )
|
|
|
|
DocWrap_FORWARD_READLOCK( GetFormattedText, 3, ( DocTraits::PosType, Start,
|
|
DocTraits::PosType, End,
|
|
IDataObject **, ppDataObject ) )
|
|
|
|
DocWrap_FORWARD_READLOCK( GetTextExt, 5, ( TsViewCookie, vcView,
|
|
DocTraits::PosType, Start,
|
|
DocTraits::PosType, End,
|
|
RECT *, prc,
|
|
BOOL *, pfClipped ) )
|
|
|
|
DocWrap_FORWARDEXT( ScrollToRect, 4, ( DocTraits::PosType, Start,
|
|
DocTraits::PosType, End,
|
|
RECT, rc,
|
|
DWORD, dwPosition ) )
|
|
|
|
DocWrap_FORWARD( GetActiveView, 1, ( TsViewCookie *, pvcView ) )
|
|
|
|
// IClonableWrapper
|
|
|
|
HRESULT STDMETHODCALLTYPE CloneNewWrapper( REFIID riid, void ** ppv )
|
|
{
|
|
IMETHOD( CloneNewWrapper );
|
|
|
|
// Just call through to CWrapMgr's CreateWrappedDoc...
|
|
|
|
Assert( m_pMgr );
|
|
|
|
IUnknown * punk;
|
|
HRESULT hr = m_pMgr->CreateWrappedDoc( & punk );
|
|
if( hr != S_OK )
|
|
return hr;
|
|
|
|
hr = punk->QueryInterface( riid, ppv );
|
|
punk->Release();
|
|
return hr;
|
|
}
|
|
|
|
// IInternalDocWrap
|
|
|
|
HRESULT STDMETHODCALLTYPE NotifyRevoke()
|
|
{
|
|
// Just pass on to the CWrapMgr...
|
|
Assert( m_pMgr );
|
|
|
|
m_pMgr->DocWrap_NotifyRevoke();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// ICoCreateLocally
|
|
#include <Rpcdce.h>
|
|
HRESULT STDMETHODCALLTYPE CoCreateLocally (
|
|
REFCLSID rclsid,
|
|
DWORD dwClsContext,
|
|
REFIID riid,
|
|
IUnknown ** punk,
|
|
REFIID riidParam,
|
|
IUnknown * punkParam,
|
|
VARIANT varParam
|
|
)
|
|
{
|
|
IMETHOD( CoCreateLocally );
|
|
|
|
LPCTSTR pPrivs = NULL; //Pointer to handle to privilege information
|
|
|
|
HRESULT hrSec = CoQueryClientBlanket( 0, 0, 0, 0, 0, (void **)&pPrivs, 0 );
|
|
if ( hrSec != S_OK )
|
|
return hrSec;
|
|
|
|
TSTR strUser(128);
|
|
DWORD nSize = strUser.left();
|
|
if ( !GetUserName( strUser.ptr(), &nSize ) )
|
|
return E_ACCESSDENIED;
|
|
|
|
strUser.advance( nSize - 1 );
|
|
|
|
TSTR strClientUser( pPrivs );
|
|
const int nSlashPos = strClientUser.find( TEXT("\\") );
|
|
if ( nSlashPos > 0 )
|
|
strClientUser = strClientUser.substr( nSlashPos + 1, strClientUser.size() - nSlashPos );
|
|
|
|
TraceDebug( TSTR() << TEXT("Current user = ") << strUser << TEXT(", Client user = ") << strClientUser );
|
|
if ( strClientUser.compare( strUser ) != 0 )
|
|
if ( strClientUser.compare( TEXT("SYSTEM") ) != 0 )
|
|
return E_ACCESSDENIED;
|
|
|
|
HRESULT hr = CoCreateInstance(rclsid, NULL, dwClsContext, riid, (void **)punk);
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
CComPtr<ICoCreatedLocally> pICoCreatedLocally;
|
|
hr = (*punk)->QueryInterface(IID_ICoCreatedLocally, (void **)&pICoCreatedLocally);
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
hr = pICoCreatedLocally->LocalInit(m_pDoc, riidParam, punkParam, varParam);
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// IServiceProvider - Cicero uses this to 'drill through' to the original anchor to pull out
|
|
// internal information. Just pass it through...
|
|
//
|
|
HRESULT STDMETHODCALLTYPE QueryService( REFGUID guidService, REFIID riid, void **ppvObject )
|
|
{
|
|
IMETHOD( QueryService );
|
|
|
|
*ppvObject = NULL;
|
|
|
|
CComPtr<IServiceProvider> pISP;
|
|
HRESULT hr = m_pDoc->QueryInterface( IID_IServiceProvider, (void **) & pISP );
|
|
if( hr != S_OK || pISP == NULL )
|
|
return E_FAIL;
|
|
|
|
hr = pISP->QueryService( guidService, riid, ppvObject );
|
|
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
// CTextStoreWrapACP - ACP version of ITextStoreACP wrapper...
|
|
//
|
|
|
|
class ATL_NO_VTABLE CDocWrapACP :
|
|
public CDocWrapBase< DocTraitsACP >
|
|
{
|
|
public:
|
|
|
|
// ITextStoreACP
|
|
DocWrap_FORWARD_READLOCK( GetSelection, 4, ( ULONG, ulIndex, ULONG, ulCount, TS_SELECTION_ACP *, pSelection, ULONG *, pcFetched ) )
|
|
DocWrap_FORWARD_READLOCK( GetText, 9, ( LONG, acpStart,
|
|
LONG, acpEnd,
|
|
WCHAR *, pchPlain,
|
|
ULONG, cchPlainReq,
|
|
ULONG *, pcchPlainRet,
|
|
TS_RUNINFO *, prgRunInfo,
|
|
ULONG, cRunInfoReq,
|
|
ULONG *, pcRunInfoRet,
|
|
LONG *, pacpNext ) )
|
|
DocWrap_FORWARD_READLOCK( GetEmbedded, 4, ( LONG, Pos, REFGUID, rguidService, REFIID, riid, IUnknown **, ppunk ) )
|
|
DocWrap_FORWARD_READLOCK( GetEndACP, 1, ( LONG *, pacp ) )
|
|
DocWrap_FORWARD_READLOCK( GetACPFromPoint, 4, ( TsViewCookie, vcView, const POINT *, ptScreen, DWORD, dwFlags, LONG *, pacp ) )
|
|
DocWrap_FORWARD( RequestSupportedAttrs, 3, ( DWORD, dwFlags,
|
|
ULONG, cFilterAttrs,
|
|
const TS_ATTRID *, paFilterAttrs ) )
|
|
DocWrap_FORWARD_READLOCK( RequestAttrsAtPosition, 4, ( DocTraits::PosType, Pos,
|
|
ULONG, cFilterAttrs,
|
|
const TS_ATTRID *, paFilterAttrs,
|
|
DWORD, dwFlags ) )
|
|
DocWrap_FORWARD_READLOCK( RequestAttrsTransitioningAtPosition,
|
|
4, ( DocTraits::PosType, Pos,
|
|
ULONG, cFilterAttrs,
|
|
const TS_ATTRID *, paFilterAttrs,
|
|
DWORD, dwFlags ) )
|
|
DocWrap_FORWARD_READLOCK( FindNextAttrTransition, 8, ( LONG, acpStart, LONG, acpEnd, ULONG, cFilterAttrs, const TS_ATTRID *, paFilterAttrs, DWORD, dwFlags, LONG *, pacpNext, BOOL *, pfFound, LONG *, plFoundOffset ) )
|
|
DocWrap_FORWARD( RetrieveRequestedAttrs, 3, ( ULONG, ulCount,
|
|
TS_ATTRVAL *, paAttrVals,
|
|
ULONG *, pcFetched ) )
|
|
DocWrap_FORWARD_AND_SINK( SetSelection, 2, ( ULONG, ulCount, const TS_SELECTION_ACP *, pSelection ),
|
|
TS_AS_SEL_CHANGE, pSink->OnSelectionChange() )
|
|
|
|
DocWrap_FORWARD_AND_SINK( SetText, 6, ( DWORD, dwFlags, LONG, acpStart, LONG, acpEnd, const WCHAR *, pchText, ULONG, cch, TS_TEXTCHANGE *, pChange ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, pChange ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertEmbedded, 5, ( DWORD, dwFlags, LONG, acpStart, LONG, acpEnd, IDataObject *, pDataObject, TS_TEXTCHANGE *, pChange ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, pChange ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertTextAtSelection, 6, ( DWORD, dwFlags, const WCHAR *, pchText, ULONG, cch, LONG *, pacpStart, LONG *, pacpEnd, TS_TEXTCHANGE *, pChange),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, pChange ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertEmbeddedAtSelection, 5, ( DWORD, dwFlags, IDataObject *, pDataObject, LONG *, pacpStart, LONG *, pacpEnd, TS_TEXTCHANGE *, pChange),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, pChange ) )
|
|
|
|
|
|
};
|
|
|
|
//
|
|
// CTextStoreWrapAnchor - Anchor version of ITextStoreACP wrapper
|
|
//
|
|
|
|
class ATL_NO_VTABLE CDocWrapAnchor :
|
|
public CDocWrapBase< DocTraitsAnchor >
|
|
{
|
|
/*
|
|
// Used when generating OnTextChange events in response to InsertEmbedded.
|
|
// See forwarding macro for InsertEmbedded below...
|
|
void ProcessInsertEmbeddedOnTextChange( DocTraits::ISink * pSink, DWORD dwFlags, IAnchor * paPos )
|
|
{
|
|
// Want to send a TextChange with anchors before and after the insert position -
|
|
// we have the before position - clone and move it to get the after position.
|
|
IAnchor * pAnchorAfter = NULL;
|
|
HRESULT hr = paPos->Clone( & pAnchorAfter );
|
|
if( hr != S_OK || pAnchorAfter == NULL )
|
|
{
|
|
TraceInteropHR( hr, TEXT("IAnchor::Clone failed") );
|
|
return;
|
|
}
|
|
|
|
LONG cchShifted = 0;
|
|
hr = pAnchorAfter->Shift( 1, & cchShifted, NULL );
|
|
if( hr != S_OK || cchShifted != 1 )
|
|
{
|
|
TraceInteropHR( hr, TEXT("IAnchor::Shift failed?") );
|
|
return;
|
|
}
|
|
|
|
pSink->OnTextChange( dwFlags, paPos, pAnchorAfter );
|
|
|
|
pAnchorAfter->Release();
|
|
}
|
|
*/
|
|
|
|
public:
|
|
|
|
CDocWrapAnchor() : m_cMaxAttrs(0),
|
|
m_cAttrsTAP(0),
|
|
m_iAttrsTAP(0),
|
|
m_cAttrsTAPSize(0),
|
|
m_paAttrsTAP(NULL),
|
|
m_paAttrsSupported(NULL)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
~CDocWrapAnchor()
|
|
{
|
|
ResetAttrs();
|
|
if ( m_paAttrsTAP )
|
|
{
|
|
delete [] m_paAttrsTAP;
|
|
m_paAttrsTAP = NULL;
|
|
}
|
|
if ( m_paAttrsSupported )
|
|
{
|
|
delete [] m_paAttrsSupported;
|
|
m_paAttrsSupported = NULL;
|
|
}
|
|
}
|
|
|
|
// ITextStoreAnchor
|
|
DocWrap_FORWARD_READLOCK( GetSelection, 4, ( ULONG, ulIndex, ULONG, ulCount, TS_SELECTION_ANCHOR *, pSelection, ULONG *, pcFetched ) )
|
|
DocWrap_FORWARD_READLOCK( GetText, 7, ( DWORD, dwFlags, IAnchor *, paStart, IAnchor *, paEnd, WCHAR *, pchText, ULONG, cchReq, ULONG *, pcch, BOOL, fUpdateAnchor ) )
|
|
DocWrap_FORWARD_READLOCK( GetEmbedded, 5, ( DWORD, dwFlags,
|
|
IAnchor *, Pos,
|
|
REFGUID, rguidService,
|
|
REFIID, riid,
|
|
IUnknown **, ppunk ) )
|
|
DocWrap_FORWARD_READLOCK( GetStart, 1, ( IAnchor **, ppaStart ) )
|
|
DocWrap_FORWARD_READLOCK( GetEnd, 1, ( IAnchor **, ppaEnd ) )
|
|
DocWrap_FORWARD_READLOCK( GetAnchorFromPoint, 4, ( TsViewCookie, vcView, const POINT *, ptScreen, DWORD, dwFlags, IAnchor **, ppaSite ) )
|
|
// DocWrap_FORWARD( RequestSupportedAttrs, 3, ( DWORD, dwFlags,
|
|
// ULONG, cFilterAttrs,
|
|
// const TS_ATTRID *, paFilterAttrs ) )
|
|
// DocWrap_FORWARD_READLOCK( RequestAttrsAtPosition, 4, ( DocTraits::PosType, Pos,
|
|
// ULONG, cFilterAttrs,
|
|
// const TS_ATTRID *, paFilterAttrs,
|
|
// DWORD, dwFlags ) )
|
|
// DocWrap_FORWARD_READLOCK( RequestAttrsTransitioningAtPosition,
|
|
// 4, ( DocTraits::PosType, Pos,
|
|
// ULONG, cFilterAttrs,
|
|
// const TS_ATTRID *, paFilterAttrs,
|
|
// DWORD, dwFlags ) )
|
|
// DocWrap_FORWARD_READLOCK( FindNextAttrTransition, 7, ( IAnchor *, paStart, IAnchor *, paEnd, ULONG, cFilterAttrs, const TS_ATTRID *, paFilterAttrs, DWORD, dwFlags, BOOL *, pfFound, LONG *, plFoundOffset ) )
|
|
|
|
|
|
DocWrap_FORWARD_AND_SINK( SetSelection, 2, ( ULONG, ulCount, const TS_SELECTION_ANCHOR *, pSelection ),
|
|
TS_AS_SEL_CHANGE, pSink->OnSelectionChange() )
|
|
|
|
DocWrap_FORWARD_AND_SINK( SetText, 5, ( DWORD, dwFlags, IAnchor *, paStart, IAnchor *, paEnd, const WCHAR *, pchText, ULONG, cch ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, paStart, paEnd ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertEmbedded, 4, ( DWORD, dwFlags, IAnchor *, paStart, IAnchor *, paEnd, IDataObject *, pDataObject ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, paStart, paEnd ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertTextAtSelection, 5, ( DWORD, dwFlags, const WCHAR *, pchText, ULONG, cch, IAnchor **, ppaStart, IAnchor **, ppaEnd ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, *ppaStart, *ppaEnd ) )
|
|
|
|
DocWrap_FORWARD_AND_SINK( InsertEmbeddedAtSelection, 4, ( DWORD, dwFlags, IDataObject *, pDataObject, IAnchor **, ppaStart, IAnchor **, ppaEnd ),
|
|
TS_AS_TEXT_CHANGE, pSink->OnTextChange( dwFlags, *ppaStart, *ppaEnd ) )
|
|
|
|
HRESULT STDMETHODCALLTYPE RequestSupportedAttrs ( DWORD dwFlags,
|
|
ULONG cFilterAttrs,
|
|
const TS_ATTRID * paFilterAttrs )
|
|
{
|
|
IMETHOD( RequestSupportedAttrs );
|
|
ResetAttrs();
|
|
return m_pDoc->RequestSupportedAttrs( dwFlags, cFilterAttrs, paFilterAttrs ) ;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE RequestAttrsAtPosition ( DocTraits::PosType Pos,
|
|
ULONG cFilterAttrs,
|
|
const TS_ATTRID * paFilterAttrs,
|
|
DWORD dwFlags )
|
|
{
|
|
IMETHOD( RequestAttrsAtPosition );
|
|
if ( VerifyLock( TS_LF_READ ) == S_FALSE )
|
|
return TS_E_NOLOCK;
|
|
ResetAttrs();
|
|
return m_pDoc->RequestAttrsAtPosition( Pos, cFilterAttrs, paFilterAttrs, dwFlags ) ;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE RequestAttrsTransitioningAtPosition ( IAnchor * paStart,
|
|
ULONG cFilterAttrs,
|
|
const TS_ATTRID * paFilterAttrs,
|
|
DWORD dwFlags )
|
|
{
|
|
IMETHOD( RequestAttrsTransitioningAtPosition );
|
|
if ( VerifyLock( TS_LF_READ ) == S_FALSE )
|
|
return TS_E_NOLOCK;
|
|
ResetAttrs();
|
|
|
|
// call through to the doc
|
|
HRESULT hr = m_pDoc->RequestAttrsTransitioningAtPosition( paStart, cFilterAttrs, paFilterAttrs, dwFlags );
|
|
if ( hr != E_NOTIMPL )
|
|
return hr;
|
|
|
|
// if the server does not support this do it ourselves
|
|
// make sure there is really something to do
|
|
if ( paStart == NULL )
|
|
return S_OK;
|
|
|
|
// make sure we can hold the attributes we find
|
|
hr = AllocateAttrs( cFilterAttrs );
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
|
|
ULONG cAlloAttrs = cFilterAttrs ? cFilterAttrs : m_cMaxAttrs;
|
|
|
|
TS_ATTRVAL * paCurrent = reinterpret_cast<TS_ATTRVAL *>( alloca( sizeof( TS_ATTRVAL ) * cAlloAttrs ) );
|
|
if ( !paCurrent )
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = GetAttr ( paStart, cFilterAttrs, paFilterAttrs, dwFlags, paCurrent);
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
|
|
LONG cchShifted;
|
|
ULONG iAttrs = 0;
|
|
TS_ATTRVAL * paComp = NULL;
|
|
CComPtr <IAnchor> paPos;
|
|
paStart->Clone( &paPos );
|
|
hr = paPos->Shift( 0, -1, &cchShifted, NULL ); // TODO fix hidden text
|
|
if ( SUCCEEDED(hr) && cchShifted == -1 )
|
|
{
|
|
paComp = reinterpret_cast<TS_ATTRVAL *>( alloca( sizeof( TS_ATTRVAL ) * cAlloAttrs ) );
|
|
if ( !paComp )
|
|
return E_OUTOFMEMORY;
|
|
hr = GetAttr ( paPos, cFilterAttrs, paFilterAttrs, dwFlags, paComp);
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
if ( dwFlags & TS_ATTR_FIND_WANT_END )
|
|
CompareAttrs ( paCurrent, paComp, cAlloAttrs, iAttrs, TRUE );
|
|
else
|
|
CompareAttrs ( paComp, paCurrent, cAlloAttrs, iAttrs, TRUE );
|
|
}
|
|
|
|
if ( !( dwFlags & TS_ATTR_FIND_WANT_VALUE ) )
|
|
{
|
|
for ( int i= 0; i < cFilterAttrs; i++ )
|
|
{
|
|
VariantClear( &m_paAttrsTAP[i].varValue );
|
|
}
|
|
}
|
|
|
|
m_cAttrsTAP = iAttrs;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE FindNextAttrTransition ( IAnchor * paStart,
|
|
IAnchor * paEnd,
|
|
ULONG cFilterAttrs,
|
|
const TS_ATTRID * paFilterAttrs,
|
|
DWORD dwFlags,
|
|
BOOL * pfFound,
|
|
LONG * plFoundOffset )
|
|
{
|
|
IMETHOD( FindNextAttrTransition );
|
|
if ( VerifyLock( TS_LF_READ ) == S_FALSE )
|
|
return TS_E_NOLOCK;
|
|
|
|
HRESULT hr = m_pDoc->FindNextAttrTransition( paStart, paEnd, cFilterAttrs, paFilterAttrs, dwFlags, pfFound, plFoundOffset );
|
|
if ( hr != E_NOTIMPL )
|
|
return hr;
|
|
|
|
*pfFound = FALSE;
|
|
*plFoundOffset = 0;
|
|
|
|
// if the server does not support this do it ourselves
|
|
// make sure there is really something to do
|
|
if ( paStart == NULL )
|
|
return S_OK;
|
|
|
|
// make sure we can hold the attributes we find
|
|
hr = AllocateAttrs( cFilterAttrs );
|
|
if ( FAILED(hr) )
|
|
{
|
|
TraceDebugHR( hr, TEXT("AllocateAttrs failed ") );
|
|
return hr;
|
|
}
|
|
ULONG cAlloAttrs = cFilterAttrs ? cFilterAttrs : m_cMaxAttrs;
|
|
|
|
TS_ATTRVAL * paCurrent = reinterpret_cast<TS_ATTRVAL *>( alloca( sizeof( TS_ATTRVAL ) * cAlloAttrs ) );
|
|
TS_ATTRVAL * paNext = reinterpret_cast<TS_ATTRVAL *>( alloca( sizeof( TS_ATTRVAL ) * cAlloAttrs ) );
|
|
if ( !paCurrent || !paNext )
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = GetAttr ( paStart, cFilterAttrs, paFilterAttrs, dwFlags, paCurrent);
|
|
if ( FAILED(hr) )
|
|
{
|
|
TraceDebugHR( hr, TEXT("Current GetAttr failed ") );
|
|
return hr;
|
|
}
|
|
|
|
LONG cchShifted;
|
|
ULONG iAttrs = 0;
|
|
BOOL fDone = TRUE;
|
|
const LONG cchShift = ( dwFlags & TS_ATTR_FIND_BACKWARDS ) ? -1 : 1;
|
|
|
|
CComPtr <IAnchor> paPos, paEndOfDoc;
|
|
hr = paStart->Clone( &paPos );
|
|
if ( FAILED(hr) )
|
|
{
|
|
TraceDebugHR( hr, TEXT("Clone failed ") );
|
|
return hr;
|
|
}
|
|
if ( paEnd == NULL )
|
|
{
|
|
if ( dwFlags & TS_ATTR_FIND_BACKWARDS )
|
|
{
|
|
m_pDoc->GetStart( &paEndOfDoc );
|
|
}
|
|
else
|
|
{
|
|
BOOL fRegion = FALSE;
|
|
hr = paStart->Clone( &paEndOfDoc );
|
|
while ( fRegion )
|
|
{
|
|
paEndOfDoc->Shift( 0, LONG_MAX, &cchShifted, NULL );
|
|
paEndOfDoc->ShiftRegion( 0, TS_SD_FORWARD, &fRegion );
|
|
}
|
|
}
|
|
}
|
|
|
|
while ( iAttrs == 0 )
|
|
{
|
|
hr = paPos->Shift( 0, cchShift, &cchShifted, NULL ); // TODO fix hidden text
|
|
if ( SUCCEEDED(hr) && cchShifted == cchShift )
|
|
{
|
|
*plFoundOffset += 1;
|
|
hr = paPos->IsEqual( paEnd ? paEnd : paEndOfDoc, &fDone );
|
|
if ( FAILED(hr) )
|
|
{
|
|
TraceDebugHR( hr, TEXT("IsEqual failed ") );
|
|
return hr;
|
|
}
|
|
if ( fDone )
|
|
break;
|
|
|
|
hr = GetAttr ( paPos, cFilterAttrs, paFilterAttrs, dwFlags, paNext);
|
|
if ( FAILED(hr) )
|
|
{
|
|
TraceDebugHR( hr, TEXT("Next GetAttr failed ") );
|
|
return hr;
|
|
}
|
|
CompareAttrs ( paCurrent, paNext, cFilterAttrs, iAttrs, FALSE );
|
|
if ( iAttrs )
|
|
{
|
|
*pfFound = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceDebugHR( hr, TEXT("Shift failed ") );
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE RetrieveRequestedAttrs ( ULONG ulCount,
|
|
TS_ATTRVAL * paAttrVals,
|
|
ULONG * pcFetched )
|
|
{
|
|
// if there is no outstanding requests we satisfy then call through to the doc
|
|
if ( m_cAttrsTAP == 0 )
|
|
return m_pDoc->RetrieveRequestedAttrs( ulCount, paAttrVals, pcFetched );
|
|
|
|
if ( ( m_cAttrsTAP - m_iAttrsTAP ) < ulCount )
|
|
*pcFetched = m_cAttrsTAP - m_iAttrsTAP;
|
|
else
|
|
*pcFetched = ulCount;
|
|
|
|
memcpy(paAttrVals, &m_paAttrsTAP[m_iAttrsTAP], *pcFetched * sizeof(TS_ATTRVAL));
|
|
memset(&m_paAttrsTAP[m_iAttrsTAP], 0, *pcFetched * sizeof(TS_ATTRVAL));
|
|
m_iAttrsTAP += *pcFetched;
|
|
|
|
if ( m_iAttrsTAP == m_cAttrsTAP )
|
|
ResetAttrs();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
ULONG m_cMaxAttrs;
|
|
ULONG m_iAttrsTAP;
|
|
ULONG m_cAttrsTAP;
|
|
ULONG m_cAttrsTAPSize;
|
|
TS_ATTRVAL * m_paAttrsTAP;
|
|
TS_ATTRID * m_paAttrsSupported;
|
|
|
|
private:
|
|
|
|
HRESULT STDMETHODCALLTYPE CompareAttrs ( TS_ATTRVAL * paAttr1,
|
|
TS_ATTRVAL * paAttr2,
|
|
ULONG cAttrs,
|
|
ULONG &iAttrs,
|
|
BOOL fCopy)
|
|
{
|
|
cAttrs = cAttrs ? cAttrs : m_cMaxAttrs;
|
|
|
|
for ( int i = 0; i < cAttrs; i++ )
|
|
{
|
|
for ( int j = 0; j < cAttrs; j++ )
|
|
{
|
|
if ( paAttr1[i].idAttr == paAttr2[j].idAttr )
|
|
{
|
|
if ( CComVariant( paAttr1[i].varValue ) != CComVariant( paAttr2[j].varValue ) )
|
|
{
|
|
if ( fCopy )
|
|
{
|
|
char * cBuf = ( char * )&m_paAttrsTAP[iAttrs];
|
|
memcpy( cBuf, ( char * )&paAttr2[j], sizeof(TS_ATTRVAL) );
|
|
}
|
|
iAttrs++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE GetAttr ( IAnchor * paStart,
|
|
ULONG cFilterAttrs,
|
|
const TS_ATTRID * paFilterAttrs,
|
|
DWORD dwFlags,
|
|
TS_ATTRVAL * paAttrVals)
|
|
{
|
|
ULONG cFetched;
|
|
HRESULT hr;
|
|
|
|
ULONG cAlloAttrs = cFilterAttrs ? cFilterAttrs : m_cMaxAttrs;
|
|
const TS_ATTRID * paActualFilterAttrs = paFilterAttrs ? paFilterAttrs : m_paAttrsSupported;
|
|
|
|
hr = m_pDoc->RequestAttrsAtPosition( paStart, cAlloAttrs, paActualFilterAttrs, 0 );
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
|
|
hr = m_pDoc->RetrieveRequestedAttrs( cAlloAttrs, paAttrVals, &cFetched );
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE AllocateAttrs ( ULONG cFilterAttrs )
|
|
{
|
|
if ( cFilterAttrs == 0 && m_cMaxAttrs == 0 )
|
|
{
|
|
const LONG cAttrs = 512;
|
|
|
|
HRESULT hr = m_pDoc->RequestSupportedAttrs( 0, 0, NULL );
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
|
|
TS_ATTRVAL * paSupported = new TS_ATTRVAL[ cAttrs ];
|
|
if ( !paSupported )
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = m_pDoc->RetrieveRequestedAttrs( cAttrs, paSupported, &m_cMaxAttrs );
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
m_paAttrsSupported = new TS_ATTRID[ m_cMaxAttrs ];
|
|
if ( m_paAttrsSupported )
|
|
{
|
|
for ( int i = 0; i < m_cMaxAttrs; i++ )
|
|
{
|
|
m_paAttrsSupported[i] = paSupported[i].idAttr;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete [] paSupported;
|
|
|
|
if ( FAILED(hr) )
|
|
return hr;
|
|
if ( !m_paAttrsSupported )
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
ULONG cAlloAttrs = cFilterAttrs ? cFilterAttrs : m_cMaxAttrs;
|
|
if ( m_cAttrsTAPSize < cAlloAttrs )
|
|
{
|
|
if ( m_paAttrsTAP )
|
|
delete [] m_paAttrsTAP;
|
|
|
|
m_paAttrsTAP = new TS_ATTRVAL[ cAlloAttrs ];
|
|
if ( !m_paAttrsTAP )
|
|
{
|
|
m_cAttrsTAPSize = 0;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
m_cAttrsTAPSize = cAlloAttrs;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void ResetAttrs()
|
|
{
|
|
m_cAttrsTAP = 0;
|
|
m_iAttrsTAP = 0;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 12e53b1b-7d7f-40bd-8f88-4603ee40cf58 */
|
|
const IID IID_PRIV_CINPUTCONTEXT = { 0x12e53b1b, 0x7d7f, 0x40bd, {0x8f, 0x88, 0x46, 0x03, 0xee, 0x40, 0xcf, 0x58} };
|
|
|
|
/* aabf7f9a-4487-4b2e-8164-e54c5fe19204 */
|
|
const GUID GUID_SERVICE_CTF = { 0xaabf7f9a, 0x4487, 0x4b2e, {0x81, 0x64, 0xe5, 0x4c, 0x5f, 0xe1, 0x92, 0x04} };
|
|
|
|
|
|
//
|
|
// CDocSinkWrapBase
|
|
//
|
|
// - Base from which Anchor and ACP sink wrappers are derived.
|
|
//
|
|
// This class contains ACP/Anchor-neutral wrapping code - anything that is
|
|
// ACP/Anchor-specific is handled in the derived ..ACP or ...Anchor class
|
|
// instead.
|
|
//
|
|
// Since this class is the sink for the wrapper, it derives from the full Cicero
|
|
// sink (DocTraits::ICicSink - which is a typedef for ITfTextStoreSink[Anchor]),
|
|
// and that in turn includes the ITextStoreACP sink.
|
|
//
|
|
|
|
template < class _DocTraits >
|
|
class ATL_NO_VTABLE CSinkWrapBase :
|
|
public CComObjectRootEx<CComSingleThreadModel>,
|
|
public _DocTraits::ISink,
|
|
public _DocTraits::ICicSink,
|
|
public IServiceProvider
|
|
{
|
|
public:
|
|
|
|
// This typedef makes the DocTraits type visible in this and in the
|
|
// Anchor/ACP-specific derived classes. (Otherwise, as a template
|
|
// parameter in this class, it would not be available to them.)
|
|
typedef _DocTraits DocTraits;
|
|
|
|
DECLARE_PROTECT_FINAL_CONSTRUCT()
|
|
|
|
BEGIN_COM_MAP( CSinkWrapBase<DocTraits> )
|
|
COM_INTERFACE_ENTRY( DocTraits::ISink )
|
|
COM_INTERFACE_ENTRY( DocTraits::ICicSink )
|
|
COM_INTERFACE_ENTRY( IServiceProvider )
|
|
END_COM_MAP()
|
|
|
|
/*
|
|
static HRESULT InternalQueryInterface( void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject )
|
|
{
|
|
// Hack for cicero - they expect to be able to QI for IID_PRIV_CINPUTCONTEXT and get
|
|
// a ptr to one of ther internal class types (ick!)
|
|
// This breaks COM identity, but hey, it's a private IID, and is only ever used
|
|
// locally.
|
|
// We can't do that sort of goo using the interface map (above), so have to hijack InternalQI instead.
|
|
if( iid == IID_PRIV_CINPUTCONTEXT )
|
|
{
|
|
CSinkWrapBase<DocTraits> * pTHIS = (CSinkWrapBase<DocTraits> *)pThis;
|
|
|
|
// Look for the cicero sink...
|
|
// recognize it by the iid - it will have reg'd with a ITf (not TextStore) IID...
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( pTHIS->m_pMgr->m_DocWraps ) ; ! i.AtEnd(); i++ )
|
|
{
|
|
if( i->m_Sink.m_pSink && i->m_Sink.m_iid == __uuidof( DocTraits::ICicSink ) )
|
|
{
|
|
return i->m_Sink.m_pSink->QueryInterface( iid, ppvObject );
|
|
}
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
return CComObjectRootEx<CComSingleThreadModel>::InternalQueryInterface( pThis, pEntries, iid, ppvObject );
|
|
}
|
|
*/
|
|
protected:
|
|
|
|
// Protected stuff - used in this class, and by the ACP/Anchor-specific
|
|
// derived classes.
|
|
|
|
// Link back to the wrap manager. Used to tell it when we are going
|
|
// away...
|
|
CWrapMgr< DocTraits > * m_pMgr;
|
|
|
|
// Ptr to the manager's list of docs (which contain sinks)...
|
|
List_dl< DocTraits::CDocWrap > * m_pDocs;
|
|
|
|
|
|
// This macro forwards a call by iterating all the sinks in the manager,
|
|
// and forwarding if their mask has the right bit set.
|
|
// Note - this CANNOT be used for OnLockGranted.
|
|
#define CSinkWrap_FORWARD( mask, fname, c, params ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params )\
|
|
{\
|
|
IMETHOD( fname );\
|
|
Assert( m_pMgr && m_pDocs );\
|
|
m_pMgr->RemoveDeadDocs();\
|
|
CPrivateAddRef MyAddRef(m_pMgr->m_lIterationRefCount);\
|
|
for( Iter_dl < DocTraits::CDocWrap > i ( *m_pDocs ) ; ! i.AtEnd() ; i++ )\
|
|
{\
|
|
DocTraits::ISink * pSink = static_cast< DocTraits::ISink * >( i->m_Sink.m_pSink );\
|
|
DWORD dwMask = i->m_Sink.m_dwMask;\
|
|
if( pSink && ( mask == 0 || ( dwMask & mask ) ) )\
|
|
{\
|
|
pSink-> fname AS_CALL( c, params );\
|
|
}\
|
|
}\
|
|
return S_OK ;\
|
|
}
|
|
|
|
|
|
|
|
|
|
// This macro forwards Cicero's TextStoreSink calls - they're not really sinks since
|
|
// the return values are significant - true broadcast is not supported; only the
|
|
// first sink in the manager which supports the interface is used - and its
|
|
// return value gets returned.
|
|
|
|
#define CSinkWrap_FORWARD_CICERO( fname, c, params ) /**/ \
|
|
HRESULT STDMETHODCALLTYPE fname AS_DECL( c, params )\
|
|
{\
|
|
IMETHOD( fname );\
|
|
Assert( m_pMgr && m_pDocs );\
|
|
for( Iter_dl < DocTraits::CDocWrap > i ( *m_pDocs ) ; ! i.AtEnd() ; i++ )\
|
|
{\
|
|
if ( i->m_Sink.m_pSink )\
|
|
{\
|
|
CComPtr< DocTraits::ICicSink > pTheSink;\
|
|
HRESULT hr = i->m_Sink.m_pSink->QueryInterface( __uuidof(DocTraits::ICicSink), (void **)&pTheSink );\
|
|
if( hr == S_OK )\
|
|
{\
|
|
return pTheSink-> fname AS_CALL( c, params );\
|
|
}\
|
|
}\
|
|
}\
|
|
return E_FAIL ;\
|
|
}
|
|
|
|
|
|
public:
|
|
|
|
//
|
|
// Ctor, Dtor and initialization...
|
|
//
|
|
|
|
CSinkWrapBase()
|
|
: m_pMgr( NULL ),
|
|
m_pDocs( NULL )
|
|
{
|
|
|
|
}
|
|
|
|
~CSinkWrapBase()
|
|
{
|
|
AssertMsg( m_pMgr != NULL && m_pDocs != NULL, TEXT("CSinkWrapBase::Init never got called?") );
|
|
m_pMgr->SinkWrap_NotifyDisconnect();
|
|
m_pMgr->Release();
|
|
}
|
|
|
|
void Init( CWrapMgr< DocTraits > * pMgr, List_dl< DocTraits::CDocWrap > * pDocs )
|
|
{
|
|
AssertMsg( m_pMgr == NULL && m_pDocs == NULL, TEXT("CSinkWrapBase::Init should only be called once when m_pMgr is NULL") );
|
|
m_pMgr = pMgr;
|
|
m_pMgr->AddRef();
|
|
|
|
m_pDocs = pDocs;
|
|
}
|
|
|
|
//
|
|
// IServiceProvider - Cicero uses this to 'drill through' to the original anchor to pull out
|
|
// internal information. Just pass it through...
|
|
//
|
|
HRESULT STDMETHODCALLTYPE QueryService( REFGUID guidService, REFIID riid, void **ppvObject )
|
|
{
|
|
IMETHOD( QueryService );
|
|
|
|
// Find the cicero sink...
|
|
|
|
DocTraits::ICicSink * pTheSink = NULL;
|
|
|
|
// Look for the cicero sink...
|
|
// The cicero sink supports the Services interfaces and other don't
|
|
for( Iter_dl< DocTraits::CDocWrap > i ( m_pMgr->m_DocWraps ) ; ! i.AtEnd(); i++ )
|
|
{
|
|
if ( i->m_Sink.m_pSink )
|
|
{
|
|
if( i->m_Sink.m_pSink->QueryInterface( __uuidof(DocTraits::ICicSink), (void **)&pTheSink ) == S_OK )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTheSink == NULL )
|
|
return E_FAIL;
|
|
|
|
CComPtr<IServiceProvider> pISP;
|
|
HRESULT hr = pTheSink->QueryInterface( IID_IServiceProvider, (void **) & pISP );
|
|
if( hr != S_OK || pISP == NULL )
|
|
return E_FAIL;
|
|
|
|
hr = pISP->QueryService( guidService, riid, ppvObject );
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// ACP/Anchor-neutral sinks - just broadcast these...
|
|
//
|
|
|
|
CSinkWrap_FORWARD( TS_AS_SEL_CHANGE, OnSelectionChange, 0, () )
|
|
CSinkWrap_FORWARD( TS_AS_LAYOUT_CHANGE, OnLayoutChange, 2, ( TsLayoutCode, lcode, TsViewCookie, vcView ) )
|
|
CSinkWrap_FORWARD( 0, OnStatusChange, 1, ( DWORD, dwFlags ) )
|
|
CSinkWrap_FORWARD( 0, OnStartEditTransaction,0, () )
|
|
CSinkWrap_FORWARD( 0, OnEndEditTransaction, 0, () )
|
|
|
|
|
|
//
|
|
// Special case for OnLockGranted...
|
|
// Handle single-client sync requests, and multiple queued async requests...
|
|
//
|
|
|
|
HRESULT STDMETHODCALLTYPE OnLockGranted ( DWORD dwLockFlags )
|
|
{
|
|
IMETHOD( OnLockGranted );
|
|
|
|
Assert( m_pMgr );
|
|
return m_pMgr->SinkWrap_HandleOnLockGranted( dwLockFlags );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// CSinkWrapACP
|
|
//
|
|
// - ACP sink wrapper
|
|
//
|
|
// Derived from the CSinkWrapBase, this adds ACP-specific methods; including
|
|
// those from both ITextStoreACP and the cicero-specific ITfTextStoreSink
|
|
// interfaces.
|
|
//
|
|
|
|
class ATL_NO_VTABLE CSinkWrapACP :
|
|
public CSinkWrapBase< DocTraitsACP >
|
|
{
|
|
|
|
public:
|
|
//
|
|
// ITextStoreACPSink ACP/Anchor-specific methods - broadcast to all interested sinks...
|
|
// (See CSinkWrapBase for the forwarding macro.)
|
|
//
|
|
|
|
CSinkWrap_FORWARD( TS_AS_TEXT_CHANGE, OnTextChange, 2, ( DWORD, dwFlags, const TS_TEXTCHANGE *, pChange ) )
|
|
CSinkWrap_FORWARD( 0, OnAttrsChange, 4, ( LONG, acpStart, LONG, acpEnd, ULONG, cAttrs, const TS_ATTRID *, paAttrs ) )
|
|
|
|
|
|
//
|
|
// Cicero-specific sink methods - forward these to the first available sink that implements the cicero interface...
|
|
// (See CSinkWrapBase for the forwarding macro.)
|
|
//
|
|
|
|
CSinkWrap_FORWARD_CICERO( Serialize, 4, (ITfProperty *, pProp, ITfRange *, pRange, TF_PERSISTENT_PROPERTY_HEADER_ACP *, pHdr, IStream *, pStream) )
|
|
CSinkWrap_FORWARD_CICERO( Unserialize, 4, (ITfProperty *, pProp, const TF_PERSISTENT_PROPERTY_HEADER_ACP *, pHdr, IStream *, pStream, ITfPersistentPropertyLoaderACP *, pLoader) )
|
|
CSinkWrap_FORWARD_CICERO( ForceLoadProperty,1, (ITfProperty *, pProp) )
|
|
CSinkWrap_FORWARD_CICERO( CreateRange, 3, (LONG, acpStart, LONG, acpEnd, ITfRangeACP **, ppRange) )
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// CSinkWrapAnchor
|
|
//
|
|
// - Anchor sink wrapper
|
|
//
|
|
// Derived from the CSinkWrapBase, this adds Anchor-specific methods; including
|
|
// those from both ITextStoreACP and the cicero-specific ITfTextStoreSink
|
|
// interfaces.
|
|
//
|
|
|
|
class ATL_NO_VTABLE CSinkWrapAnchor :
|
|
public CSinkWrapBase< DocTraitsAnchor >
|
|
{
|
|
|
|
public:
|
|
//
|
|
// ITextStoreACPSink ACP/Anchor-specific methods - broadcast to all interested sinks...
|
|
// (See CSinkWrapBase for the forwarding macro.)
|
|
//
|
|
|
|
CSinkWrap_FORWARD( TS_AS_TEXT_CHANGE, OnTextChange, 3, ( DWORD, dwFlags, IAnchor *, paStart, IAnchor *, paEnd ) )
|
|
CSinkWrap_FORWARD( 0, OnAttrsChange, 4, ( IAnchor *, paStart, IAnchor *, paEnd, ULONG, cAttrs, const TS_ATTRID *, paAttrs ) )
|
|
|
|
|
|
//
|
|
// Cicero-specific sink methods - forward these to the first available sink that implements the cicero interface...
|
|
// (See CSinkWrapBase for the forwarding macro.)
|
|
//
|
|
|
|
CSinkWrap_FORWARD_CICERO( Serialize, 4, (ITfProperty *, pProp, ITfRange *, pRange, TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *, pHdr, IStream *, pStream) )
|
|
CSinkWrap_FORWARD_CICERO( Unserialize, 4, (ITfProperty *, pProp, const TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *, pHdr, IStream *, pStream, ITfPersistentPropertyLoaderAnchor *, pLoader) )
|
|
CSinkWrap_FORWARD_CICERO( ForceLoadProperty,1, (ITfProperty *, pProp) )
|
|
CSinkWrap_FORWARD_CICERO( CreateRange, 3, (IAnchor *, paStart, IAnchor *, paEnd, ITfRangeAnchor **, ppRange) )
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CDocWrap::CDocWrap()
|
|
: m_punkDoc( NULL ),
|
|
m_pWrapMgr( NULL )
|
|
{
|
|
IMETHOD( CDocWrap );
|
|
// Done.
|
|
}
|
|
|
|
CDocWrap::~CDocWrap()
|
|
{
|
|
IMETHOD( ~CDocWrap );
|
|
|
|
_Clear();
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE CDocWrap::SetDoc( REFIID riid, IUnknown * pDocIn )
|
|
{
|
|
IMETHOD( SetDoc );
|
|
|
|
_Clear();
|
|
|
|
if( pDocIn == NULL )
|
|
{
|
|
TraceInfo( TEXT("CDocWrapp::SetDoc( NULL ) - doc cleared") );
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr;
|
|
if( riid == IID_ITextStoreACP || riid == IID_ITfTextStoreACP )
|
|
{
|
|
CWrapMgr< DocTraitsACP > * pWrapMgrACP;
|
|
hr = CreateLocalInstance( & pWrapMgrACP );
|
|
m_pWrapMgr = pWrapMgrACP;
|
|
}
|
|
else if( riid == IID_ITextStoreAnchor || riid == IID_ITfTextStoreAnchor )
|
|
{
|
|
CWrapMgr< DocTraitsAnchor > * pWrapMgrAnchor;
|
|
hr = CreateLocalInstance( & pWrapMgrAnchor );
|
|
m_pWrapMgr = pWrapMgrAnchor;
|
|
}
|
|
else
|
|
{
|
|
TraceParam( TEXT("CDocWrapp::SetDoc - given unknown IID") );
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
CHECK_HR_RETURN( hr, m_pWrapMgr != NULL );
|
|
|
|
if( hr != S_OK )
|
|
{
|
|
TraceErrorHR( hr, TEXT("Couldn't create CWrapMgr") );
|
|
return FAILED( (hr) ) ? (hr) : E_UNEXPECTED;
|
|
}
|
|
if( hr == S_OK && ! m_pWrapMgr )
|
|
{
|
|
TraceErrorHR( hr, TEXT("Couldn't create CWrapMgr") );
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
|
|
m_pWrapMgr->SetDoc( pDocIn );
|
|
m_iid = riid;
|
|
m_punkDoc = pDocIn;
|
|
m_punkDoc->AddRef();
|
|
|
|
TraceInfo( TEXT("CDocWrap::SetDoc - new doc set.") );
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE CDocWrap::GetWrappedDoc( REFIID riid, IUnknown ** pWrappedDocOut )
|
|
{
|
|
IMETHOD( GetWrappedDoc );
|
|
|
|
if( ! m_punkDoc || ! m_pWrapMgr )
|
|
{
|
|
TraceParam( TEXT("GetWrappedDoc called without prior successful call to SetDoc") );
|
|
return E_FAIL;
|
|
}
|
|
if( ! pWrappedDocOut )
|
|
{
|
|
TraceParam( TEXT("GetWrappedDoc called without NULL pWrappedDocOut param") );
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Check that requested iid matches...
|
|
// We allow Doc/ITf mixes, provided the interfaces match ACP/Anchor-wise.
|
|
if( m_iid == IID_ITextStoreAnchor || m_iid == IID_ITfTextStoreAnchor )
|
|
{
|
|
if( riid != IID_ITextStoreAnchor && riid != IID_ITfTextStoreAnchor )
|
|
{
|
|
TraceParam( TEXT("Interface requested by GetWrappedDoc doesn't match that suplied by SetDoc") );
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( riid != IID_ITextStoreACP && riid != IID_ITfTextStoreACP )
|
|
{
|
|
TraceParam( TEXT("Interface requested by GetWrappedDoc doesn't match that suplied by SetDoc") );
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
|
|
TraceInfo( TEXT("GetWrappedDoc succeeded") );
|
|
|
|
return m_pWrapMgr->CreateWrappedDoc( pWrappedDocOut );
|
|
}
|
|
|
|
|
|
|
|
void CDocWrap::_Clear()
|
|
{
|
|
SafeReleaseClear( m_pWrapMgr );
|
|
SafeReleaseClear( m_punkDoc );
|
|
}
|