Do not call new to create an unique_ptr
[TortoiseGit.git] / src / Git / GitDataObject.cpp
blobd393cb93017df863d7d0be5beff8277550b5863a
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016 - 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 = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILECONTENTS);
29 CLIPFORMAT CF_FILEDESCRIPTOR = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
30 CLIPFORMAT CF_PREFERREDDROPEFFECT = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
31 CLIPFORMAT CF_INETURL = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_INETURL);
32 CLIPFORMAT CF_SHELLURL = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLURL);
33 CLIPFORMAT CF_FILE_ATTRIBUTES_ARRAY = (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)
43 ASSERT((m_revision.IsEmpty() && m_iStripLength == -1) || (!m_revision.IsEmpty() && m_iStripLength >= -1)); // m_iStripLength only possible if rev is set
44 m_containsExistingFiles = false;
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(void)
89 return ++m_cRefCount;
92 STDMETHODIMP_(ULONG) GitDataObject::Release(void)
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 < (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 < (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(1, m_allPaths.size()) - 1) * sizeof(FILEDESCRIPTOR));
171 HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, dataSize);
173 FILEGROUPDESCRIPTOR* files = (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, (LPCTSTR)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 DWORD* effect = (DWORD*)GlobalLock(data);
215 (*effect) = DROPEFFECT_COPY;
216 GlobalUnlock(data);
217 pmedium->hGlobal = data;
218 pmedium->tymed = TYMED_HGLOBAL;
219 return S_OK;
221 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_TEXT))
223 // caller wants text
224 // create the string from the path list
225 CString text;
226 if (!m_gitPaths.IsEmpty())
228 // create a single string where the URLs are separated by newlines
229 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
231 text += m_gitPaths[i].GetWinPathString();
232 text += L"\r\n";
235 CStringA texta = CUnicodeUtils::GetUTF8(text);
236 pmedium->tymed = TYMED_HGLOBAL;
237 pmedium->hGlobal = GlobalAlloc(GHND, (texta.GetLength() + 1) * sizeof(char));
238 if (pmedium->hGlobal)
240 char* pMem = (char*)GlobalLock(pmedium->hGlobal);
241 strcpy_s(pMem, texta.GetLength() + 1, (LPCSTR)texta);
242 GlobalUnlock(pmedium->hGlobal);
244 pmedium->pUnkForRelease = nullptr;
245 return S_OK;
247 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && ((pformatetcIn->cfFormat == CF_UNICODETEXT) || (pformatetcIn->cfFormat == CF_INETURL) || (pformatetcIn->cfFormat == CF_SHELLURL)))
249 // caller wants Unicode text
250 // create the string from the path list
251 CString text;
252 if (!m_gitPaths.IsEmpty())
254 // create a single string where the URLs are separated by newlines
255 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
257 if (pformatetcIn->cfFormat == CF_UNICODETEXT)
258 text += m_gitPaths[i].GetWinPathString();
259 else
260 text += g_Git.CombinePath(m_gitPaths[i]);
261 text += L"\r\n";
264 pmedium->tymed = TYMED_HGLOBAL;
265 pmedium->hGlobal = GlobalAlloc(GHND, (text.GetLength() + 1) * sizeof(TCHAR));
266 if (pmedium->hGlobal)
268 TCHAR* pMem = (TCHAR*)GlobalLock(pmedium->hGlobal);
269 wcscpy_s(pMem, text.GetLength() + 1, (LPCTSTR)text);
270 GlobalUnlock(pmedium->hGlobal);
272 pmedium->pUnkForRelease = nullptr;
273 return S_OK;
275 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_HDROP))
277 int nLength = 0;
279 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
281 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
282 continue;
284 nLength += g_Git.CombinePath(m_gitPaths[i]).GetLength();
285 nLength += 1; // '\0' separator
288 int nBufferSize = sizeof(DROPFILES) + (nLength + 1) * sizeof(TCHAR);
289 auto pBuffer = std::make_unique<char[]>(nBufferSize);
290 SecureZeroMemory(pBuffer.get(), nBufferSize);
292 DROPFILES* df = (DROPFILES*)pBuffer.get();
293 df->pFiles = sizeof(DROPFILES);
294 df->fWide = 1;
296 TCHAR* pFilenames = (TCHAR*)(pBuffer.get() + sizeof(DROPFILES));
297 TCHAR* pCurrentFilename = pFilenames;
299 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
301 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
302 continue;
303 CString str = g_Git.CombinePath(m_gitPaths[i]);
304 wcscpy_s(pCurrentFilename, str.GetLength() + 1, (LPCWSTR)str);
305 pCurrentFilename += str.GetLength();
306 *pCurrentFilename = '\0'; // separator between file names
307 pCurrentFilename++;
309 *pCurrentFilename = '\0'; // terminate array
311 pmedium->tymed = TYMED_HGLOBAL;
312 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
313 if (pmedium->hGlobal)
315 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
316 if (pMem)
317 memcpy(pMem, pBuffer.get(), nBufferSize);
318 GlobalUnlock(pmedium->hGlobal);
320 pmedium->pUnkForRelease = nullptr;
321 return S_OK;
323 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILE_ATTRIBUTES_ARRAY))
325 int nBufferSize = sizeof(FILE_ATTRIBUTES_ARRAY) + m_gitPaths.GetCount() * sizeof(DWORD);
326 auto pBuffer = std::make_unique<char[]>(nBufferSize);
327 SecureZeroMemory(pBuffer.get(), nBufferSize);
329 FILE_ATTRIBUTES_ARRAY* cf = (FILE_ATTRIBUTES_ARRAY*)pBuffer.get();
330 cf->cItems = m_gitPaths.GetCount();
331 cf->dwProductFileAttributes = DWORD_MAX;
332 cf->dwSumFileAttributes = 0;
333 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
335 DWORD fileattribs = FILE_ATTRIBUTE_NORMAL;
336 cf->rgdwFileAttributes[i] = fileattribs;
337 cf->dwProductFileAttributes &= fileattribs;
338 cf->dwSumFileAttributes |= fileattribs;
341 pmedium->tymed = TYMED_HGLOBAL;
342 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
343 if (pmedium->hGlobal)
345 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
346 if (pMem)
347 memcpy(pMem, pBuffer.get(), nBufferSize);
348 GlobalUnlock(pmedium->hGlobal);
350 pmedium->pUnkForRelease = nullptr;
351 return S_OK;
354 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
356 if ((pformatetcIn->tymed == m_vecFormatEtc[i]->tymed) &&
357 (pformatetcIn->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
358 (pformatetcIn->cfFormat == m_vecFormatEtc[i]->cfFormat))
360 CopyMedium(pmedium, m_vecStgMedium[i], m_vecFormatEtc[i]);
361 return S_OK;
365 return DV_E_FORMATETC;
368 STDMETHODIMP GitDataObject::GetDataHere(FORMATETC* /*pformatetc*/, STGMEDIUM* /*pmedium*/)
370 return E_NOTIMPL;
373 STDMETHODIMP GitDataObject::QueryGetData(FORMATETC* pformatetc)
375 if (!pformatetc)
376 return E_INVALIDARG;
378 if (!(DVASPECT_CONTENT & pformatetc->dwAspect))
379 return DV_E_DVASPECT;
381 if ((pformatetc->tymed & TYMED_ISTREAM) &&
382 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
383 (pformatetc->cfFormat == CF_FILECONTENTS) &&
384 m_containsExistingFiles)
386 return S_OK;
388 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
389 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
390 ((pformatetc->cfFormat == CF_TEXT) || (pformatetc->cfFormat == CF_UNICODETEXT) || (pformatetc->cfFormat == CF_PREFERREDDROPEFFECT)))
392 return S_OK;
394 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
395 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
396 (pformatetc->cfFormat == CF_FILEDESCRIPTOR) &&
397 !m_revision.IsEmpty() &&
398 m_containsExistingFiles)
400 return S_OK;
402 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
403 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
404 ((pformatetc->cfFormat == CF_HDROP) || (pformatetc->cfFormat == CF_INETURL) || (pformatetc->cfFormat == CF_SHELLURL)) &&
405 m_revision.IsEmpty() &&
406 m_containsExistingFiles)
408 return S_OK;
410 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
411 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
412 (pformatetc->cfFormat == CF_FILE_ATTRIBUTES_ARRAY) &&
413 m_containsExistingFiles)
415 return S_OK;
418 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
420 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
421 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
422 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
423 return S_OK;
426 return DV_E_TYMED;
429 STDMETHODIMP GitDataObject::GetCanonicalFormatEtc(FORMATETC* /*pformatectIn*/, FORMATETC* pformatetcOut)
431 if (!pformatetcOut)
432 return E_INVALIDARG;
433 return DATA_S_SAMEFORMATETC;
436 STDMETHODIMP GitDataObject::SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease)
438 if (!pformatetc || !pmedium)
439 return E_INVALIDARG;
441 FORMATETC* fetc = new (std::nothrow) FORMATETC;
442 STGMEDIUM* pStgMed = new (std::nothrow) STGMEDIUM;
444 if (!fetc || !pStgMed)
446 delete fetc;
447 delete pStgMed;
448 return E_OUTOFMEMORY;
450 SecureZeroMemory(fetc, sizeof(FORMATETC));
451 SecureZeroMemory(pStgMed, sizeof(STGMEDIUM));
453 // do we already store this format?
454 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
456 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
457 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
458 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
460 // yes, this format is already in our object:
461 // we have to replace the existing format. To do that, we
462 // remove the format we already have
463 delete m_vecFormatEtc[i];
464 m_vecFormatEtc.erase(m_vecFormatEtc.begin() + i);
465 ReleaseStgMedium(m_vecStgMedium[i]);
466 delete m_vecStgMedium[i];
467 m_vecStgMedium.erase(m_vecStgMedium.begin() + i);
468 break;
472 *fetc = *pformatetc;
473 m_vecFormatEtc.push_back(fetc);
475 if (fRelease)
476 *pStgMed = *pmedium;
477 else
478 CopyMedium(pStgMed, pmedium, pformatetc);
479 m_vecStgMedium.push_back(pStgMed);
481 return S_OK;
484 STDMETHODIMP GitDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc)
486 if (!ppenumFormatEtc)
487 return E_POINTER;
489 *ppenumFormatEtc = nullptr;
490 switch (dwDirection)
492 case DATADIR_GET:
493 *ppenumFormatEtc = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_revision.IsEmpty(), m_containsExistingFiles);
494 if (!*ppenumFormatEtc)
495 return E_OUTOFMEMORY;
496 (*ppenumFormatEtc)->AddRef();
497 break;
498 default:
499 return E_NOTIMPL;
501 return S_OK;
504 STDMETHODIMP GitDataObject::DAdvise(FORMATETC* /*pformatetc*/, DWORD /*advf*/, IAdviseSink* /*pAdvSink*/, DWORD* /*pdwConnection*/)
506 return OLE_E_ADVISENOTSUPPORTED;
509 STDMETHODIMP GitDataObject::DUnadvise(DWORD /*dwConnection*/)
511 return E_NOTIMPL;
514 HRESULT STDMETHODCALLTYPE GitDataObject::EnumDAdvise(IEnumSTATDATA** /*ppenumAdvise*/)
516 return OLE_E_ADVISENOTSUPPORTED;
519 void GitDataObject::CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc)
521 switch (pMedSrc->tymed)
523 case TYMED_HGLOBAL:
524 pMedDest->hGlobal = (HGLOBAL)OleDuplicateData(pMedSrc->hGlobal, pFmtSrc->cfFormat, 0);
525 break;
526 case TYMED_GDI:
527 pMedDest->hBitmap = (HBITMAP)OleDuplicateData(pMedSrc->hBitmap, pFmtSrc->cfFormat, 0);
528 break;
529 case TYMED_MFPICT:
530 pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(pMedSrc->hMetaFilePict, pFmtSrc->cfFormat, 0);
531 break;
532 case TYMED_ENHMF:
533 pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(pMedSrc->hEnhMetaFile, pFmtSrc->cfFormat, 0);
534 break;
535 case TYMED_FILE:
536 pMedSrc->lpszFileName = (LPOLESTR)OleDuplicateData(pMedSrc->lpszFileName, pFmtSrc->cfFormat, 0);
537 break;
538 case TYMED_ISTREAM:
539 pMedDest->pstm = pMedSrc->pstm;
540 pMedSrc->pstm->AddRef();
541 break;
542 case TYMED_ISTORAGE:
543 pMedDest->pstg = pMedSrc->pstg;
544 pMedSrc->pstg->AddRef();
545 break;
546 case TYMED_NULL:
547 default:
548 break;
550 pMedDest->tymed = pMedSrc->tymed;
551 pMedDest->pUnkForRelease = nullptr;
552 if (pMedSrc->pUnkForRelease)
554 pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease;
555 pMedSrc->pUnkForRelease->AddRef();
560 //////////////////////////////////////////////////////////////////////////
561 // IAsyncOperation
562 //////////////////////////////////////////////////////////////////////////
563 HRESULT STDMETHODCALLTYPE GitDataObject::SetAsyncMode(BOOL fDoOpAsync)
565 m_bIsAsync = fDoOpAsync;
566 return S_OK;
569 HRESULT STDMETHODCALLTYPE GitDataObject::GetAsyncMode(BOOL* pfIsOpAsync)
571 if (!pfIsOpAsync)
572 return E_FAIL;
574 *pfIsOpAsync = m_bIsAsync;
576 return S_OK;
579 HRESULT STDMETHODCALLTYPE GitDataObject::StartOperation(IBindCtx* /*pbcReserved*/)
581 m_bInOperation = TRUE;
582 return S_OK;
585 HRESULT STDMETHODCALLTYPE GitDataObject::InOperation(BOOL* pfInAsyncOp)
587 if (!pfInAsyncOp)
588 return E_FAIL;
590 *pfInAsyncOp = m_bInOperation;
592 return S_OK;
595 HRESULT STDMETHODCALLTYPE GitDataObject::EndOperation(HRESULT /*hResult*/, IBindCtx* /*pbcReserved*/, DWORD /*dwEffects*/)
597 m_bInOperation = FALSE;
598 return S_OK;
601 HRESULT GitDataObject::SetDropDescription(DROPIMAGETYPE image, LPCTSTR format, LPCTSTR insert)
603 if (!format || !insert)
604 return E_INVALIDARG;
606 FORMATETC fetc = { 0 };
607 fetc.cfFormat = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_DROPDESCRIPTION);
608 fetc.dwAspect = DVASPECT_CONTENT;
609 fetc.lindex = -1;
610 fetc.tymed = TYMED_HGLOBAL;
612 STGMEDIUM medium = { 0 };
613 medium.hGlobal = GlobalAlloc(GHND, sizeof(DROPDESCRIPTION));
614 if (medium.hGlobal == 0)
615 return E_OUTOFMEMORY;
617 DROPDESCRIPTION* pDropDescription = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal);
618 if (!pDropDescription)
619 return E_FAIL;
620 StringCchCopy(pDropDescription->szInsert, _countof(pDropDescription->szInsert), insert);
621 StringCchCopy(pDropDescription->szMessage, _countof(pDropDescription->szMessage), format);
622 pDropDescription->type = image;
623 GlobalUnlock(medium.hGlobal);
624 return SetData(&fetc, &medium, TRUE);
627 void CGitEnumFormatEtc::Init(bool localonly, bool containsExistingFiles)
629 int index = 0;
630 m_formats[index].cfFormat = CF_UNICODETEXT;
631 m_formats[index].dwAspect = DVASPECT_CONTENT;
632 m_formats[index].lindex = -1;
633 m_formats[index].ptd = nullptr;
634 m_formats[index].tymed = TYMED_HGLOBAL;
635 index++;
637 m_formats[index].cfFormat = CF_TEXT;
638 m_formats[index].dwAspect = DVASPECT_CONTENT;
639 m_formats[index].lindex = -1;
640 m_formats[index].ptd = nullptr;
641 m_formats[index].tymed = TYMED_HGLOBAL;
642 index++;
644 m_formats[index].cfFormat = CF_PREFERREDDROPEFFECT;
645 m_formats[index].dwAspect = DVASPECT_CONTENT;
646 m_formats[index].lindex = -1;
647 m_formats[index].ptd = nullptr;
648 m_formats[index].tymed = TYMED_HGLOBAL;
649 index++;
651 if (containsExistingFiles && localonly)
653 m_formats[index].cfFormat = CF_INETURL;
654 m_formats[index].dwAspect = DVASPECT_CONTENT;
655 m_formats[index].lindex = -1;
656 m_formats[index].ptd = nullptr;
657 m_formats[index].tymed = TYMED_HGLOBAL;
658 index++;
660 m_formats[index].cfFormat = CF_SHELLURL;
661 m_formats[index].dwAspect = DVASPECT_CONTENT;
662 m_formats[index].lindex = -1;
663 m_formats[index].ptd = nullptr;
664 m_formats[index].tymed = TYMED_HGLOBAL;
665 index++;
668 m_formats[index].cfFormat = CF_FILE_ATTRIBUTES_ARRAY;
669 m_formats[index].dwAspect = DVASPECT_CONTENT;
670 m_formats[index].lindex = -1;
671 m_formats[index].ptd = nullptr;
672 m_formats[index].tymed = TYMED_HGLOBAL;
673 index++;
675 if (containsExistingFiles && localonly)
677 m_formats[index].cfFormat = CF_HDROP;
678 m_formats[index].dwAspect = DVASPECT_CONTENT;
679 m_formats[index].lindex = -1;
680 m_formats[index].ptd = nullptr;
681 m_formats[index].tymed = TYMED_HGLOBAL;
682 index++;
684 else if (containsExistingFiles)
686 m_formats[index].cfFormat = CF_FILECONTENTS;
687 m_formats[index].dwAspect = DVASPECT_CONTENT;
688 m_formats[index].lindex = -1;
689 m_formats[index].ptd = nullptr;
690 m_formats[index].tymed = TYMED_ISTREAM;
691 index++;
693 m_formats[index].cfFormat = CF_FILEDESCRIPTOR;
694 m_formats[index].dwAspect = DVASPECT_CONTENT;
695 m_formats[index].lindex = -1;
696 m_formats[index].ptd = nullptr;
697 m_formats[index].tymed = TYMED_HGLOBAL;
698 index++;
700 // clear possible leftovers
701 while (index < GITDATAOBJECT_NUMFORMATS)
703 m_formats[index].cfFormat = 0;
704 m_formats[index].dwAspect = 0;
705 m_formats[index].lindex = -1;
706 m_formats[index].ptd = nullptr;
707 m_formats[index].tymed = 0;
708 index++;
712 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC>& vec, bool localonly, bool containsExistingFiles)
713 : m_cRefCount(0)
714 , m_iCur(0)
715 , m_localonly(localonly)
716 , m_containsExistingFiles(containsExistingFiles)
718 for (size_t i = 0; i < vec.size(); ++i)
719 m_vecFormatEtc.push_back(vec[i]);
720 Init(localonly, containsExistingFiles);
723 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC*>& vec, bool localonly, bool containsExistingFiles)
724 : m_cRefCount(0)
725 , m_iCur(0)
726 , m_localonly(localonly)
727 , m_containsExistingFiles(containsExistingFiles)
729 for (size_t i = 0; i < vec.size(); ++i)
730 m_vecFormatEtc.push_back(*vec[i]);
731 Init(localonly, containsExistingFiles);
734 STDMETHODIMP CGitEnumFormatEtc::QueryInterface(REFIID refiid, void** ppv)
736 if (!ppv)
737 return E_POINTER;
738 *ppv = nullptr;
739 if (IID_IUnknown == refiid || IID_IEnumFORMATETC == refiid)
740 *ppv = static_cast<IEnumFORMATETC*>(this);
741 else
742 return E_NOINTERFACE;
744 AddRef();
745 return S_OK;
748 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::AddRef(void)
750 return ++m_cRefCount;
753 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::Release(void)
755 --m_cRefCount;
756 if (m_cRefCount == 0)
758 delete this;
759 return 0;
761 return m_cRefCount;
764 STDMETHODIMP CGitEnumFormatEtc::Next(ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched)
766 if (celt <= 0)
767 return E_INVALIDARG;
768 if (!pceltFetched && celt != 1) // pceltFetched can be NULL only for 1 item request
769 return E_POINTER;
770 if (!lpFormatEtc)
771 return E_POINTER;
773 if (pceltFetched)
774 *pceltFetched = 0;
776 if (m_iCur >= GITDATAOBJECT_NUMFORMATS)
777 return S_FALSE;
779 ULONG cReturn = celt;
781 while (m_iCur < (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()) && cReturn > 0)
783 if (m_iCur < GITDATAOBJECT_NUMFORMATS)
784 *lpFormatEtc++ = m_formats[m_iCur++];
785 else
786 *lpFormatEtc++ = m_vecFormatEtc[m_iCur++ - GITDATAOBJECT_NUMFORMATS];
787 --cReturn;
790 if (pceltFetched)
791 *pceltFetched = celt - cReturn;
793 return (cReturn == 0) ? S_OK : S_FALSE;
796 STDMETHODIMP CGitEnumFormatEtc::Skip(ULONG celt)
798 if ((m_iCur + int(celt)) >= (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()))
799 return S_FALSE;
800 m_iCur += celt;
801 return S_OK;
804 STDMETHODIMP CGitEnumFormatEtc::Reset(void)
806 m_iCur = 0;
807 return S_OK;
810 STDMETHODIMP CGitEnumFormatEtc::Clone(IEnumFORMATETC** ppCloneEnumFormatEtc)
812 if (!ppCloneEnumFormatEtc)
813 return E_POINTER;
815 CGitEnumFormatEtc *newEnum = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_localonly, m_containsExistingFiles);
816 if (!newEnum)
817 return E_OUTOFMEMORY;
819 newEnum->AddRef();
820 newEnum->m_iCur = m_iCur;
821 *ppCloneEnumFormatEtc = newEnum;
822 return S_OK;