No need to double zero arrays
[TortoiseGit.git] / src / Git / GitDataObject.cpp
blob3df3a15fc679a70e3ef6c3a29174b558f6343a09
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016-2019, 2021-2022 - TortoiseGit
4 // Copyright (C) 2007-2014 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "GitDataObject.h"
21 #include "Git.h"
22 #include "UnicodeUtils.h"
23 #include "PathUtils.h"
24 #include "TempFile.h"
25 #include "StringUtils.h"
26 #include <strsafe.h>
28 CLIPFORMAT CF_FILECONTENTS = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_FILECONTENTS));
29 CLIPFORMAT CF_FILEDESCRIPTOR = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
30 CLIPFORMAT CF_PREFERREDDROPEFFECT = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT));
31 CLIPFORMAT CF_INETURL = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_INETURL));
32 CLIPFORMAT CF_SHELLURL = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_SHELLURL));
33 CLIPFORMAT CF_FILE_ATTRIBUTES_ARRAY = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_FILE_ATTRIBUTES_ARRAY));
35 GitDataObject::GitDataObject(const CTGitPathList& gitpaths, const CGitHash& rev, int stripLength)
36 : m_gitPaths(gitpaths)
37 , m_revision(rev)
38 , m_bInOperation(FALSE)
39 , m_bIsAsync(TRUE)
40 , m_cRefCount(0)
41 , m_iStripLength(stripLength)
42 , m_containsExistingFiles(false)
44 ASSERT((m_revision.IsEmpty() && m_iStripLength == -1) || (!m_revision.IsEmpty() && m_iStripLength >= -1)); // m_iStripLength only possible if rev is set
45 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
47 if ((m_gitPaths[i].m_Action == 0 || (m_gitPaths[i].m_Action & ~(CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED))) && !m_gitPaths[i].IsDirectory())
49 m_containsExistingFiles = true;
50 break;
55 GitDataObject::~GitDataObject()
57 for (size_t i = 0; i < m_vecStgMedium.size(); ++i)
59 ReleaseStgMedium(m_vecStgMedium[i]);
60 delete m_vecStgMedium[i];
63 for (size_t j = 0; j < m_vecFormatEtc.size(); ++j)
64 delete m_vecFormatEtc[j];
67 //////////////////////////////////////////////////////////////////////////
68 // IUnknown
69 //////////////////////////////////////////////////////////////////////////
71 STDMETHODIMP GitDataObject::QueryInterface(REFIID riid, void** ppvObject)
73 if (!ppvObject)
74 return E_POINTER;
75 *ppvObject = NULL;
76 if (IsEqualIID(IID_IUnknown, riid) || IsEqualIID(IID_IDataObject, riid))
77 *ppvObject = static_cast<IDataObject*>(this);
78 else if (IsEqualIID(riid, IID_IDataObjectAsyncCapability))
79 *ppvObject = static_cast<IDataObjectAsyncCapability*>(this);
80 else
81 return E_NOINTERFACE;
83 AddRef();
84 return S_OK;
87 STDMETHODIMP_(ULONG) GitDataObject::AddRef()
89 return ++m_cRefCount;
92 STDMETHODIMP_(ULONG) GitDataObject::Release()
94 --m_cRefCount;
95 if (m_cRefCount == 0)
97 delete this;
98 return 0;
100 return m_cRefCount;
103 //////////////////////////////////////////////////////////////////////////
104 // IDataObject
105 //////////////////////////////////////////////////////////////////////////
106 STDMETHODIMP GitDataObject::GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium)
108 if (!pformatetcIn)
109 return E_INVALIDARG;
110 if (!pmedium)
111 return E_POINTER;
112 pmedium->hGlobal = nullptr;
114 if ((pformatetcIn->tymed & TYMED_ISTREAM) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILECONTENTS))
116 // supports the IStream format.
117 // The lindex param is the index of the file to return
118 CString filepath;
119 IStream* pIStream = nullptr;
121 // Note: we don't get called for directories since those are simply created and don't
122 // need to be fetched.
124 // Note2: It would be really nice if we could get a stream from the subversion library
125 // from where we could read the file contents. But the Subversion lib is not implemented
126 // to *read* from a remote stream but so that the library *writes* to a stream we pass.
127 // Since we can't get such a read stream, we have to fetch the file in whole first to
128 // a temp location and then pass the shell an IStream for that temp file.
130 if (m_revision.IsEmpty())
132 if (pformatetcIn->lindex >= 0 && pformatetcIn->lindex < static_cast<LONG>(m_allPaths.size()))
133 filepath = g_Git.CombinePath(m_allPaths[pformatetcIn->lindex]);
135 else
137 filepath = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
138 if (pformatetcIn->lindex >= 0 && pformatetcIn->lindex < static_cast<LONG>(m_allPaths.size()))
140 if (g_Git.GetOneFile(m_revision.ToString(), m_allPaths[pformatetcIn->lindex], filepath))
142 DeleteFile(filepath);
143 return STG_E_ACCESSDENIED;
148 HRESULT res = SHCreateStreamOnFileEx(filepath, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &pIStream);
149 if (res == S_OK)
151 // http://blogs.msdn.com/b/oldnewthing/archive/2014/09/18/10558763.aspx
152 LARGE_INTEGER liZero = { 0, 0 };
153 pIStream->Seek(liZero, STREAM_SEEK_END, nullptr);
155 pmedium->pstm = pIStream;
156 pmedium->tymed = TYMED_ISTREAM;
157 return S_OK;
159 return res;
161 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILEDESCRIPTOR))
163 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
165 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
166 continue;
167 m_allPaths.push_back(m_gitPaths[i]);
170 size_t dataSize = sizeof(FILEGROUPDESCRIPTOR) + ((max(size_t(1), m_allPaths.size()) - 1) * sizeof(FILEDESCRIPTOR));
171 HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, dataSize);
173 auto files = static_cast<FILEGROUPDESCRIPTOR*>(GlobalLock(data));
174 files->cItems = static_cast<UINT>(m_allPaths.size());
175 int index = 0;
176 for (auto it = m_allPaths.cbegin(); it != m_allPaths.cend(); ++it)
178 CString temp(m_iStripLength > 0 ? it->GetWinPathString().Mid(m_iStripLength + 1) : (m_iStripLength == 0 ? it->GetWinPathString() : it->GetUIFileOrDirectoryName()));
179 if (temp.GetLength() < MAX_PATH)
180 wcscpy_s(files->fgd[index].cFileName, static_cast<LPCWSTR>(temp));
181 else
183 files->cItems--;
184 continue;
186 files->fgd[index].dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI | FD_FILESIZE | FD_LINKUI;
187 files->fgd[index].dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
189 // Always set the file size to 0 even if we 'know' the file size (infodata.size64).
190 // Because for text files, the file size is too low due to the EOLs getting converted
191 // to CRLF (from LF as stored in the repository). And a too low file size set here
192 // prevents the shell from reading the full file later - it only reads the stream up
193 // to the number of bytes specified here. Which means we would end up with a truncated
194 // text file (binary files are still ok though).
195 files->fgd[index].nFileSizeLow = 0;
196 files->fgd[index].nFileSizeHigh = 0;
198 ++index;
201 GlobalUnlock(data);
203 pmedium->hGlobal = data;
204 pmedium->tymed = TYMED_HGLOBAL;
205 return S_OK;
207 // handling CF_PREFERREDDROPEFFECT is necessary to tell the shell that it should *not* ask for the
208 // CF_FILEDESCRIPTOR until the drop actually occurs. If we don't handle CF_PREFERREDDROPEFFECT, the shell
209 // will ask for the file descriptor for every object (file/folder) the mouse pointer hovers over and is
210 // a potential drop target.
211 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->cfFormat == CF_PREFERREDDROPEFFECT))
213 HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, sizeof(DWORD));
214 if (!data)
215 return E_OUTOFMEMORY;
216 auto effect = static_cast<DWORD*>(GlobalLock(data));
217 if (!effect)
219 GlobalFree(data);
220 return E_OUTOFMEMORY;
222 (*effect) = DROPEFFECT_COPY;
223 GlobalUnlock(data);
224 pmedium->hGlobal = data;
225 pmedium->tymed = TYMED_HGLOBAL;
226 return S_OK;
228 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_TEXT))
230 // caller wants text
231 // create the string from the path list
232 CString text;
233 if (!m_gitPaths.IsEmpty())
235 // create a single string where the URLs are separated by newlines
236 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
238 text += m_gitPaths[i].GetWinPathString();
239 text += L"\r\n";
242 CStringA texta = CUnicodeUtils::GetUTF8(text);
243 pmedium->tymed = TYMED_HGLOBAL;
244 pmedium->hGlobal = GlobalAlloc(GHND, (texta.GetLength() + 1) * sizeof(char));
245 if (pmedium->hGlobal)
247 auto pMem = static_cast<char*>(GlobalLock(pmedium->hGlobal));
248 strcpy_s(pMem, texta.GetLength() + 1, static_cast<LPCSTR>(texta));
249 GlobalUnlock(pmedium->hGlobal);
251 pmedium->pUnkForRelease = nullptr;
252 return S_OK;
254 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && ((pformatetcIn->cfFormat == CF_UNICODETEXT) || (pformatetcIn->cfFormat == CF_INETURL) || (pformatetcIn->cfFormat == CF_SHELLURL)))
256 // caller wants Unicode text
257 // create the string from the path list
258 CString text;
259 if (!m_gitPaths.IsEmpty())
261 // create a single string where the URLs are separated by newlines
262 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
264 if (pformatetcIn->cfFormat == CF_UNICODETEXT)
265 text += m_gitPaths[i].GetWinPathString();
266 else
267 text += g_Git.CombinePath(m_gitPaths[i]);
268 text += L"\r\n";
271 pmedium->tymed = TYMED_HGLOBAL;
272 pmedium->hGlobal = GlobalAlloc(GHND, (text.GetLength() + 1) * sizeof(wchar_t));
273 if (pmedium->hGlobal)
275 auto pMem = static_cast<wchar_t*>(GlobalLock(pmedium->hGlobal));
276 wcscpy_s(pMem, text.GetLength() + 1, static_cast<LPCWSTR>(text));
277 GlobalUnlock(pmedium->hGlobal);
279 pmedium->pUnkForRelease = nullptr;
280 return S_OK;
282 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_HDROP))
284 int nLength = 0;
286 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
288 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
289 continue;
291 nLength += g_Git.CombinePath(m_gitPaths[i]).GetLength();
292 nLength += 1; // '\0' separator
295 int nBufferSize = sizeof(DROPFILES) + (nLength + 1) * sizeof(wchar_t);
296 auto pBuffer = std::make_unique<char[]>(nBufferSize);
298 auto df = reinterpret_cast<DROPFILES*>(pBuffer.get());
299 df->pFiles = sizeof(DROPFILES);
300 df->fWide = 1;
302 auto pFilenames = reinterpret_cast<wchar_t*>(pBuffer.get() + sizeof(DROPFILES));
303 wchar_t* pCurrentFilename = pFilenames;
305 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
307 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
308 continue;
309 CString str = g_Git.CombinePath(m_gitPaths[i]);
310 wcscpy_s(pCurrentFilename, str.GetLength() + 1, static_cast<LPCWSTR>(str));
311 pCurrentFilename += str.GetLength();
312 *pCurrentFilename = '\0'; // separator between file names
313 pCurrentFilename++;
315 *pCurrentFilename = '\0'; // terminate array
317 pmedium->tymed = TYMED_HGLOBAL;
318 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
319 if (pmedium->hGlobal)
321 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
322 if (pMem)
323 memcpy(pMem, pBuffer.get(), nBufferSize);
324 GlobalUnlock(pmedium->hGlobal);
326 pmedium->pUnkForRelease = nullptr;
327 return S_OK;
329 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILE_ATTRIBUTES_ARRAY))
331 int nBufferSize = sizeof(FILE_ATTRIBUTES_ARRAY) + m_gitPaths.GetCount() * sizeof(DWORD);
332 auto pBuffer = std::make_unique<char[]>(nBufferSize);
334 auto cf = reinterpret_cast<FILE_ATTRIBUTES_ARRAY*>(pBuffer.get());
335 cf->cItems = m_gitPaths.GetCount();
336 cf->dwProductFileAttributes = DWORD_MAX;
337 cf->dwSumFileAttributes = 0;
338 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
340 DWORD fileattribs = FILE_ATTRIBUTE_NORMAL;
341 cf->rgdwFileAttributes[i] = fileattribs;
342 cf->dwProductFileAttributes &= fileattribs;
343 cf->dwSumFileAttributes |= fileattribs;
346 pmedium->tymed = TYMED_HGLOBAL;
347 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
348 if (pmedium->hGlobal)
350 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
351 if (pMem)
352 memcpy(pMem, pBuffer.get(), nBufferSize);
353 GlobalUnlock(pmedium->hGlobal);
355 pmedium->pUnkForRelease = nullptr;
356 return S_OK;
359 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
361 if ((pformatetcIn->tymed == m_vecFormatEtc[i]->tymed) &&
362 (pformatetcIn->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
363 (pformatetcIn->cfFormat == m_vecFormatEtc[i]->cfFormat))
365 CopyMedium(pmedium, m_vecStgMedium[i], m_vecFormatEtc[i]);
366 return S_OK;
370 return DV_E_FORMATETC;
373 STDMETHODIMP GitDataObject::GetDataHere(FORMATETC* /*pformatetc*/, STGMEDIUM* /*pmedium*/)
375 return E_NOTIMPL;
378 STDMETHODIMP GitDataObject::QueryGetData(FORMATETC* pformatetc)
380 if (!pformatetc)
381 return E_INVALIDARG;
383 if (!(DVASPECT_CONTENT & pformatetc->dwAspect))
384 return DV_E_DVASPECT;
386 if ((pformatetc->tymed & TYMED_ISTREAM) &&
387 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
388 (pformatetc->cfFormat == CF_FILECONTENTS) &&
389 m_containsExistingFiles)
391 return S_OK;
393 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
394 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
395 ((pformatetc->cfFormat == CF_TEXT) || (pformatetc->cfFormat == CF_UNICODETEXT) || (pformatetc->cfFormat == CF_PREFERREDDROPEFFECT)))
397 return S_OK;
399 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
400 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
401 (pformatetc->cfFormat == CF_FILEDESCRIPTOR) &&
402 !m_revision.IsEmpty() &&
403 m_containsExistingFiles)
405 return S_OK;
407 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
408 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
409 ((pformatetc->cfFormat == CF_HDROP) || (pformatetc->cfFormat == CF_INETURL) || (pformatetc->cfFormat == CF_SHELLURL)) &&
410 m_revision.IsEmpty() &&
411 m_containsExistingFiles)
413 return S_OK;
415 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
416 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
417 (pformatetc->cfFormat == CF_FILE_ATTRIBUTES_ARRAY) &&
418 m_containsExistingFiles)
420 return S_OK;
423 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
425 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
426 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
427 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
428 return S_OK;
431 return DV_E_TYMED;
434 STDMETHODIMP GitDataObject::GetCanonicalFormatEtc(FORMATETC* /*pformatectIn*/, FORMATETC* pformatetcOut)
436 if (!pformatetcOut)
437 return E_INVALIDARG;
438 return DATA_S_SAMEFORMATETC;
441 STDMETHODIMP GitDataObject::SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease)
443 if (!pformatetc || !pmedium)
444 return E_INVALIDARG;
446 FORMATETC* fetc = new (std::nothrow) FORMATETC;
447 STGMEDIUM* pStgMed = new (std::nothrow) STGMEDIUM;
449 if (!fetc || !pStgMed)
451 delete fetc;
452 delete pStgMed;
453 return E_OUTOFMEMORY;
455 SecureZeroMemory(fetc, sizeof(FORMATETC));
456 SecureZeroMemory(pStgMed, sizeof(STGMEDIUM));
458 // do we already store this format?
459 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
461 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
462 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
463 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
465 // yes, this format is already in our object:
466 // we have to replace the existing format. To do that, we
467 // remove the format we already have
468 delete m_vecFormatEtc[i];
469 m_vecFormatEtc.erase(m_vecFormatEtc.begin() + i);
470 ReleaseStgMedium(m_vecStgMedium[i]);
471 delete m_vecStgMedium[i];
472 m_vecStgMedium.erase(m_vecStgMedium.begin() + i);
473 break;
477 *fetc = *pformatetc;
478 m_vecFormatEtc.push_back(fetc);
480 if (fRelease)
481 *pStgMed = *pmedium;
482 else
483 CopyMedium(pStgMed, pmedium, pformatetc);
484 m_vecStgMedium.push_back(pStgMed);
486 return S_OK;
489 STDMETHODIMP GitDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc)
491 if (!ppenumFormatEtc)
492 return E_POINTER;
494 *ppenumFormatEtc = nullptr;
495 switch (dwDirection)
497 case DATADIR_GET:
498 *ppenumFormatEtc = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_revision.IsEmpty(), m_containsExistingFiles);
499 if (!*ppenumFormatEtc)
500 return E_OUTOFMEMORY;
501 (*ppenumFormatEtc)->AddRef();
502 break;
503 default:
504 return E_NOTIMPL;
506 return S_OK;
509 STDMETHODIMP GitDataObject::DAdvise(FORMATETC* /*pformatetc*/, DWORD /*advf*/, IAdviseSink* /*pAdvSink*/, DWORD* /*pdwConnection*/)
511 return OLE_E_ADVISENOTSUPPORTED;
514 STDMETHODIMP GitDataObject::DUnadvise(DWORD /*dwConnection*/)
516 return E_NOTIMPL;
519 HRESULT STDMETHODCALLTYPE GitDataObject::EnumDAdvise(IEnumSTATDATA** /*ppenumAdvise*/)
521 return OLE_E_ADVISENOTSUPPORTED;
524 void GitDataObject::CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc)
526 switch (pMedSrc->tymed)
528 case TYMED_HGLOBAL:
529 pMedDest->hGlobal = static_cast<HGLOBAL>(OleDuplicateData(pMedSrc->hGlobal, pFmtSrc->cfFormat, 0));
530 break;
531 case TYMED_GDI:
532 pMedDest->hBitmap = static_cast<HBITMAP>(OleDuplicateData(pMedSrc->hBitmap, pFmtSrc->cfFormat, 0));
533 break;
534 case TYMED_MFPICT:
535 pMedDest->hMetaFilePict = static_cast<HMETAFILEPICT>(OleDuplicateData(pMedSrc->hMetaFilePict, pFmtSrc->cfFormat, 0));
536 break;
537 case TYMED_ENHMF:
538 pMedDest->hEnhMetaFile = static_cast<HENHMETAFILE>(OleDuplicateData(pMedSrc->hEnhMetaFile, pFmtSrc->cfFormat, 0));
539 break;
540 case TYMED_FILE:
541 pMedSrc->lpszFileName = static_cast<LPOLESTR>(OleDuplicateData(pMedSrc->lpszFileName, pFmtSrc->cfFormat, 0));
542 break;
543 case TYMED_ISTREAM:
544 pMedDest->pstm = pMedSrc->pstm;
545 pMedSrc->pstm->AddRef();
546 break;
547 case TYMED_ISTORAGE:
548 pMedDest->pstg = pMedSrc->pstg;
549 pMedSrc->pstg->AddRef();
550 break;
551 case TYMED_NULL:
552 default:
553 break;
555 pMedDest->tymed = pMedSrc->tymed;
556 pMedDest->pUnkForRelease = nullptr;
557 if (pMedSrc->pUnkForRelease)
559 pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease;
560 pMedSrc->pUnkForRelease->AddRef();
565 //////////////////////////////////////////////////////////////////////////
566 // IAsyncOperation
567 //////////////////////////////////////////////////////////////////////////
568 HRESULT STDMETHODCALLTYPE GitDataObject::SetAsyncMode(BOOL fDoOpAsync)
570 m_bIsAsync = fDoOpAsync;
571 return S_OK;
574 HRESULT STDMETHODCALLTYPE GitDataObject::GetAsyncMode(BOOL* pfIsOpAsync)
576 if (!pfIsOpAsync)
577 return E_FAIL;
579 *pfIsOpAsync = m_bIsAsync;
581 return S_OK;
584 HRESULT STDMETHODCALLTYPE GitDataObject::StartOperation(IBindCtx* /*pbcReserved*/)
586 m_bInOperation = TRUE;
587 return S_OK;
590 HRESULT STDMETHODCALLTYPE GitDataObject::InOperation(BOOL* pfInAsyncOp)
592 if (!pfInAsyncOp)
593 return E_FAIL;
595 *pfInAsyncOp = m_bInOperation;
597 return S_OK;
600 HRESULT STDMETHODCALLTYPE GitDataObject::EndOperation(HRESULT /*hResult*/, IBindCtx* /*pbcReserved*/, DWORD /*dwEffects*/)
602 m_bInOperation = FALSE;
603 return S_OK;
606 HRESULT GitDataObject::SetDropDescription(DROPIMAGETYPE image, LPCWSTR format, LPCWSTR insert)
608 if (!format || !insert)
609 return E_INVALIDARG;
611 FORMATETC fetc = { 0 };
612 fetc.cfFormat = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_DROPDESCRIPTION));
613 fetc.dwAspect = DVASPECT_CONTENT;
614 fetc.lindex = -1;
615 fetc.tymed = TYMED_HGLOBAL;
617 STGMEDIUM medium = { 0 };
618 medium.hGlobal = GlobalAlloc(GHND, sizeof(DROPDESCRIPTION));
619 if (medium.hGlobal == 0)
620 return E_OUTOFMEMORY;
622 auto pDropDescription = static_cast<DROPDESCRIPTION*>(GlobalLock(medium.hGlobal));
623 if (!pDropDescription)
624 return E_FAIL;
625 StringCchCopy(pDropDescription->szInsert, _countof(pDropDescription->szInsert), insert);
626 StringCchCopy(pDropDescription->szMessage, _countof(pDropDescription->szMessage), format);
627 pDropDescription->type = image;
628 GlobalUnlock(medium.hGlobal);
629 return SetData(&fetc, &medium, TRUE);
632 void CGitEnumFormatEtc::Init(bool localonly, bool containsExistingFiles)
634 int index = 0;
635 m_formats[index].cfFormat = CF_UNICODETEXT;
636 m_formats[index].dwAspect = DVASPECT_CONTENT;
637 m_formats[index].lindex = -1;
638 m_formats[index].ptd = nullptr;
639 m_formats[index].tymed = TYMED_HGLOBAL;
640 index++;
642 m_formats[index].cfFormat = CF_TEXT;
643 m_formats[index].dwAspect = DVASPECT_CONTENT;
644 m_formats[index].lindex = -1;
645 m_formats[index].ptd = nullptr;
646 m_formats[index].tymed = TYMED_HGLOBAL;
647 index++;
649 m_formats[index].cfFormat = CF_PREFERREDDROPEFFECT;
650 m_formats[index].dwAspect = DVASPECT_CONTENT;
651 m_formats[index].lindex = -1;
652 m_formats[index].ptd = nullptr;
653 m_formats[index].tymed = TYMED_HGLOBAL;
654 index++;
656 if (containsExistingFiles && localonly)
658 m_formats[index].cfFormat = CF_INETURL;
659 m_formats[index].dwAspect = DVASPECT_CONTENT;
660 m_formats[index].lindex = -1;
661 m_formats[index].ptd = nullptr;
662 m_formats[index].tymed = TYMED_HGLOBAL;
663 index++;
665 m_formats[index].cfFormat = CF_SHELLURL;
666 m_formats[index].dwAspect = DVASPECT_CONTENT;
667 m_formats[index].lindex = -1;
668 m_formats[index].ptd = nullptr;
669 m_formats[index].tymed = TYMED_HGLOBAL;
670 index++;
673 m_formats[index].cfFormat = CF_FILE_ATTRIBUTES_ARRAY;
674 m_formats[index].dwAspect = DVASPECT_CONTENT;
675 m_formats[index].lindex = -1;
676 m_formats[index].ptd = nullptr;
677 m_formats[index].tymed = TYMED_HGLOBAL;
678 index++;
680 if (containsExistingFiles && localonly)
682 m_formats[index].cfFormat = CF_HDROP;
683 m_formats[index].dwAspect = DVASPECT_CONTENT;
684 m_formats[index].lindex = -1;
685 m_formats[index].ptd = nullptr;
686 m_formats[index].tymed = TYMED_HGLOBAL;
687 index++;
689 else if (containsExistingFiles)
691 m_formats[index].cfFormat = CF_FILECONTENTS;
692 m_formats[index].dwAspect = DVASPECT_CONTENT;
693 m_formats[index].lindex = -1;
694 m_formats[index].ptd = nullptr;
695 m_formats[index].tymed = TYMED_ISTREAM;
696 index++;
698 m_formats[index].cfFormat = CF_FILEDESCRIPTOR;
699 m_formats[index].dwAspect = DVASPECT_CONTENT;
700 m_formats[index].lindex = -1;
701 m_formats[index].ptd = nullptr;
702 m_formats[index].tymed = TYMED_HGLOBAL;
703 index++;
705 // clear possible leftovers
706 while (index < GITDATAOBJECT_NUMFORMATS)
708 m_formats[index].cfFormat = 0;
709 m_formats[index].dwAspect = 0;
710 m_formats[index].lindex = -1;
711 m_formats[index].ptd = nullptr;
712 m_formats[index].tymed = 0;
713 index++;
717 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC>& vec, bool localonly, bool containsExistingFiles)
718 : m_cRefCount(0)
719 , m_iCur(0)
720 , m_localonly(localonly)
721 , m_containsExistingFiles(containsExistingFiles)
723 for (size_t i = 0; i < vec.size(); ++i)
724 m_vecFormatEtc.push_back(vec[i]);
725 Init(localonly, containsExistingFiles);
728 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC*>& vec, bool localonly, bool containsExistingFiles)
729 : m_cRefCount(0)
730 , m_iCur(0)
731 , m_localonly(localonly)
732 , m_containsExistingFiles(containsExistingFiles)
734 for (size_t i = 0; i < vec.size(); ++i)
735 m_vecFormatEtc.push_back(*vec[i]);
736 Init(localonly, containsExistingFiles);
739 STDMETHODIMP CGitEnumFormatEtc::QueryInterface(REFIID refiid, void** ppv)
741 if (!ppv)
742 return E_POINTER;
743 *ppv = nullptr;
744 if (IID_IUnknown == refiid || IID_IEnumFORMATETC == refiid)
745 *ppv = static_cast<IEnumFORMATETC*>(this);
746 else
747 return E_NOINTERFACE;
749 AddRef();
750 return S_OK;
753 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::AddRef()
755 return ++m_cRefCount;
758 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::Release()
760 --m_cRefCount;
761 if (m_cRefCount == 0)
763 delete this;
764 return 0;
766 return m_cRefCount;
769 STDMETHODIMP CGitEnumFormatEtc::Next(ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched)
771 if (celt <= 0)
772 return E_INVALIDARG;
773 if (!pceltFetched && celt != 1) // pceltFetched can be NULL only for 1 item request
774 return E_POINTER;
775 if (!lpFormatEtc)
776 return E_POINTER;
778 if (pceltFetched)
779 *pceltFetched = 0;
781 if (m_iCur >= GITDATAOBJECT_NUMFORMATS)
782 return S_FALSE;
784 ULONG cReturn = celt;
786 while (m_iCur < (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()) && cReturn > 0)
788 if (m_iCur < GITDATAOBJECT_NUMFORMATS)
789 *lpFormatEtc++ = m_formats[m_iCur++];
790 else
791 *lpFormatEtc++ = m_vecFormatEtc[m_iCur++ - GITDATAOBJECT_NUMFORMATS];
792 --cReturn;
795 if (pceltFetched)
796 *pceltFetched = celt - cReturn;
798 return (cReturn == 0) ? S_OK : S_FALSE;
801 STDMETHODIMP CGitEnumFormatEtc::Skip(ULONG celt)
803 if ((m_iCur + int(celt)) >= (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()))
804 return S_FALSE;
805 m_iCur += celt;
806 return S_OK;
809 STDMETHODIMP CGitEnumFormatEtc::Reset()
811 m_iCur = 0;
812 return S_OK;
815 STDMETHODIMP CGitEnumFormatEtc::Clone(IEnumFORMATETC** ppCloneEnumFormatEtc)
817 if (!ppCloneEnumFormatEtc)
818 return E_POINTER;
820 CGitEnumFormatEtc *newEnum = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_localonly, m_containsExistingFiles);
821 if (!newEnum)
822 return E_OUTOFMEMORY;
824 newEnum->AddRef();
825 newEnum->m_iCur = m_iCur;
826 *ppCloneEnumFormatEtc = newEnum;
827 return S_OK;