windows-nt/Source/XPSP1/NT/windows/richedit/re30/ldte.cpp
2020-09-26 16:20:57 +08:00

2394 lines
61 KiB
C++

/*
* @doc INTERNAL
*
* @module LDTE.C - RichEdit Light Data Transfer Engine |
*
* This file contains data transfer code using IDataObject
*
* Author: <nl>
* alexgo (4/25/95)
*
* Revisions: <nl>
* murrays (7/6/95) auto-doc'd and added RTF support
*
* FUTURE (AlexGo): <nl>
* Maybe merge this class with CTxtRange to make more efficient use of
* the this ptr. All but two methods use a CTxtRange and one of these
* could be global. The two are:
*
* GetDropTarget( IDropTarget **ppDropTarget )
* GetDataObjectInfo(IDataObject *pdo, DWORD *pDOIFlags) // Can be global
*
* In general, a range can spawn data objects, which need to have a clone
* of the range in case the range is moved around. The contained range
* is used for delayed rendering. A prenotification is sent to the data
* object just before the data object's data is to be changed. The data
* object then renders the data in its contained range, whereupon the
* object becomes independent of the range and destroys the range.
*
* @devnote
* We use the word ANSI in a general way to mean any multibyte character
* system as distinguished from 16-bit Unicode. Technically, ANSI refers
* to a specific single-byte character system (SBCS). We translate
* between "ANSI" and Unicode text using the Win32
* MultiByteToWideChar() and WideCharToMultiByte() APIs.
*
* Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_range.h"
#include "_ldte.h"
#include "_m_undo.h"
#include "_antievt.h"
#include "_edit.h"
#include "_disp.h"
#include "_select.h"
#include "_dragdrp.h"
#include "_dxfrobj.h"
#include "_rtfwrit.h"
#include "_rtfread.h"
#include "_urlsup.h"
ASSERTDATA
//Local Prototypes
DWORD CALLBACK WriteHGlobal(WRITEHGLOBAL *pwhg, LPBYTE pbBuff, LONG cb, LONG *pcb);
#define SFF_ADJUSTENDEOP 0x80000000
//
// LOCAL METHODS
//
/*
* ReadHGlobal(dwCookie, pbBuff, cb, pcb)
*
* @func
* EDITSTREAM callback for reading from an hglobal
*
* @rdesc
* es.dwError
*/
DWORD CALLBACK ReadHGlobal(
DWORD_PTR dwCookie, // @parm dwCookie
LPBYTE pbBuff, // @parm Buffer to fill
LONG cb, // @parm Buffer length
LONG * pcb) // @parm Out parm for # bytes stored
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "ReadHGlobal");
READHGLOBAL * const prhg = (READHGLOBAL *)dwCookie;
cb = min(cb, prhg->cbLeft);
CopyMemory(pbBuff, prhg->ptext, cb);
prhg->cbLeft -= cb;
prhg->ptext += cb;
if(pcb)
*pcb = cb;
return NOERROR;
}
/*
* WriteHGlobal(pwhg, pbBuff, cb, pcb)
*
* @func
* EDITSTREAM callback for writing ASCII to an hglobal
*
* @rdesc
* error (E_OUTOFMEMORY or NOERROR)
*/
DWORD CALLBACK WriteHGlobal(
DWORD_PTR dwCookie, // @parm dwCookie
LPBYTE pbBuff, // @parm Buffer to write from
LONG cb, // @parm Buffer length
LONG * pcb) // @parm Out parm for # bytes written
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "WriteHGlobal");
WRITEHGLOBAL * const pwhg = (WRITEHGLOBAL *)dwCookie;
HGLOBAL hglobal = pwhg->hglobal;
LPSTR pstr;
if(pwhg->cch + cb > pwhg->cb) // Less than requested cb in
{ // current Alloc
ULONG cbNewSize = GROW_BUFFER(pwhg->cb, cb);
hglobal = GlobalReAlloc(hglobal, cbNewSize, GMEM_MOVEABLE);
if(!hglobal)
return (DWORD)E_OUTOFMEMORY;
pwhg->hglobal = hglobal; // May be superfluous...
pwhg->cb = cbNewSize;
}
pstr = (LPSTR)GlobalLock(hglobal);
if(!pstr)
return (DWORD)E_OUTOFMEMORY;
CopyMemory(pstr + pwhg->cch, pbBuff, cb);
GlobalUnlock(hglobal);
pwhg->cch += cb;
if(pcb)
*pcb = cb;
return NOERROR;
}
//
// PUBLIC METHODS
//
/*
* GetCharFlags(ch, bDefaultCharset)
*
* @func
* Return flags set if ch is in first 256 Unicodes, complex-script,
* BiDi (RTL), FE. Also flags identifying which charset is likely.
* These flags correspond to those in the font signature.
*
* @rdesc
* Flags saying if ch is complex-script, BiDi (RTL), or FE
*
* =FUTURE= should be constructed as a 2-level lookup.
*/
DWORD GetCharFlags(
DWORD ch,
BYTE bDefaultCharset)
{
if(ch < 0x100) // Latin1: divide into 3 bits
return ch > 0x7F ? fHILATIN1 :
ch < 0x40 ? fBELOWX40 : fASCIIUPR;
if(ch < 0x590)
{
if(ch >= 0x530)
return fARMENIAN;
if(ch >= 0x400)
return fCYRILLIC;
if(ch >= 0x370)
return fGREEK;
if(ch >= 0x300) // Combining diacritical marks
return fCOMBINING;
return (ch < 0x250) ? fLATIN2 : fOTHER;
}
// Complex scripts start at 0x590 with Hebrew (aside from combining)
if(ch <= 0x10FF) // Complex scripts end at 0x10FF
{ // (at least in Feb, 1998)
if(ch < 0x900)
{
return fBIDI |
(ch < 0x600 ? fHEBREW :
ch < 0x700 ? fARABIC : 0);
}
if(ch < 0xE00)
{
return (ch < 0x980 ? fDEVANAGARI :
ch < 0xB80 ? 0 :
ch < 0xC00 ? fTAMIL : 0);
}
if(ch < 0xF00)
return ch < 0xE80 ? fTHAI : 0;
return ch >= 0x10A0 ? fGEORGIAN : fOTHER;
}
if(ch < 0x3100)
{
if(ch > 0x3040)
return fKANA;
if(ch >= 0x3000)
goto CLASSIFY_CHINESE;
if(IN_RANGE(RTLMARK, ch, 0x202E) && (ch == RTLMARK ||
IN_RANGE(0x202A, ch, 0x202E)))
{
return fBIDI;
}
if(ch <= 0x11FF) // Hangul Jamo
return fHANGUL;
if(ch == EURO || ch == 0x2122) // Euro or TM
return fHILATIN1;
if(ch == 0x20AA) // Hebrew currency sign
return fBIDI | fHEBREW;
if (W32->IsFESystem() || IsFECharSet(bDefaultCharset))
goto CLASSIFY_CHINESE;
if (IN_RANGE(0x200b, ch, 0x200d)) // ZWSP, ZWNJ, ZWJ
return fUNIC_CTRL;
if (ch == 0x2016 || ch == 0x2236)
{
// Some hack to make Word2000 happy
WCHAR wch = ch;
if (VerifyFEString(CP_CHINESE_TRAD, &wch, 1, TRUE) == CP_CHINESE_TRAD)
return fBIG5;
if (VerifyFEString(CP_CHINESE_SIM, &wch, 1, TRUE) == CP_CHINESE_SIM)
return fCHINESE;
}
return fOTHER;
}
if(ch < 0xD800)
{
if (ch < 0x3400)
{
if (IN_RANGE(0x3130, ch, 0x318F) || // Hangul Compatibility Jamo
IN_RANGE(0x3200, ch, 0x321F) || // Parenthesized Hangul
IN_RANGE(0x3260, ch, 0x327F)) // Circled Hangul
return fHANGUL;
if (IN_RANGE(0x032D0, ch, 0x337F)) // Circled & Squared Katakana words
return fKANA;
goto CLASSIFY_CHINESE;
}
if (ch < 0xAC00)
goto CLASSIFY_CHINESE;
return fHANGUL;
}
if (ch < 0xE000)
return fSURROGATE; // Surrogate
if(ch < 0xF900) // Private Use Area
{
if(IN_RANGE(0xF000, ch, 0xF0FF))
return fSYMBOL;
if (W32->IsFESystem())
goto CLASSIFY_USER;
return fOTHER;
}
if(ch < 0xFF00)
{
if(IN_RANGE(0xFE30, ch, 0xFE4F)) // CJK Vertical variants
goto CLASSIFY_CHINESE;
if(IN_RANGE(0xF900, ch, 0xFAFF)) // CJK characters
goto CLASSIFY_CHINESE;
return fOTHER;
}
if(IN_RANGE(0xFF00, ch, 0xFFEF))
{
if (ch < 0xFF60 || ch >= 0xFFE0 || // Fullwidth ASCII or Fullwidth symbols
ch == 0xFF64) // special case Half-width ideographic comma
goto CLASSIFY_CHINESE;
return ch < 0xFFA0 ? fKANA : fHANGUL; // Halfwidth Katakana/Hangul
}
return fOTHER;
CLASSIFY_CHINESE:
if (bDefaultCharset)
{
switch (bDefaultCharset)
{
case SHIFTJIS_CHARSET:
return fKANA;
case HANGEUL_CHARSET:
return fHANGUL;
case CHINESEBIG5_CHARSET:
return fBIG5;
case GB2312_CHARSET:
return fCHINESE;
}
}
CLASSIFY_USER:
switch (W32->GetFEFontInfo())
{
case CP_JAPAN:
return fKANA;
case CP_KOREAN:
return fHANGUL;
case CP_CHINESE_TRAD:
return fBIG5;
default:
// case CP_CHINESE_SIM:
return fCHINESE;
}
}
/*
* CLightDTEngine::CLightDTEngine()
*
* @mfunc
* Constructor for Light Data Transfer Engine
*/
CLightDTEngine::CLightDTEngine()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::CLightDTEngine");
_ped = NULL;
_pdt = NULL;
_pdo = NULL;
_fUseLimit = FALSE;
_fOleless = FALSE;
}
/*
* CLightDTEngine::~CLightDTEngine
*
* @mfunc
* Handles all necessary clean up for the object..
*/
CLightDTEngine::~CLightDTEngine()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::~CLightDTEngine");
if( _pdt )
{
_pdt->Zombie();
_pdt->Release();
_pdt = NULL;
}
Assert(_pdo == NULL);
}
/*
* CLightDTEngine::Destroy()
*
* @mfunc
* Deletes this instance
*/
void CLightDTEngine::Destroy()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::Destroy");
delete this;
}
/*
* CLightDTEngine::CopyRangeToClipboard ( prg )
*
* @mfunc
* Copy the text of the range prg to the clipboard using Win32 APIs
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::CopyRangeToClipboard(
CTxtRange *prg ) // @parm range to copy to clipboard
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::CopyRangeToClipboard");
HRESULT hresult = E_FAIL;
IDataObject *pdo = NULL;
IRichEditOleCallback * precall = _ped->GetRECallback();
BOOL fSingleObject;
CHARRANGE chrg;
prg->GetRange(chrg.cpMin, chrg.cpMost);
if (chrg.cpMin >= chrg.cpMost)
{
// We can't copy an insertion point to the clipboard so we are done.
return NOERROR;
}
fSingleObject = chrg.cpMost - chrg.cpMin == 1 &&
_ped->HasObjects() &&
_ped->_pobjmgr->CountObjectsInRange(chrg.cpMin, chrg.cpMost);
if(precall)
{
// Give the callback a chance to give us it's own IDataObject
hresult = precall->GetClipboardData(&chrg, RECO_COPY, &pdo);
}
// If we didn't get an IDataObject from the callback, build our own
if(hresult != NOERROR)
{
// If the range is empty, don't bother creating it. Just
// leave the clipboard alone and return
if( prg->GetCch() == 0 )
{
_ped->Beep();
return NOERROR;
}
hresult = RangeToDataObject(prg, SF_TEXT | SF_RTF, &pdo);
}
// NB: it's important to check both hresult && pdo; it is legal for
// our client to say "yep, I handled the copy, but there was nothing
// to copy".
if( hresult == NOERROR && pdo )
{
hresult = OleSetClipboard(pdo);
if( hresult != NOERROR )
{
HWND hwnd;
_fOleless = TRUE;
// Ole less clipboard support
if (_ped->TxGetWindow(&hwnd) == NOERROR &&
::OpenClipboard(hwnd) &&
::EmptyClipboard()
)
{
::SetClipboardData(cf_RTF, NULL);
::SetClipboardData(CF_UNICODETEXT, NULL);
if(_ped->GetCharFlags() & ~(fLATIN1 | fSYMBOL))
{
::SetClipboardData(cf_RTFUTF8, NULL);
::SetClipboardData(cf_RTFNCRFORNONASCII, NULL);
}
::SetClipboardData(CF_TEXT, NULL);
if (fSingleObject)
::SetClipboardData(CF_DIB, NULL);
::CloseClipboard();
hresult = NOERROR; // To cause replace range to happen
}
}
if(_pdo)
_pdo->Release();
_pdo = pdo;
}
return hresult;
}
/*
* CLightDTEngine::CutRangeToClipboard( prg, publdr );
*
* @mfunc
* Cut text of the range prg to the clipboard
*
* @devnote
* If publdr is non-NULL, anti-events for the cut operation should be
* stuffed into this collection
*
* @rdesc
* HRESULT from CopyRangeToClipboard()
*
* @devnote
* First copy the text to the clipboard, then delete it from the range
*/
HRESULT CLightDTEngine::CutRangeToClipboard(
CTxtRange * prg, // @parm Range to cut to clipboard
IUndoBuilder *publdr ) // @parm Undo builder to receive antievents
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::CutRangeToClipboard");
Assert(!_ped->TxGetReadOnly());
prg->AdjustEndEOP(NONEWCHARS); // Don't include trailing EOP
// in some selection cases
HRESULT hr = CopyRangeToClipboard(prg);
if( publdr )
{
publdr->SetNameID(UID_CUT);
publdr->StopGroupTyping();
}
if(hr == NOERROR) // Delete contents of range
prg->Delete(publdr, SELRR_REMEMBERRANGE);
return hr;
}
/*
* CLightDTEngine::FlushClipboard()
*
* @mfunc flushes the clipboard (if needed). Typically called during
* shutdown.
*
* @rdesc void
*/
void CLightDTEngine::FlushClipboard()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::FlushClipboard");
ENSAVECLIPBOARD ens;
if( _pdo )
{
if( OleIsCurrentClipboard(_pdo) == NOERROR )
{
CDataTransferObj *pdo = NULL;
// check to see if we have to flush the clipboard.
ZeroMemory(&ens, sizeof(ENSAVECLIPBOARD));
// check to make sure the object is one of ours before accessing
// the memory. EVIL HACK ALERT. 'nuff said.
if( _pdo->QueryInterface(IID_IRichEditDO, (void **)&pdo )
== NOERROR && pdo )
{
ens.cObjectCount = pdo->_cObjs;
ens.cch = pdo->_cch;
pdo->Release();
}
if( _ped->TxNotify(EN_SAVECLIPBOARD, &ens) == NOERROR )
OleFlushClipboard();
else
OleSetClipboard(NULL);
}
_pdo->Release();
_pdo = NULL;
}
}
/*
* CLightDTEngine::CanPaste(pdo, cf, flags)
*
* @mfunc
* Determines if clipboard format cf is one we can paste.
*
* @rdesc
* BOOL - true if we can paste cf into range prg OR DF_CLIENTCONTROL
* if the client is going to handle this one.
*
* @devnote
* we check the clipboard ourselves if cf is 0. Primarily, this
* is for backwards compatibility with Richedit1.0's EM_CANPASTE
* message.
*
*/
DWORD CLightDTEngine::CanPaste(
IDataObject *pdo, // @parm Data object to check; if NULL use clipboard
CLIPFORMAT cf, // @parm clipboard format to query about; if 0, use
// best available.
DWORD flags) // @parm flags
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::CanPaste");
IRichEditOleCallback *precall = _ped->GetRECallback();
CLIPFORMAT cf0 = cf;
DWORD cFETC = CFETC;
HRESULT hr = NOERROR;
DWORD ret = FALSE;
#ifndef MACPORT
if( pdo == NULL && precall )
#else
if( pdo == NULL)
#endif
{
// don't worry about errors
OleGetClipboard(&pdo);
}
else if( pdo )
{
// So we can make just one 'Release' call below
pdo->AddRef();
}
if( precall && pdo )
{
hr = precall->QueryAcceptData(pdo, &cf, flags, 0, NULL);
if( SUCCEEDED(hr) && (hr != S_OK && hr != DATA_S_SAMEFORMATETC ) )
{
ret = DF_CLIENTCONTROL;
goto Exit;
}
else if( FAILED(hr) && hr != E_NOTIMPL )
goto Exit;
else if(SUCCEEDED(hr))
{
// We should go on and check ourselves unless the client
// modified the format when it shouldn't have
if(cf0 && cf0 != cf)
goto Exit;
}
// otherwise, continue with our normal checks
}
if(_ped->TxGetReadOnly()) // Can't paste if read only
goto Exit;
while(cFETC--) // Does cf = format we can paste or
{ // is selection left up to us?
cf0 = g_rgFETC[cFETC].cfFormat;
if( cf == cf0 || !cf )
{
// Either we hit the format requested, or no format
// was requested. Now see if the format matches what
// we could handle in principle. There are three
// basic categories:
// 1. we are rich text and have an OLE callback;
// then we can handle pretty much everything.
// 2. we are rich text but have no OLE callback.
// then we can handle anything but OLE specific
// formats.
// 3. we are plain text only. Then we can only
// handle plain text formats.
if( (_ped->_fRich || (g_rgDOI[cFETC] & DOI_CANPASTEPLAIN)) &&
(precall || !(g_rgDOI[cFETC] & DOI_CANPASTEOLE)))
{
// once we get this far, make sure the data format
// is actually available.
if( (pdo && pdo->QueryGetData(&g_rgFETC[cFETC]) == NOERROR ) ||
(!pdo && IsClipboardFormatAvailable(cf0)) )
{
ret = TRUE; // Return arbitrary non zero value.
break;
}
}
}
}
Exit:
if(pdo)
pdo->Release();
return ret;
}
/*
* CLightDTEngine::RangeToDataObject (prg, lStreamFormat, ppdo)
*
* @mfunc
* Create data object (with no OLE-formats) for the range prg
*
* @rdesc
* HRESULT = !ppdo ? E_INVALIDARG :
* pdo ? NOERROR : E_OUTOFMEMORY
*/
HRESULT CLightDTEngine::RangeToDataObject(
CTxtRange * prg, // @parm Range to get DataObject for
LONG lStreamFormat, // @parm stream format to use for loading
IDataObject ** ppdo) // @parm Out parm for DataObject
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::RangeToDataObject");
if(!ppdo)
return E_INVALIDARG;
CDataTransferObj *pdo = CDataTransferObj::Create(_ped, prg, lStreamFormat);
*ppdo = pdo;
return pdo ? NOERROR : E_OUTOFMEMORY;
}
/*
* CLightDTEngine::RenderClipboardFormat(wFmt)
*
* @mfunc
* Renders current clipboard data object in specified format. (Ole less transfer)
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::RenderClipboardFormat(
WPARAM wFmt)
{
HRESULT hr = S_OK;
if(_fOleless &&
(wFmt == cf_RTF || wFmt == CF_UNICODETEXT || wFmt == CF_DIB || wFmt == CF_TEXT))
{
Assert(_pdo);
STGMEDIUM med;
DWORD iFETC = iUnicodeFETC;
if (wFmt == cf_RTF)
iFETC = iRtfFETC;
else if (wFmt == CF_DIB)
iFETC = iDIB;
else if (wFmt == CF_TEXT)
iFETC = iAnsiFETC;
med.tymed = TYMED_HGLOBAL;
med.pUnkForRelease = NULL;
med.hGlobal = NULL;
hr = _pdo->GetData(&g_rgFETC[iFETC], &med);
hr = hr || ::SetClipboardData(wFmt, med.hGlobal) == NULL;
}
return hr; // Pretend we did the right thing.
}
/*
* CLightDTEngine::RenderAllClipboardFormats()
*
* @mfunc
* Renders current clipboard data object (text and RTF). (Ole less transfer)
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::RenderAllClipboardFormats()
{
HRESULT hr;
if(_fOleless)
{
HWND howner = ::GetClipboardOwner();
HWND hwnd;
if (howner &&
_ped->TxGetWindow(&hwnd) == NOERROR &&
howner == hwnd &&
::OpenClipboard(hwnd))
{
::EmptyClipboard();
hr = RenderClipboardFormat(cf_RTF);
hr = hr || RenderClipboardFormat(CF_UNICODETEXT);
hr = hr || RenderClipboardFormat(CF_DIB);
hr = hr || RenderClipboardFormat(CF_TEXT);
::CloseClipboard();
return hr;
}
}
return S_OK; // Pretend we did the right thing.
}
/*
* CLightDTEngine::DestroyClipboard()
*
* @mfunc
* Destroys the clipboard data object
*
* @rdesc
* HRESULT
*
*/
HRESULT CLightDTEngine::DestroyClipboard()
{
// Nothing to do. This should work together with our Flush clipboard logic
return S_OK;
}
/*
* CLightDTEngine::HGlobalToRange(dwFormatIndex, hGlobal, ptext, prg, publdr)
*
* @mfunc
* Copies the contents of the given string (ptext) to the given range.
* The global memory handle may or may not point to the string depending
* on the format
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::HGlobalToRange(
DWORD dwFormatIndex,
HGLOBAL hGlobal,
LPTSTR ptext,
CTxtRange * prg,
IUndoBuilder *publdr)
{
READHGLOBAL rhg;
EDITSTREAM es;
HCURSOR hcur = NULL;
// If RTF, wrap EDITSTREAM around hGlobal & delegate to LoadFromEs()
if (dwFormatIndex == iRtfNoObjs || dwFormatIndex == iRtfFETC ||
dwFormatIndex == iRtfUtf8 || dwFormatIndex == iRtfNCRforNonASCII)
{
Assert(hGlobal != NULL);
rhg.ptext = (LPSTR)ptext; // Start at beginning
rhg.cbLeft = GlobalSize(hGlobal); // with full length
es.dwCookie = (DWORD_PTR)&rhg; // The read "this" ptr
es.dwError = NOERROR; // No errors yet
es.pfnCallback = ReadHGlobal; // The read method
// Want wait cursor to display sooner
bool fSetCursor = rhg.cbLeft > NUMPASTECHARSWAITCURSOR;
if (fSetCursor)
hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
LoadFromEs(prg, SFF_SELECTION | SF_RTF, &es, TRUE, publdr);
if (fSetCursor)
SetCursor(hcur);
return es.dwError;
}
Assert( dwFormatIndex == iRtfAsTextFETC ||
dwFormatIndex == iAnsiFETC ||
dwFormatIndex == iUnicodeFETC );
LONG cchMove, cchNew;
cchNew = prg->CleanseAndReplaceRange(-1, ptext, TRUE, publdr, NULL, &cchMove, RR_ITMZ_NONE);
if(prg->GetCch() && prg->IsSel())
return E_FAIL; // Paste failed due to UI rules
if(_ped->IsRich() && !_ped->Get10Mode())// If rich text,
prg->DeleteTerminatingEOP(publdr); // if new text ends with EOP,
prg->ItemizeReplaceRange(cchNew, cchMove, publdr, TRUE); // itemize w/ UnicodeBidi
// select and delete that EOP
return NOERROR; // to agree with Word
}
/*
* CLightDTEngine::DIBToRange(hGlobal, prg, publdr)
*
* @mfunc
* Inserts dib data from the clipboard into range in the control
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::DIBToRange(
HGLOBAL hGlobal,
CTxtRange * prg,
IUndoBuilder * publdr)
{
HRESULT hresult = DV_E_FORMATETC;
REOBJECT reobj = { 0 };
LPBITMAPINFO pbmi = (LPBITMAPINFO) GlobalLock(hGlobal);
WCHAR ch = WCH_EMBEDDING;
reobj.clsid = CLSID_StaticDib;
reobj.sizel.cx =
(LONG) _ped->_pdp->DXtoHimetricX( pbmi->bmiHeader.biWidth );
reobj.sizel.cy =
(LONG) _ped->_pdp->DYtoHimetricY( pbmi->bmiHeader.biHeight );
_ped->GetClientSite(&reobj.polesite);
COleObject *pobj = (COleObject *)reobj.polesite;
COleObject::ImageInfo *pimageinfo = new COleObject::ImageInfo;
pobj->SetHdata(hGlobal);
pimageinfo->xScale = 100;
pimageinfo->yScale = 100;
pimageinfo->xExtGoal = reobj.sizel.cx;
pimageinfo->yExtGoal = reobj.sizel.cy;
pimageinfo->cBytesPerLine = 0;
pobj->SetImageInfo(pimageinfo);
// FUTURE: Why are we not testing for NULL earlier before we assign it to pobj? v-honwch
// Also, do we need to release interfaces inside reobj (poleobj, polesite, pstg) before exit?
if (!reobj.polesite )
return hresult;
// Put object into the edit control
reobj.cbStruct = sizeof(REOBJECT);
reobj.cp = prg->GetCp();
reobj.dvaspect = DVASPECT_CONTENT;
reobj.dwFlags = REO_RESIZABLE;
// Since we are loading an object, it shouldn't be blank
reobj.dwFlags &= ~REO_BLANK;
prg->Set_iCF(-1);
prg->ReplaceRange(1, &ch, publdr, SELRR_IGNORE);
hresult = _ped->GetObjectMgr()->InsertObject(reobj.cp, &reobj, NULL);
return hresult;
}
/*
* CLightDTEngine::PasteDataObjectToRange (pdo, prg, cf, rps, pubdlr, dwFlags)
*
* @mfunc
* Inserts data from the data object pdo into the range prg. If the
* clipboard format cf is not NULL, that format is used; else the highest
* priority clipboard format is used. In either case, any text that
* already existed in the range is replaced. If pdo is NULL, the
* clipboard is used.
*
* @rdesc
* HRESULT
*
*/
HRESULT CLightDTEngine::PasteDataObjectToRange(
IDataObject * pdo, // @parm Data object to paste
CTxtRange * prg, // @parm Range into which to paste
CLIPFORMAT cf, // @parm ClipBoard format to paste
REPASTESPECIAL *rps, // @parm Special paste info
IUndoBuilder * publdr, // @parm Undo builder to receive antievents
DWORD dwFlags) // @parm DWORD packed flags
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::PasteDataObjectToRange");
if(prg->GetPF()->InTable())
{
if(prg->GetPrevChar() == CELL && prg->_rpTX.GetChar() == CR)
{
if(prg->IsSel())
{
_ped->Beep();
return E_FAIL;
}
prg->SetExtend(FALSE); // Illegal paste point
prg->Advance(-1);
}
if(prg->IsSel())
{
if(prg->fHasCell()) // Can only paste inside single cell
return E_FAIL;
}
else if(prg->GetCch()) // Would use _fSelHasCell, but isn't
return E_FAIL; // maintained unless _fSel is TRUE
}
BOOL f10Mode = _ped->Get10Mode();
HGLOBAL hGlobal = NULL;
HRESULT hresult = DV_E_FORMATETC;
HGLOBAL hUnicode = NULL;
DWORD i;
STGMEDIUM medium = {0, NULL};
IDataObject *pdoSave = pdo;
FORMATETC * pfetc = g_rgFETC;
LPTSTR ptext = NULL;
LPRICHEDITOLECALLBACK const precall = _ped->GetRECallback();
BOOL fThawDisplay = FALSE;
BOOL bFormatFound = FALSE; // flag which determines if a matching cf format
// was found in g_rgFETC (1.0 compatibility)
if(!pdo) // No data object: use clipboard
{
hresult = OleGetClipboard(&pdo);
if(FAILED(hresult))
{
// Ooops. No Ole clipboard support
// Need to use direct clipboard access
HWND howner = ::GetClipboardOwner();
HWND hwnd;
if (howner &&
_ped->TxGetWindow(&hwnd) == NOERROR &&
howner == hwnd)
{
// We are cut/pasting within the same richedit instance
// Use our cached clipboard data object
pdo = _pdo;
if(!pdo) // Some failure
{
_ped->Beep();
return hresult;
}
pdo->AddRef();
}
else
{
// Oh Oh We need to transfer from clipboard without data object
// Data must be coming from another window instance
if (_ped->TxGetWindow(&hwnd) == NOERROR &&
::OpenClipboard(hwnd)
)
{
HGLOBAL hUnicode = NULL;
DWORD dwFmt = iRtfUtf8; // Try for UTF8 RTF
_ped->_pdp->Freeze();
if(!f10Mode)
{
hGlobal = ::GetClipboardData(cf_RTFUTF8);
if (hGlobal == NULL) // Wasn't there, so
{ // try for RTF
hGlobal = ::GetClipboardData(cf_RTFNCRFORNONASCII);
dwFmt = iRtfNCRforNonASCII;
}
}
if (hGlobal == NULL) // Wasn't there, so
{ // try for RTF
hGlobal = ::GetClipboardData(cf_RTF);
dwFmt = iRtfFETC;
}
if (hGlobal == NULL && !f10Mode) // Wasn't there either
{ // so try for plain
hGlobal = ::GetClipboardData(CF_UNICODETEXT);
dwFmt = iUnicodeFETC;
}
if (hGlobal == NULL) // Wasn't there either
{ // so try for plain text
hGlobal = ::GetClipboardData(CF_TEXT);
dwFmt = iAnsiFETC;
}
if (hGlobal)
{
if (dwFmt == iAnsiFETC)
{
// Convert Ansi plain text to Unicode
hUnicode = TextHGlobalAtoW(hGlobal);
if (hUnicode)
ptext = (LPTSTR)GlobalLock(hUnicode);
}
else
ptext = (LPTSTR)GlobalLock(hGlobal);
if (ptext)
hresult = HGlobalToRange(dwFmt, hGlobal, ptext, prg, publdr);
else
hresult = E_OUTOFMEMORY;
if (hUnicode)
{
// Free plain text buffer
GlobalUnlock(hUnicode);
GlobalFree(hUnicode);
}
else
GlobalUnlock(hGlobal);
}
else // hGlobal == NULL Try for bitmaps
{
hGlobal = ::GetClipboardData(CF_DIB);
if (hGlobal)
hresult = DIBToRange(hGlobal, prg, publdr);
}
_ped->_pdp->Thaw();
::CloseClipboard();
}
if (FAILED(hresult))
_ped->Beep();
return hresult;
}
}
}
// Paste an object uses the limit text calculation
_fUseLimit = TRUE;
//Call QueryAcceptData unless caller has specified otherwise
if(!(dwFlags & PDOR_NOQUERY) && precall)
{
CLIPFORMAT cfReq = cf;
HGLOBAL hmeta = NULL;
if(rps)
hmeta = (HGLOBAL)((rps->dwAspect == DVASPECT_ICON) ? rps->dwParam : NULL);
// Ask callback if it likes the data object and cfReq.
hresult = precall->QueryAcceptData(
pdo,
&cfReq,
(dwFlags & PDOR_DROP) ? RECO_DROP : RECO_PASTE,
TRUE,
hmeta);
if(hresult == DATA_S_SAMEFORMATETC)
{
// Allow callback to return DATA_S_SAMEFORMATETC if it only
// wants cf as passed in - we don't really care because
// any non-zero CLIPFORMAT causes us to only accept that format.
hresult = S_OK;
}
if(hresult == S_OK || hresult == E_NOTIMPL)
{
// Callback either liked it or didn't implement the method.
// It may have changed the format while it was at it.
// Treat a change of cf to zero as acceptance of the original.
// In any event, we will try to handle it.
// If a specific CLIPFORMAT was originally requested and the
// callback changed it, don't accept it.
if(cfReq && cf && (cf != cfReq))
{
hresult = DV_E_FORMATETC;
goto Exit;
}
// If a specific CLIPFORMAT was originally requested and the
// callback either left it alone or changed it to zero,
// make sure we use the original. If no CLIPFORMAT was
// originally requested, make sure we use what came back
// from the callback.
if(!cf)
cf = cfReq;
}
else
{
// Some success other than S_OK && DATA_S_SAMEFORMATETC.
// The callback has handled the paste. OR some error
// was returned.
goto Exit;
}
}
// Even if the rich edit client wants CF_TEXT
// If the data object supports CF_UNICODETEXT, we should prefer it.
// as long as we are not in 1.0 mode
if(cf == CF_TEXT && !f10Mode)
{
FORMATETC fetc = {CF_UNICODETEXT, NULL, 0, -1, TYMED_NULL};
if(pdo->QueryGetData(&fetc) == S_OK)
cf = CF_UNICODETEXT;
}
if (_ped->TxGetReadOnly()) // Should check for range protection
{
hresult = E_ACCESSDENIED;
goto Exit;
}
// At this point we freeze the display
fThawDisplay = TRUE;
_ped->_pdp->Freeze();
if( publdr )
{
publdr->StopGroupTyping();
publdr->SetNameID(UID_PASTE);
}
for( i = 0; i < CFETC; i++, pfetc++ )
{
// Make sure the format is either 1.) a plain text format
// if we are in plain text mode or 2.) a rich text format
// or 3.) matches the requested format.
if( cf && cf != pfetc->cfFormat )
continue;
if( _ped->IsRich() || (g_rgDOI[i] & DOI_CANPASTEPLAIN) )
{
// Make sure format is available
if( pdo->QueryGetData(pfetc) != NOERROR )
continue;
// If we have a format that uses an hGlobal get and lock it
if (i == iRtfFETC || i == iRtfAsTextFETC ||
i == iAnsiFETC || i == iRtfNoObjs ||
!f10Mode && (i == iUnicodeFETC || i == iRtfUtf8 || i == iRtfNCRforNonASCII))
{
if( pdo->GetData(pfetc, &medium) != NOERROR )
continue;
hGlobal = medium.hGlobal;
ptext = (LPTSTR)GlobalLock(hGlobal);
if( !ptext )
{
ReleaseStgMedium(&medium);
hresult = E_OUTOFMEMORY;
goto Exit;
}
// 1.0 COMPATBILITY HACK ALERT! RichEdit 1.0 has a bit of
// "error recovery" for parsing rtf files; if they aren't
// valid rtf, it treats them as just plain text.
// Unfortunately, apps like Exchange depend on this behavior,
// i.e., they give RichEdit plain text data, but call it rich
// text anyway. Accordingly, we emulate 1.0 behavior here by
// checking for an rtf signature.
if ((i == iRtfFETC || i == iRtfNoObjs || i == iRtfUtf8) &&
!IsRTF((char *)ptext))
{
i = iAnsiFETC; // Not RTF, make it ANSI text
}
}
else if (f10Mode && (i == iUnicodeFETC || i == iRtfUtf8))
{
// This else handles the case where we want to keep searching
// for a goood format. i.e. Unicode in 10 Mode
continue;
}
// Don't delete trail EOP in some cases
prg->AdjustEndEOP(NONEWCHARS);
// Found a format we want.
bFormatFound = TRUE;
switch(i)
{
case iRtfNoObjs:
case iRtfFETC:
case iRtfUtf8:
case iRtfNCRforNonASCII:
hresult = HGlobalToRange(i, hGlobal, ptext, prg, publdr);
break;
case iRtfAsTextFETC:
case iAnsiFETC: // ANSI plain text
hUnicode = TextHGlobalAtoW(hGlobal);
ptext = (LPTSTR)GlobalLock(hUnicode);
if(!ptext)
{
hresult = E_OUTOFMEMORY; // Unless out of RAM,
break; // fall thru to
} // Unicode case
case iUnicodeFETC: // Unicode plain text
// Ok to pass in NULL for hglobal since argument won't be used
hresult = HGlobalToRange(i, NULL, ptext, prg, publdr);
if(hUnicode) // For iAnsiFETC case
{
GlobalUnlock(hUnicode);
GlobalFree(hUnicode);
}
break;
case iObtDesc: // Object Descriptor
continue; // To search for a good format.
// the object descriptor hints will be used
// when the format is found.
case iEmbObj: // Embedded Object
case iEmbSrc: // Embed Source
case iLnkSrc: // Link Source
case iMfPict: // Metafile
case iDIB: // DIB
case iBitmap: // Bitmap
case iFilename: // Filename
hresult = CreateOleObjFromDataObj(pdo, prg, rps, i, publdr);
break;
// COMPATIBILITY ISSUE (v-richa) iTxtObj is needed by Exchange and
// as a flag for Wordpad. iRichEdit doesn't seem to be needed by
// anyone but might consider implementing as a flag.
case iRichEdit: // RichEdit
case iTxtObj: // Text with Objects
break;
default:
// Ooops didn't find a format after all
bFormatFound = FALSE;
break;
}
//If we used the hGlobal unlock it and free it.
if(hGlobal)
{
GlobalUnlock(hGlobal);
ReleaseStgMedium(&medium);
}
break; //Break out of for loop
}
}
// richedit 1.0 returned an error if an unsupported FORMATETC was
// found. This behaviour is expected by ccMail so it can handle the
// format itself
if (!bFormatFound && f10Mode)
hresult = DV_E_FORMATETC;
Exit:
if (fThawDisplay)
_ped->_pdp->Thaw();
if(!pdoSave) // Release data object
pdo->Release(); // used for clipboard
return hresult;
}
/*
* CLightDTEngine::GetDropTarget (ppDropTarget)
*
* @mfunc
* creates an OLE drop target
*
* @rdesc
* HRESULT
*
* @devnote The caller is responsible for AddRef'ing this object
* if appropriate.
*/
HRESULT CLightDTEngine::GetDropTarget(
IDropTarget **ppDropTarget) // @parm outparm for drop target
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::GetDropTarget");
if(!_pdt)
{
_pdt = new CDropTarget(_ped);
// the AddRef done by the constructor will be
// undone by the destructor of this object
}
if(ppDropTarget)
*ppDropTarget = _pdt;
return _pdt ? NOERROR : E_OUTOFMEMORY;
}
/*
* CLightDTEngine::StartDrag (psel, publdr)
*
* @mfunc
* starts the main drag drop loop
*
*/
HRESULT CLightDTEngine::StartDrag(
CTxtSelection *psel, // @parm Selection to drag from
IUndoBuilder *publdr) // @parm undo builder to receive antievents
{
#ifndef PEGASUS
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::StartDrag");
LONG cch, cch1;
LONG cp1, cpMin, cpMost;
DWORD dwEffect = 0;
HRESULT hr;
IDataObject * pdo = NULL;
IDropSource * pds;
IRichEditOleCallback * precall = _ped->GetRECallback();
// If we're doing drag drop's, we should have our own drop target
// It's possible that _pdt will be NULL at this point--some clients
// will delay instantiation of our drop target until a drop target
// in the parent window decides that ours is needed. However, since
// we need it just to initiate drag drop, go ahead and create one
// here.
if( _pdt == NULL )
{
hr = GetDropTarget(NULL);
if(hr != NOERROR)
return hr;
}
psel->CheckTableSelection();
if(precall)
{
CHARRANGE chrg;
// give the callback a chance to give us its own IDataObject
psel->GetRange(chrg.cpMin, chrg.cpMost);
hr = precall->GetClipboardData(&chrg, RECO_COPY, &pdo);
}
else
{
// we need to build our own data object.
hr = S_FALSE;
}
// If we didn't get an IDataObject from the callback, build our own
if(hr != NOERROR || pdo == NULL)
{ // Don't include trailing EOP
psel->AdjustEndEOP(NONEWCHARS); // in some selection cases
hr = RangeToDataObject(psel, SF_TEXT | SF_RTF, &pdo);
if(hr != NOERROR)
return hr;
}
cch = psel->GetRange(cpMin, cpMost); // NB: prg is the selection
cp1 = psel->GetCp(); // Save active end and signed
cch1 = psel->GetCch(); // length for Undo antievent
CTxtRange rg(_ped, cpMost, cch); // Use range copy to float over
// mods made to backing store
// The floating range that we just created on the stack needs to
// think that it's protected, so it won't change size.
rg.SetDragProtection(TRUE);
pds = new CDropSource();
if(pds == NULL)
{
pdo->Release();
return E_OUTOFMEMORY;
}
// Cache some info with our own drop target
_pdt->SetDragInfo(publdr, cpMin, cpMost);
// Set allowable effects
dwEffect = DROPEFFECT_COPY;
if(!_ped->TxGetReadOnly())
dwEffect |= DROPEFFECT_MOVE;
// Let the client decide what it wants.
if(precall)
hr = precall->GetDragDropEffect(TRUE, 0, &dwEffect);
if(!FAILED(hr) || hr == E_NOTIMPL)
{
// Start drag-drop operation
psel->AddRef(); // Stabilize Selection around DoDragDrop
hr = DoDragDrop(pdo, pds, dwEffect, &dwEffect);
psel->Release();
}
// Clear drop target
_pdt->SetDragInfo(NULL, -1, -1);
// Handle 'move' operations
if( hr == DRAGDROP_S_DROP && (dwEffect & DROPEFFECT_MOVE) )
{
// We're going to delete the dragged range, so turn off protection.
rg.SetDragProtection(FALSE);
if( publdr )
{
LONG cpNext, cchNext;
if(_ped->GetCallMgr()->GetChangeEvent() )
{
cpNext = cchNext = -1;
}
else
{
cpNext = rg.GetCpMin();
cchNext = 0;
}
HandleSelectionAEInfo(_ped, publdr, cp1, cch1, cpNext, cchNext,
SELAE_FORCEREPLACE);
}
// Delete the data that was moved. The selection will float
// to the new correct location.
rg.Delete(publdr, SELRR_IGNORE);
// The update that happens implicitly by the update of the range may
// have the effect of scrolling the window. This in turn may have the
// effect in the drag drop case of scrolling non-inverted text into
// the place where the selection was. The logic in the selection
// assumes that the selection is inverted and so reinverts it to turn
// off the selection. Of course, it is obvious what happens in the
// case where non-inverted text is scrolled into the selection area.
// To simplify the processing here, we just say the whole window is
// invalid so we are guaranteed to get the right painting for the
// selection.
// FUTURE: (ricksa) This solution does have the disadvantage of causing
// a flash during drag and drop. We probably want to come back and
// investigate a better way to update the screen.
_ped->TxInvalidateRect(NULL, FALSE);
// Display is updated via notification from the range
// Update the caret
psel->Update(TRUE);
}
else if( hr == DRAGDROP_S_DROP && _ped->GetCallMgr()->GetChangeEvent() &&
(dwEffect & DROPEFFECT_COPY) && publdr)
{
// if we copied to ourselves, we want to restore the selection to
// the original drag origin on undo
HandleSelectionAEInfo(_ped, publdr, cp1, cch1, -1, -1,
SELAE_FORCEREPLACE);
}
if(SUCCEEDED(hr))
hr = NOERROR;
pdo->Release();
pds->Release();
// we do this last since we may have re-used some 'paste' code which
// will stomp the undo name to be UID_PASTE.
if( publdr )
publdr->SetNameID(UID_DRAGDROP);
if(_ped->GetEventMask() & ENM_DRAGDROPDONE)
{
NMHDR hdr;
ZeroMemory(&hdr, sizeof(NMHDR));
_ped->TxNotify(EN_DRAGDROPDONE, &hdr);
}
return hr;
#else
return 0;
#endif
}
/*
* CLightDTEngine::LoadFromEs (prg, lStreamFormat, pes, fTestLimit, publdr)
*
* @mfunc
* Load data from the stream pes into the range prg according to the
* format lStreamFormat
*
* @rdesc
* LONG -- count of characters read
*/
LONG CLightDTEngine::LoadFromEs(
CTxtRange * prg, // @parm range to load into
LONG lStreamFormat, // @parm stream format to use for loading
EDITSTREAM *pes, // @parm edit stream to load from
BOOL fTestLimit, // @parm Whether to test text limit
IUndoBuilder *publdr) // @parm undo builder to receive antievents
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::LoadFromEs");
#ifdef DEBUG
// FUTURE: Currently freezing the display prior to loading text
// is simply an optimization. This may become a requirement in the
// future. If this does become a requirement then we'll want to
// exit with an error.
if( !_ped->_pdp->IsFrozen() )
{
TRACEWARNSZ("CLightDTEngine::LoadFromEs display not frozen");
}
#endif // DEBUG
LONG cch = 0; // Default no chars read
IAntiEvent *pae = NULL;
if(publdr)
publdr->StopGroupTyping();
// Other components, such as the display and backing store, will
// be able to make optimizations if they know that we are streaming
// in text or RTF data.
if(lStreamFormat & SF_RTF) // RTF case must precede
{ // TEXT case (see SF_x
if(!_ped->IsRich()) // values)
return 0;
LONG cpMin, cpMost;
// Here we do something a bit unusual for performance reasons.
// Instead of letting the rtf reader generate its own undo actions,
// we take care of it ourselves. Instead of generating actions
// for each little operation, we simply generate a "big" anti-event
// for the whole shebang
// There is a subtlty w.r.t. to paragraph format runs. By inserting
// text with para formatting, it's possible that we will modify the
// para formatting of the _current_ paragraph. Thus, it's necessary
// to remember what the formatting currently is for undo. Note that
// it may actually not be changed; but we go ahead and generate an
// anti-event anyways. Note that we only need to do this if cpMin is
// the middle of a paragraph
CTxtPtr tp(prg->_rpTX);
if(prg->GetCch() > 0)
tp.AdvanceCp(-prg->GetCch());
if(publdr && !tp.IsAfterEOP())
{
tp.FindEOP(tomBackward);
cpMin = tp.GetCp();
tp.FindEOP(tomForward);
cpMost = tp.GetCp();
// We must be in rich text mode, so we must be able to always
// find a paragraph.
Assert(cpMost > cpMin);
if (prg->_rpPF.IsValid())
{
CFormatRunPtr rpPF(prg->_rpPF);
rpPF.AdvanceCp(cpMin - prg->GetCp());
pae = gAEDispenser.CreateReplaceFormattingAE( _ped, rpPF,
cpMost - cpMin, GetParaFormatCache(), ParaFormat);
if(pae)
publdr->AddAntiEvent(pae);
}
// Also create the charformat anti-event for the current paragraph
// to preserve BiDi level. We cannot check fBiDi here since we may be running
// on US platform inserting a BiDi rtf.
if (prg->_rpCF.IsValid())
{
CFormatRunPtr rpCF(prg->_rpCF);
rpCF.AdvanceCp(cpMin - prg->GetCp());
pae = gAEDispenser.CreateReplaceFormattingAE( _ped, rpCF,
cpMost - cpMin, GetCharFormatCache(), CharFormat);
if(pae)
publdr->AddAntiEvent(pae);
}
}
// First, clear range
if(prg->GetCch())
{
prg->ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);
if (prg->GetCch() != 0)
{
// Text deletion failed because range didn't collapse. Our work
// here is done.
return 0;
}
}
Assert(prg->GetCch() == 0);
cpMin = prg->GetCp();
_ped->SetStreaming(TRUE);
CRTFRead rtfRead(prg, pes, lStreamFormat);
cch = rtfRead.ReadRtf();
cpMost = prg->GetCp();
Assert(pes->dwError != 0 || cpMost >= cpMin);
// If nothing changed, get rid of any anti-events (like the formatting
// one) that we may have "speculatively" added
if(publdr && !_ped->GetCallMgr()->GetChangeEvent())
publdr->Discard();
if(publdr && cpMost > cpMin)
{
// If some text was added, create an anti-event for
// it and add it in.
AssertSz(_ped->GetCallMgr()->GetChangeEvent(),
"Something changed, but nobody set the change flag");
pae = gAEDispenser.CreateReplaceRangeAE(_ped, cpMin, cpMost, 0,
NULL, NULL, NULL);
HandleSelectionAEInfo(_ped, publdr, -1, -1, cpMost, 0,
SELAE_FORCEREPLACE);
if(pae)
publdr->AddAntiEvent(pae);
}
}
else if(lStreamFormat & SF_TEXT)
{
_ped->SetStreaming(TRUE);
cch = ReadPlainText(prg, pes, fTestLimit, publdr, lStreamFormat);
}
_ped->SetStreaming(FALSE);
// Before updating the selection, try the auto-URL detect. This makes
// two cases better: 1. a long drag drop is now faster and 2. the
// selection _iFormat will now be udpated correctly for cases of
// copy/paste of a URL.
if(_ped->GetDetectURL())
_ped->GetDetectURL()->ScanAndUpdate(publdr);
// The caret belongs in one of two places:
// 1. if we loaded into a selection, at the end of the new text
// 2. otherwise, we loaded an entire document, set it to cp 0
//
// ReadPlainText() and ReadRtf() set prg to an insertion point
// at the end, so if we loaded a whole document, reset it.
CTxtSelection *psel = _ped->GetSelNC();
if(psel)
{
if(!(lStreamFormat & SFF_SELECTION))
{
psel->Set(0,0);
psel->Update(FALSE);
}
psel->Update_iFormat(-1);
}
if (!fTestLimit)
{
// If we don't limit the text then we adjust the text limit
// if we have exceeded it.
_ped->TxSetMaxToMaxText();
}
return cch;
}
/*
* CLightDTEngine::SaveToEs (prg, lStreamFormat, pes)
*
* @mfunc
* save data into the given stream
*
* @rdesc
* LONG -- count of characters written
*/
LONG CLightDTEngine::SaveToEs(
CTxtRange * prg, // @parm range to drag from
LONG lStreamFormat, // @parm stream format to use for saving
EDITSTREAM *pes ) // @parm edit stream to save to
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::SaveToEs");
LONG cch = 0; // Default no chars written
if(lStreamFormat & SF_RTF) // Be sure to check for SF_RTF
{ // before checking for SF_TEXT
CRTFWrite rtfWrite( prg, pes, lStreamFormat );
cch = rtfWrite.WriteRtf();
}
else if(lStreamFormat & (SF_TEXT | SF_TEXTIZED))
cch = WritePlainText(prg, pes, lStreamFormat);
else
{
Assert(FALSE);
}
return cch;
}
/*
* CLightDTEngine::UnicodePlainTextFromRange (prg)
*
* @mfunc
* Fetch plain text from a range and put it in an hglobal
*
* @rdesc
* an allocated HGLOBAL.
*
* @devnote
* FUTURE: Export bullets as does Word for plain text
*/
HGLOBAL CLightDTEngine::UnicodePlainTextFromRange(
CTxtRange *prg) // @parm range to get text from
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::UnicodePlainTextFromRange");
LONG cpMin, cpMost;
LONG cch = prg->GetRange(cpMin, cpMost);
LONG cchT = 2*(cch + 1);
HGLOBAL hText;
TCHAR * pText;
CTxtPtr tp(_ped, cpMin);
hText = GlobalAlloc(GMEM_FIXED, // Allocate 2* in
cchT * sizeof(TCHAR) ); // case all CRs
if(!hText)
return NULL;
pText = (TCHAR *)GlobalLock(hText);
if(!pText)
return NULL;
if(cch)
{
cch = tp.GetPlainText(cchT, pText, cpMost, FALSE);
AssertSz(cch <= cchT,
"CLightDTEngine::UnicodePlainTextFromRange: got too much text");
}
*(pText + cch) = '\0';
GlobalUnlock(hText);
HGLOBAL hTemp = GlobalReAlloc(hText, 2*(cch + 1), GMEM_MOVEABLE);
if(!hTemp)
GlobalFree(hText);
return hTemp;
}
/*
* CLightDTEngine::AnsiPlainTextFromRange (prg)
*
* @mfunc
* Retrieve an ANSI copy of the text in the range prg
*
* @rdesc
* HRESULT
*/
HGLOBAL CLightDTEngine::AnsiPlainTextFromRange(
CTxtRange *prg) // @parm range to get text from
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::AnsiPlainTextFromRange");
HGLOBAL hUnicode;
HGLOBAL hAnsi;
// FUTURE (alexgo): if we implement the option to store text as 8-bit
// chars, then we can make this routine more efficient
hUnicode = UnicodePlainTextFromRange(prg);
hAnsi = TextHGlobalWtoA(hUnicode);
GlobalFree(hUnicode);
return hAnsi;
}
/*
* CLightDTEngine::RtfFromRange (prg, lStreamFormat)
*
* @mfunc
* Fetch RTF text from a range and put it in an hglobal
*
* @rdesc
* an allocated HGLOBAL.
*/
HGLOBAL CLightDTEngine::RtfFromRange(
CTxtRange * prg, // @parm Range to get RTF from
LONG lStreamFormat) // @parm stream format to use for loading
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::RtfFromRange");
WRITEHGLOBAL whg;
EDITSTREAM es = {(DWORD_PTR)&whg, NOERROR, WriteHGlobal};
DWORD cb = 2*abs(prg->GetCch()) + 100; // Rough estimate
whg.cb = cb;
whg.hglobal = GlobalAlloc(GMEM_FIXED, cb);
if(!whg.hglobal)
return NULL;
whg.cch = 0; // Nothing written yet
SaveToEs(prg, lStreamFormat & ~SF_TEXT, &es);
if(es.dwError)
{
GlobalFree(whg.hglobal);
return NULL;
}
HGLOBAL hTemp = GlobalReAlloc(whg.hglobal, whg.cch, GMEM_MOVEABLE);
if (!hTemp)
GlobalFree(whg.hglobal); // Fail ReAlloc...
return hTemp;
}
//
// PROTECTED METHODS
//
#define READSIZE 4096 - 2
#define WRITESIZE 2048
/*
* CLightDTEngine::ReadPlainText (prg, pes, publdr, lStreamFormat)
*
* @mfunc
* Replaces contents of the range prg with the data given in the edit
* stream pes. Handles multibyte sequences that overlap stream buffers.
*
* @rdesc
* Count of bytes read (to be compatible with RichEdit 1.0)
*
* @devnote
* prg is modified; at the return of the call, it will be a degenerate
* range at the end of the read in text.
*
* Three kinds of multibyte/char sequences can overlap stream buffers:
* DBCS, UTF-8, and CRLF/CRCRLF combinations. DBCS and UTF-8 streams are
* converted by MultiByteToWideChar(), which cannot convert a lead byte
* (DBCS and UTF-8) that occurs at the end of the buffer, since the
* corresponding trail byte(s) will be in the next buffer. Similarly,
* in RichEdit 2.0 mode, we convert CRLFs to CRs and CRCRLFs to blanks,
* so one or two CRs at the end of the buffer require knowledge of the
* following char to determine if they are part of a CRLF or CRCRLF.
*
* To handle these overlapped buffer cases, we move the ambiguous chars
* to the start of the next buffer, rather than keeping them as part of
* the current buffer. At the start of the buffer, the extra char(s)
* needed for translation follow immediately.
*/
LONG CLightDTEngine::ReadPlainText(
CTxtRange * prg, // @parm range to read to
EDITSTREAM * pes, // @parm edit stream to read from
BOOL fTestLimit, // @parm whether limit testing is needed
IUndoBuilder *publdr, // @parm undo builder to receive antievents
LONG lStreamFormat)// @parm Stream format
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::ReadPlainText");
CTxtEdit *ped = _ped;
LONG cbRead;
LONG cbReadTotal = 0; // No bytes read yet
LONG cchConv;
LONG cchMove = 0;
LONG cCR = 0; // Count of CRs from preceding buffer
LONG cCRPrev = 0; // Count used while calc'ing new cCR
LONG cpMin;
BOOL fContinue = TRUE; // Keep reading so long as TRUE
BYTE * pb; // Byte ptr to szBuf or wszBuf
CCallMgr *pCallMgr = ped->GetCallMgr();
TCHAR * pch; // Ptr to wszBuf
UINT uCpg = GetStreamCodePage(lStreamFormat);
CFreezeDisplay fd(ped->_pdp);
// Just put a big buffer on the stack. Thankfully, we only
// run on 32bit OS's. 4K is a good read size for NT file caching.
char szBuf[READSIZE];
WCHAR wszBuf[READSIZE+2]; // Allow for moving end CRs to start
// Empty the range
if(prg->GetCch())
prg->ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE, &cchMove);
cpMin = prg->GetCp(); // Save initial cp for
// BreakRuns() at end
pb = (uCpg == 1200) ? (BYTE *)(wszBuf + 2) // Setup Unicode or MBCS
: (BYTE *)szBuf;
LONG j = 0; // Haven't read anything,
// so no lead byte left
while(fContinue) // from previous read
{
LONG jPrev = j; // Save byte(s) left over
LONG cbSkip = 0; // from previous read
pes->dwError = (*pes->pfnCallback)( // Read next bufferful,
pes->dwCookie, pb + j, // bypassing any lead
READSIZE - j, &cbRead); // bytes
if(pes->dwError || !cbRead && !cCR)
break; // Error or done
if(!cbReadTotal && cbRead >= 3 && W32->IsUTF8BOM(pb))
{
uCpg = CP_UTF8;
cbSkip = 3; // Bypass 3 bytes
}
// Adjust cbRead with previous leading byte(s)
cbRead += j;
j = 0;
cchConv = cbRead/2; // Default Unicode cch
if(uCpg != 1200 && cbRead) // Multibyte of some kind
{
Assert(pb == (BYTE *)szBuf && !j); // Just in case...
// Check if last byte is a leading byte
if(uCpg == CP_UTF8)
{
// Note: Unlike UTF-8, UTF-7 can be in the middle of a long
// sequence, so it can't be converted effectively in chunks
// and we don't handle it
LONG cb = cbRead - 1;
BYTE b;
BYTE bLeadMax = 0xDF;
// Find UTF-8 lead byte
while((b = (BYTE)szBuf[cb - j]) >= 0x80)
{
j++;
if(b >= 0xC0) // Break on UTF-8 lead
{ // byte
if(j > 1 && (b <= bLeadMax || b >= 0xF8))
j = 0; // Full UTF-8 char or
break; // illegal sequence
}
if(j > 1)
{
if(j == 5) // Illegal UTF-8
{
j = 0;
break;
}
*(char *)&bLeadMax >>= 1;
}
}
}
else
{
LONG temp = cbRead - 1;
// GetTrailBytesCount() can return 1 for some trail bytes
// esp. for GBX. So, we need to keep on checking until
// we hit a non-lead byte character. Then, based on
// how many bytes we went back, we can determine if the
// last byte is really a Lead byte.
while (temp && GetTrailBytesCount((BYTE)szBuf[temp], uCpg))
temp--;
if(temp && ((cbRead-1-temp) & 1))
j = 1;
}
// We don't want to pass the lead byte or partial UTF-8 to
// MultiByteToWideChar() because it will return bad char.
cchConv = MBTWC(uCpg, 0, szBuf + cbSkip, cbRead - j - cbSkip,
&wszBuf[2], READSIZE, NULL);
for(LONG i = j; i; i--) // Copy down partial
szBuf[j - i] = szBuf[cbRead - i]; // multibyte sequence
}
cbReadTotal += cbRead - j - jPrev;
// Cleanse (CRLFs -> CRs, etc.), limit, and insert the data. Have
// to handle CRLFs and CRCRLFs that overlap two successive buffers.
Assert(cCR <= 2);
pch = &wszBuf[2 - cCR]; // Include CRs from prev
if(!ped->_pdp->IsMultiLine()) // Single-line control
{
Assert(!cCR);
}
else
{
wszBuf[0] = wszBuf[1] = CR; // Store CRs for cchCR > 0
cCRPrev = cCR; // Save prev cchCR
cCR = 0; // Default no CR this buf
Assert(ARRAY_SIZE(wszBuf) >= cchConv + 2);
// Need to +2 since we are moving data into wszBuf[2]
if(cchConv && wszBuf[cchConv + 2 - 1] == CR)
{ // There's at least one
cCR++; // Set it up for next buf
if (cchConv > 1 && // in case CR of CRLF
wszBuf[cchConv + 2 - 2] == CR) // Got 2nd CR; might be
{ // first CR of CRCRLF so
cCR++; // setup for next buffer
}
}
cchConv += cCRPrev - cCR; // Add in count from prev
} // next
Assert(!prg->GetCch()); // Range is IP
prg->CleanseAndReplaceRange(cchConv, pch, fTestLimit, publdr, pch, NULL, RR_ITMZ_NONE);
if(pCallMgr->GetMaxText() || pCallMgr->GetOutOfMemory())
{
// Out of memory or reached the max size of our text control.
// In either case, return STG_E_MEDIUMFULL (for compatibility
// with RichEdit 1.0)
pes->dwError = (DWORD)STG_E_MEDIUMFULL;
break;
}
}
prg->ItemizeReplaceRange(prg->GetCp() - cpMin, cchMove, publdr, TRUE);
return cbReadTotal;
}
/*
* CLightDTEngine::WritePlainText (prg, pes, lStreamFormat)
*
* @mfunc
* Writes plain text from the range into the given edit stream
*
* @rdesc
* Count of bytes written
*/
LONG CLightDTEngine::WritePlainText(
CTxtRange * prg, // @parm range to write from
EDITSTREAM *pes, // @parm edit stream to write to
LONG lStreamFormat) // @parm Stream format
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::WritePlainText");
LONG cbConverted; // Bytes for output stream
LONG cbWrite; // Incremental byte count
LONG cbWriteTotal = 0; // No chars written yet
LONG cpMin, cpMost;
LONG cch = prg->GetRange(cpMin, cpMost);
BOOL fAdjustCRLF = TRUE; // Adjust first time through loop
BOOL fTextize = lStreamFormat & SF_TEXTIZED;
LPBYTE pb; // Byte ptr to szBuf or wszBuf
COleObject *pobj; // Ptr to embedded object
CTxtPtr tp(_ped, cpMin); // tp to walk prg with
UINT uCpg = GetStreamCodePage(lStreamFormat);
// DBCS has up to 2 times as many chars as WCHARs. UTF-8 has 3 BYTES for
// all codes above 0x7ff. UTF-7 has even more due to shift in/out codes.
// We don't support UTF-7, since can't use WCTMB with UTF-7 chunks
char szBuf[3*WRITESIZE]; // Factor of 2 works with DBCS, 3 with UTF-8
WCHAR wszBuf[WRITESIZE];
pes->dwError = NOERROR; // No error yet
pb = (uCpg == 1200) ? (BYTE *)wszBuf // Setup Unicode or MBCS
: (BYTE *)szBuf;
LONG cchText = _ped->GetAdjustedTextLength();
cpMost = min(cpMost, cchText); // Don't write final CR
while(tp.GetCp() < cpMost)
{
if (fTextize && tp.GetChar() == WCH_EMBEDDING)
{
Assert(_ped->GetObjectCount());
pobj = _ped->GetObjectMgr()->GetObjectFromCp(tp.GetCp());
tp.AdvanceCp(1); // Advance past object
if(pobj)
{
cbWriteTotal += pobj->WriteTextInfoToEditStream(pes);
continue; // If no object at cp,
} // just ignore char
}
cch = tp.GetPlainText(WRITESIZE, wszBuf, cpMost, fTextize, fAdjustCRLF);
if(!cch)
break; // No more to do
fAdjustCRLF = FALSE; // Already adjusted
cbConverted = 2*cch; // Default Unicode byte ct
if(uCpg != 1200) // Multibyte of some kind
{
cbConverted = MbcsFromUnicode(szBuf, 3*WRITESIZE, wszBuf, cch, uCpg,
UN_CONVERT_WCH_EMBEDDING);
// FUTURE: report some kind of error if default char used,
// i.e., data lost in conversion
// Did the conversion completely fail? As a fallback, we might try
// the system code page, or just plain ANSI...
if (!cbConverted)
{
uCpg = GetLocaleCodePage();
cbConverted = MbcsFromUnicode(szBuf, 3*WRITESIZE, wszBuf, cch, uCpg,
UN_CONVERT_WCH_EMBEDDING);
}
if (!cbConverted)
{
uCpg = CP_ACP;
cbConverted = MbcsFromUnicode(szBuf, 3*WRITESIZE, wszBuf, cch, uCpg,
UN_CONVERT_WCH_EMBEDDING);
}
}
pes->dwError = (*pes->pfnCallback)(pes->dwCookie, pb,
cbConverted, &cbWrite);
if(!pes->dwError && cbConverted != cbWrite) // Error or ran out of
pes->dwError = (DWORD)STG_E_MEDIUMFULL; // target storage
if(pes->dwError)
break;
cbWriteTotal += cbWrite;
}
AssertSz(tp.GetCp() >= cpMost,
"CLightDTEngine::WritePlainText: not all text written");
return cbWriteTotal;
}
/*
* CLightDTEngine::GetStreamCodePage (lStreamFormat)
*
* @mfunc
* Returns code page given by lStreamFormat or CTxtEdit::_pDocInfo
*
* @rdesc
* HRESULT
*/
LONG CLightDTEngine::GetStreamCodePage(
LONG lStreamFormat)
{
// FUTURE: support 1201, i.e., big-endian Unicode?
if(lStreamFormat & SF_UNICODE)
return 1200;
if(lStreamFormat & SF_USECODEPAGE)
return HIWORD(lStreamFormat);
if (W32->IsFESystem())
return GetACP();
return CP_ACP;
}
/*
* CLightDTEngine::CreateOleObjFromDataObj ( pdo, prg, rps, iformatetc, pubdlr )
*
* @mfunc
* Creates an ole object based on the data object pdo, and
* pastes the object into the range prg. Any text that already
* existed in the range is replaced.
*
* @rdesc
* HRESULT
*/
HRESULT CLightDTEngine::CreateOleObjFromDataObj(
IDataObject * pdo, // @parm Data object from which to create
CTxtRange * prg, // @parm Range in which to place
REPASTESPECIAL *rps, // @parm Special paste info
INT iformatetc, // @parm Index in g_rgFETC
IUndoBuilder * publdr) // @parm Undo builder to receive antievents
{
#ifndef PEGASUS
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CLightDTEngine::CreateOleObjFromDataObj");
HRESULT hr = NOERROR;
REOBJECT reobj;
SIZEL sizel;
FORMATETC formatetc;
DWORD dwDrawAspect = 0;
HGLOBAL hMetaPict = NULL;
LPRICHEDITOLECALLBACK const precall = _ped->GetRECallback();
LPOBJECTDESCRIPTOR lpod = NULL;
STGMEDIUM medObjDesc;
BOOL fStatic = (iformatetc == iMfPict || iformatetc == iDIB ||
iformatetc == iBitmap);
BOOL fFilename = (iformatetc == iFilename);
DUAL_FORMATETC tmpFormatEtc;
if(!precall)
return E_NOINTERFACE;
ZeroMemory(&medObjDesc, sizeof(STGMEDIUM));
ZeroMemory(&sizel, sizeof(SIZEL));
ZeroMemory(&reobj, sizeof(REOBJECT));
if(fStatic)
dwDrawAspect = DVASPECT_CONTENT;
if(fFilename)
dwDrawAspect = DVASPECT_ICON;
if(rps && !dwDrawAspect)
{
dwDrawAspect = rps->dwAspect;
if(rps->dwAspect == DVASPECT_ICON)
hMetaPict = (HGLOBAL)rps->dwParam;
}
// If no aspect was specified, pick up the object descriptor hints
if(!dwDrawAspect)
{
// Define ObjectDescriptor data
formatetc.cfFormat = cf_OBJECTDESCRIPTOR;
formatetc.ptd = NULL;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.tymed = TYMED_HGLOBAL;
if(pdo->GetData(&formatetc, &medObjDesc) == NOERROR)
{
HANDLE hGlobal = medObjDesc.hGlobal;
lpod = (LPOBJECTDESCRIPTOR)GlobalLock(hGlobal);
if(lpod)
{
dwDrawAspect = lpod->dwDrawAspect;
}
GlobalUnlock(hGlobal);
ReleaseStgMedium(&medObjDesc);
}
}
if(!dwDrawAspect)
dwDrawAspect = DVASPECT_CONTENT;
if(fStatic)
{
reobj.clsid = ((iformatetc == iMfPict) ?
CLSID_StaticMetafile : CLSID_StaticDib);
}
// COMPATIBILITY ISSUE: Compatibility Issue from Richedit 1.0 - Raid 16456:
// Don't call GetData(CF_EMBEDSOURCE)
// on 32-bit Excel. Also clsidPictPub.
// if(iformatetc == iformatetcEmbSrc && (ObFIsExcel(&clsid) ||
// IsEqualCLSID(&clsid, &clsidPictPub)))
// else
// ObGetStgFromDataObj(pdataobj, &medEmbed, iformatetc);
// Get storage for the object from the application
hr = precall->GetNewStorage(&reobj.pstg);
if(hr)
{
TRACEERRORSZ("GetNewStorage() failed.");
goto err;
}
// Create an object site for the new object
hr = _ped->GetClientSite(&reobj.polesite);
if(!reobj.polesite)
{
TRACEERRORSZ("GetClientSite() failed.");
goto err;
}
ZeroMemory(&tmpFormatEtc, sizeof(DUAL_FORMATETC));
tmpFormatEtc.ptd = NULL;
tmpFormatEtc.dwAspect = dwDrawAspect;
tmpFormatEtc.lindex = -1;
//Create the object
if(fStatic)
{
hr = OleCreateStaticFromData(pdo, IID_IOleObject, OLERENDER_DRAW,
&tmpFormatEtc, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj);
}
else if(iformatetc == iLnkSrc || (_ped->Get10Mode() && iformatetc == iFilename))
{
hr = OleCreateLinkFromData(pdo, IID_IOleObject, OLERENDER_DRAW,
&tmpFormatEtc, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj);
}
else
{
hr = OleCreateFromData(pdo, IID_IOleObject, OLERENDER_DRAW,
&tmpFormatEtc, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj);
}
if(hr)
{
TRACEERRORSZ("Failure creating object.");
goto err;
}
//Get the clsid of the object.
if(!fStatic)
{
hr = reobj.poleobj->GetUserClassID(&reobj.clsid);
if(hr)
{
TRACEERRORSZ("GetUserClassID() failed.");
goto err;
}
}
//Deal with iconic aspect if specified.
if(hMetaPict)
{
BOOL fUpdate;
hr = OleStdSwitchDisplayAspect(reobj.poleobj, &dwDrawAspect,
DVASPECT_ICON, hMetaPict, FALSE,
FALSE, NULL, &fUpdate);
if(hr)
{
TRACEERRORSZ("OleStdSwitchDisplayAspect() failed.");
goto err;
}
// If we successully changed the aspect, recompute the size.
hr = reobj.poleobj->GetExtent(dwDrawAspect, &sizel);
if(hr)
{
TRACEERRORSZ("GetExtent() failed.");
goto err;
}
}
// Try to retrieve the previous saved RichEdit site flags.
if( ObjectReadSiteFlags(&reobj) != NOERROR )
{
// Set default for site flags
reobj.dwFlags = REO_RESIZABLE;
}
// First, clear the range
prg->Delete(publdr, SELRR_REMEMBERRANGE);
reobj.cbStruct = sizeof(REOBJECT);
reobj.cp = prg->GetCp();
reobj.dvaspect = dwDrawAspect;
reobj.sizel = sizel;
//COMPATIBILITY ISSUE: from Richedit 1.0 - don't Set the Extent,
//instead Get the Extent below in ObFAddObjectSite
//hr = reobj.poleobj->SetExtent(dwDrawAspect, &sizel);
hr = reobj.poleobj->SetClientSite(reobj.polesite);
if(hr)
{
TRACEERRORSZ("SetClientSite() failed.");
goto err;
}
if(hr = _ped->InsertObject(&reobj))
{
TRACEERRORSZ("InsertObject() failed.");
}
err:
if(reobj.poleobj)
reobj.poleobj->Release();
if(reobj.polesite)
reobj.polesite->Release();
if(reobj.pstg)
reobj.pstg->Release();
return hr;
#else
return 0;
#endif
}