2394 lines
61 KiB
C++
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
|
|
}
|