/* * @doc INTERNAL * * @module LDTE.C - RichEdit Light Data Transfer Engine | * * This file contains data transfer code using IDataObject * * Author: * alexgo (4/25/95) * * Revisions: * murrays (7/6/95) auto-doc'd and added RTF support * * FUTURE (AlexGo): * 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 }