Fix typos
[TortoiseGit.git] / src / Git / GitDataObject.cpp
blobf97b663c5d29fd59d5bc42641f406bd648d1b6bf
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016-2019, 2021-2023 - 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_iStripLength(stripLength)
40 ASSERT((m_revision.IsEmpty() && m_iStripLength == -1) || (!m_revision.IsEmpty() && m_iStripLength >= -1)); // m_iStripLength only possible if rev is set
41 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
43 if ((m_gitPaths[i].m_Action == 0 || (m_gitPaths[i].m_Action & ~(CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED))) && !m_gitPaths[i].IsDirectory())
45 m_containsExistingFiles = true;
46 break;
51 GitDataObject::~GitDataObject()
53 for (size_t i = 0; i < m_vecStgMedium.size(); ++i)
55 ReleaseStgMedium(m_vecStgMedium[i]);
56 delete m_vecStgMedium[i];
59 for (size_t j = 0; j < m_vecFormatEtc.size(); ++j)
60 delete m_vecFormatEtc[j];
63 //////////////////////////////////////////////////////////////////////////
64 // IUnknown
65 //////////////////////////////////////////////////////////////////////////
67 STDMETHODIMP GitDataObject::QueryInterface(REFIID riid, void** ppvObject)
69 if (!ppvObject)
70 return E_POINTER;
71 *ppvObject = NULL;
72 if (IsEqualIID(IID_IUnknown, riid) || IsEqualIID(IID_IDataObject, riid))
73 *ppvObject = static_cast<IDataObject*>(this);
74 else if (IsEqualIID(riid, IID_IDataObjectAsyncCapability))
75 *ppvObject = static_cast<IDataObjectAsyncCapability*>(this);
76 else
77 return E_NOINTERFACE;
79 AddRef();
80 return S_OK;
83 STDMETHODIMP_(ULONG) GitDataObject::AddRef()
85 return ++m_cRefCount;
88 STDMETHODIMP_(ULONG) GitDataObject::Release()
90 --m_cRefCount;
91 if (m_cRefCount == 0)
93 delete this;
94 return 0;
96 return m_cRefCount;
99 //////////////////////////////////////////////////////////////////////////
100 // IDataObject
101 //////////////////////////////////////////////////////////////////////////
102 STDMETHODIMP GitDataObject::GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium)
104 if (!pformatetcIn)
105 return E_INVALIDARG;
106 if (!pmedium)
107 return E_POINTER;
108 pmedium->hGlobal = nullptr;
110 if ((pformatetcIn->tymed & TYMED_ISTREAM) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILECONTENTS))
112 // supports the IStream format.
113 // The lindex param is the index of the file to return
114 CString filepath;
115 IStream* pIStream = nullptr;
117 // Note: we don't get called for directories since those are simply created and don't
118 // need to be fetched.
120 // Note2: It would be really nice if we could get a stream from the subversion library
121 // from where we could read the file contents. But the Subversion lib is not implemented
122 // to *read* from a remote stream but so that the library *writes* to a stream we pass.
123 // Since we can't get such a read stream, we have to fetch the file in whole first to
124 // a temp location and then pass the shell an IStream for that temp file.
126 if (m_revision.IsEmpty())
128 if (pformatetcIn->lindex >= 0 && pformatetcIn->lindex < static_cast<LONG>(m_allPaths.size()))
129 filepath = g_Git.CombinePath(m_allPaths[pformatetcIn->lindex]);
131 else
133 filepath = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
134 if (pformatetcIn->lindex >= 0 && pformatetcIn->lindex < static_cast<LONG>(m_allPaths.size()))
136 if (g_Git.GetOneFile(m_revision.ToString(), m_allPaths[pformatetcIn->lindex], filepath))
138 DeleteFile(filepath);
139 return STG_E_ACCESSDENIED;
144 HRESULT res = SHCreateStreamOnFileEx(filepath, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &pIStream);
145 if (res == S_OK)
147 // http://blogs.msdn.com/b/oldnewthing/archive/2014/09/18/10558763.aspx
148 LARGE_INTEGER liZero = { 0, 0 };
149 pIStream->Seek(liZero, STREAM_SEEK_END, nullptr);
151 pmedium->pstm = pIStream;
152 pmedium->tymed = TYMED_ISTREAM;
153 return S_OK;
155 return res;
157 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILEDESCRIPTOR))
159 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
161 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
162 continue;
163 m_allPaths.push_back(m_gitPaths[i]);
166 size_t dataSize;
167 if (HRESULT ret = SizeTMult(max(size_t(1), m_allPaths.size()) - 1, sizeof(FILEDESCRIPTOR), &dataSize); ret != S_OK)
168 return ret;
169 if (HRESULT ret = SizeTAdd(sizeof(FILEGROUPDESCRIPTOR), dataSize, &dataSize); ret != S_OK)
170 return ret;
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 size_t bufferSize;
273 if (HRESULT ret = SizeTMult(static_cast<size_t>(text.GetLength()) + 1, sizeof(wchar_t), &bufferSize); ret != S_OK)
274 return ret;
275 pmedium->hGlobal = GlobalAlloc(GHND, bufferSize);
276 if (pmedium->hGlobal)
278 auto pMem = static_cast<wchar_t*>(GlobalLock(pmedium->hGlobal));
279 wcscpy_s(pMem, static_cast<size_t>(text.GetLength()) + 1, static_cast<LPCWSTR>(text));
280 GlobalUnlock(pmedium->hGlobal);
282 pmedium->pUnkForRelease = nullptr;
283 return S_OK;
285 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_HDROP))
287 size_t nBufferSize = 0;
288 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
290 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
291 continue;
293 if (HRESULT ret = SizeTAdd(g_Git.CombinePath(m_gitPaths[i]).GetLength(), nBufferSize, &nBufferSize); ret != S_OK)
294 return ret;
295 if (HRESULT ret = SizeTAdd(1, nBufferSize, &nBufferSize); ret != S_OK) // '\0' separator
296 return ret;
299 if (HRESULT ret = SizeTAdd(1, nBufferSize, &nBufferSize); ret != S_OK)
300 return ret;
301 if (HRESULT ret = SizeTMult(nBufferSize, sizeof(wchar_t), &nBufferSize); ret != S_OK)
302 return ret;
303 if (HRESULT ret = SizeTAdd(sizeof(DROPFILES), nBufferSize, &nBufferSize); ret != S_OK)
304 return ret;
305 auto pBuffer = std::make_unique<char[]>(nBufferSize);
307 auto df = reinterpret_cast<DROPFILES*>(pBuffer.get());
308 df->pFiles = sizeof(DROPFILES);
309 df->fWide = 1;
311 auto pFilenames = reinterpret_cast<wchar_t*>(pBuffer.get() + sizeof(DROPFILES));
312 wchar_t* pCurrentFilename = pFilenames;
314 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
316 if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory())
317 continue;
318 CString str = g_Git.CombinePath(m_gitPaths[i]);
319 wcscpy_s(pCurrentFilename, static_cast<size_t>(str.GetLength()) + 1, static_cast<LPCWSTR>(str));
320 pCurrentFilename += str.GetLength();
321 *pCurrentFilename = '\0'; // separator between file names
322 pCurrentFilename++;
324 *pCurrentFilename = '\0'; // terminate array
326 pmedium->tymed = TYMED_HGLOBAL;
327 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
328 if (pmedium->hGlobal)
330 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
331 if (pMem)
332 memcpy(pMem, pBuffer.get(), nBufferSize);
333 GlobalUnlock(pmedium->hGlobal);
335 pmedium->pUnkForRelease = nullptr;
336 return S_OK;
338 else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILE_ATTRIBUTES_ARRAY))
340 size_t nBufferSize;
341 if (HRESULT ret = SizeTMult(m_gitPaths.GetCount(), sizeof(DWORD), &nBufferSize); ret != S_OK)
342 return ret;
343 if (HRESULT ret = SizeTAdd(nBufferSize, sizeof(FILE_ATTRIBUTES_ARRAY), &nBufferSize); ret != S_OK)
344 return ret;
345 auto pBuffer = std::make_unique<char[]>(nBufferSize);
347 auto cf = reinterpret_cast<FILE_ATTRIBUTES_ARRAY*>(pBuffer.get());
348 cf->cItems = m_gitPaths.GetCount();
349 cf->dwProductFileAttributes = DWORD_MAX;
350 cf->dwSumFileAttributes = 0;
351 for (int i = 0; i < m_gitPaths.GetCount(); ++i)
353 DWORD fileattribs = FILE_ATTRIBUTE_NORMAL;
354 cf->rgdwFileAttributes[i] = fileattribs;
355 cf->dwProductFileAttributes &= fileattribs;
356 cf->dwSumFileAttributes |= fileattribs;
359 pmedium->tymed = TYMED_HGLOBAL;
360 pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize);
361 if (pmedium->hGlobal)
363 LPVOID pMem = ::GlobalLock(pmedium->hGlobal);
364 if (pMem)
365 memcpy(pMem, pBuffer.get(), nBufferSize);
366 GlobalUnlock(pmedium->hGlobal);
368 pmedium->pUnkForRelease = nullptr;
369 return S_OK;
372 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
374 if ((pformatetcIn->tymed == m_vecFormatEtc[i]->tymed) &&
375 (pformatetcIn->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
376 (pformatetcIn->cfFormat == m_vecFormatEtc[i]->cfFormat))
378 CopyMedium(pmedium, m_vecStgMedium[i], m_vecFormatEtc[i]);
379 return S_OK;
383 return DV_E_FORMATETC;
386 STDMETHODIMP GitDataObject::GetDataHere(FORMATETC* /*pformatetc*/, STGMEDIUM* /*pmedium*/)
388 return E_NOTIMPL;
391 STDMETHODIMP GitDataObject::QueryGetData(FORMATETC* pformatetc)
393 if (!pformatetc)
394 return E_INVALIDARG;
396 if (!(DVASPECT_CONTENT & pformatetc->dwAspect))
397 return DV_E_DVASPECT;
399 if ((pformatetc->tymed & TYMED_ISTREAM) &&
400 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
401 (pformatetc->cfFormat == CF_FILECONTENTS) &&
402 m_containsExistingFiles)
404 return S_OK;
406 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
407 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
408 ((pformatetc->cfFormat == CF_TEXT) || (pformatetc->cfFormat == CF_UNICODETEXT) || (pformatetc->cfFormat == CF_PREFERREDDROPEFFECT)))
410 return S_OK;
412 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
413 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
414 (pformatetc->cfFormat == CF_FILEDESCRIPTOR) &&
415 !m_revision.IsEmpty() &&
416 m_containsExistingFiles)
418 return S_OK;
420 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
421 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
422 ((pformatetc->cfFormat == CF_HDROP) || (pformatetc->cfFormat == CF_INETURL) || (pformatetc->cfFormat == CF_SHELLURL)) &&
423 m_revision.IsEmpty() &&
424 m_containsExistingFiles)
426 return S_OK;
428 if ((pformatetc->tymed & TYMED_HGLOBAL) &&
429 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
430 (pformatetc->cfFormat == CF_FILE_ATTRIBUTES_ARRAY) &&
431 m_containsExistingFiles)
433 return S_OK;
436 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
438 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
439 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
440 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
441 return S_OK;
444 return DV_E_TYMED;
447 STDMETHODIMP GitDataObject::GetCanonicalFormatEtc(FORMATETC* /*pformatectIn*/, FORMATETC* pformatetcOut)
449 if (!pformatetcOut)
450 return E_INVALIDARG;
451 return DATA_S_SAMEFORMATETC;
454 STDMETHODIMP GitDataObject::SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease)
456 if (!pformatetc || !pmedium)
457 return E_INVALIDARG;
459 FORMATETC* fetc = new (std::nothrow) FORMATETC;
460 STGMEDIUM* pStgMed = new (std::nothrow) STGMEDIUM;
462 if (!fetc || !pStgMed)
464 delete fetc;
465 delete pStgMed;
466 return E_OUTOFMEMORY;
468 SecureZeroMemory(fetc, sizeof(FORMATETC));
469 SecureZeroMemory(pStgMed, sizeof(STGMEDIUM));
471 // do we already store this format?
472 for (size_t i = 0; i < m_vecFormatEtc.size(); ++i)
474 if ((pformatetc->tymed == m_vecFormatEtc[i]->tymed) &&
475 (pformatetc->dwAspect == m_vecFormatEtc[i]->dwAspect) &&
476 (pformatetc->cfFormat == m_vecFormatEtc[i]->cfFormat))
478 // yes, this format is already in our object:
479 // we have to replace the existing format. To do that, we
480 // remove the format we already have
481 delete m_vecFormatEtc[i];
482 m_vecFormatEtc.erase(m_vecFormatEtc.begin() + i);
483 ReleaseStgMedium(m_vecStgMedium[i]);
484 delete m_vecStgMedium[i];
485 m_vecStgMedium.erase(m_vecStgMedium.begin() + i);
486 break;
490 *fetc = *pformatetc;
491 m_vecFormatEtc.push_back(fetc);
493 if (fRelease)
494 *pStgMed = *pmedium;
495 else
496 CopyMedium(pStgMed, pmedium, pformatetc);
497 m_vecStgMedium.push_back(pStgMed);
499 return S_OK;
502 STDMETHODIMP GitDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc)
504 if (!ppenumFormatEtc)
505 return E_POINTER;
507 *ppenumFormatEtc = nullptr;
508 switch (dwDirection)
510 case DATADIR_GET:
511 *ppenumFormatEtc = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_revision.IsEmpty(), m_containsExistingFiles);
512 if (!*ppenumFormatEtc)
513 return E_OUTOFMEMORY;
514 (*ppenumFormatEtc)->AddRef();
515 break;
516 default:
517 return E_NOTIMPL;
519 return S_OK;
522 STDMETHODIMP GitDataObject::DAdvise(FORMATETC* /*pformatetc*/, DWORD /*advf*/, IAdviseSink* /*pAdvSink*/, DWORD* /*pdwConnection*/)
524 return OLE_E_ADVISENOTSUPPORTED;
527 STDMETHODIMP GitDataObject::DUnadvise(DWORD /*dwConnection*/)
529 return E_NOTIMPL;
532 HRESULT STDMETHODCALLTYPE GitDataObject::EnumDAdvise(IEnumSTATDATA** /*ppenumAdvise*/)
534 return OLE_E_ADVISENOTSUPPORTED;
537 void GitDataObject::CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc)
539 switch (pMedSrc->tymed)
541 case TYMED_HGLOBAL:
542 pMedDest->hGlobal = static_cast<HGLOBAL>(OleDuplicateData(pMedSrc->hGlobal, pFmtSrc->cfFormat, 0));
543 break;
544 case TYMED_GDI:
545 pMedDest->hBitmap = static_cast<HBITMAP>(OleDuplicateData(pMedSrc->hBitmap, pFmtSrc->cfFormat, 0));
546 break;
547 case TYMED_MFPICT:
548 pMedDest->hMetaFilePict = static_cast<HMETAFILEPICT>(OleDuplicateData(pMedSrc->hMetaFilePict, pFmtSrc->cfFormat, 0));
549 break;
550 case TYMED_ENHMF:
551 pMedDest->hEnhMetaFile = static_cast<HENHMETAFILE>(OleDuplicateData(pMedSrc->hEnhMetaFile, pFmtSrc->cfFormat, 0));
552 break;
553 case TYMED_FILE:
554 pMedSrc->lpszFileName = static_cast<LPOLESTR>(OleDuplicateData(pMedSrc->lpszFileName, pFmtSrc->cfFormat, 0));
555 break;
556 case TYMED_ISTREAM:
557 pMedDest->pstm = pMedSrc->pstm;
558 pMedSrc->pstm->AddRef();
559 break;
560 case TYMED_ISTORAGE:
561 pMedDest->pstg = pMedSrc->pstg;
562 pMedSrc->pstg->AddRef();
563 break;
564 case TYMED_NULL:
565 default:
566 break;
568 pMedDest->tymed = pMedSrc->tymed;
569 pMedDest->pUnkForRelease = nullptr;
570 if (pMedSrc->pUnkForRelease)
572 pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease;
573 pMedSrc->pUnkForRelease->AddRef();
578 //////////////////////////////////////////////////////////////////////////
579 // IAsyncOperation
580 //////////////////////////////////////////////////////////////////////////
581 HRESULT STDMETHODCALLTYPE GitDataObject::SetAsyncMode(BOOL fDoOpAsync)
583 m_bIsAsync = fDoOpAsync;
584 return S_OK;
587 HRESULT STDMETHODCALLTYPE GitDataObject::GetAsyncMode(BOOL* pfIsOpAsync)
589 if (!pfIsOpAsync)
590 return E_FAIL;
592 *pfIsOpAsync = m_bIsAsync;
594 return S_OK;
597 HRESULT STDMETHODCALLTYPE GitDataObject::StartOperation(IBindCtx* /*pbcReserved*/)
599 m_bInOperation = TRUE;
600 return S_OK;
603 HRESULT STDMETHODCALLTYPE GitDataObject::InOperation(BOOL* pfInAsyncOp)
605 if (!pfInAsyncOp)
606 return E_FAIL;
608 *pfInAsyncOp = m_bInOperation;
610 return S_OK;
613 HRESULT STDMETHODCALLTYPE GitDataObject::EndOperation(HRESULT /*hResult*/, IBindCtx* /*pbcReserved*/, DWORD /*dwEffects*/)
615 m_bInOperation = FALSE;
616 return S_OK;
619 HRESULT GitDataObject::SetDropDescription(DROPIMAGETYPE image, LPCWSTR format, LPCWSTR insert)
621 if (!format || !insert)
622 return E_INVALIDARG;
624 FORMATETC fetc = { 0 };
625 fetc.cfFormat = static_cast<CLIPFORMAT>(RegisterClipboardFormat(CFSTR_DROPDESCRIPTION));
626 fetc.dwAspect = DVASPECT_CONTENT;
627 fetc.lindex = -1;
628 fetc.tymed = TYMED_HGLOBAL;
630 STGMEDIUM medium = { 0 };
631 medium.hGlobal = GlobalAlloc(GHND, sizeof(DROPDESCRIPTION));
632 if (medium.hGlobal == 0)
633 return E_OUTOFMEMORY;
635 auto pDropDescription = static_cast<DROPDESCRIPTION*>(GlobalLock(medium.hGlobal));
636 if (!pDropDescription)
637 return E_FAIL;
638 StringCchCopy(pDropDescription->szInsert, _countof(pDropDescription->szInsert), insert);
639 StringCchCopy(pDropDescription->szMessage, _countof(pDropDescription->szMessage), format);
640 pDropDescription->type = image;
641 GlobalUnlock(medium.hGlobal);
642 return SetData(&fetc, &medium, TRUE);
645 void CGitEnumFormatEtc::Init(bool localonly, bool containsExistingFiles)
647 int index = 0;
648 m_formats[index].cfFormat = CF_UNICODETEXT;
649 m_formats[index].dwAspect = DVASPECT_CONTENT;
650 m_formats[index].lindex = -1;
651 m_formats[index].ptd = nullptr;
652 m_formats[index].tymed = TYMED_HGLOBAL;
653 index++;
655 m_formats[index].cfFormat = CF_TEXT;
656 m_formats[index].dwAspect = DVASPECT_CONTENT;
657 m_formats[index].lindex = -1;
658 m_formats[index].ptd = nullptr;
659 m_formats[index].tymed = TYMED_HGLOBAL;
660 index++;
662 m_formats[index].cfFormat = CF_PREFERREDDROPEFFECT;
663 m_formats[index].dwAspect = DVASPECT_CONTENT;
664 m_formats[index].lindex = -1;
665 m_formats[index].ptd = nullptr;
666 m_formats[index].tymed = TYMED_HGLOBAL;
667 index++;
669 if (containsExistingFiles && localonly)
671 m_formats[index].cfFormat = CF_INETURL;
672 m_formats[index].dwAspect = DVASPECT_CONTENT;
673 m_formats[index].lindex = -1;
674 m_formats[index].ptd = nullptr;
675 m_formats[index].tymed = TYMED_HGLOBAL;
676 index++;
678 m_formats[index].cfFormat = CF_SHELLURL;
679 m_formats[index].dwAspect = DVASPECT_CONTENT;
680 m_formats[index].lindex = -1;
681 m_formats[index].ptd = nullptr;
682 m_formats[index].tymed = TYMED_HGLOBAL;
683 index++;
686 m_formats[index].cfFormat = CF_FILE_ATTRIBUTES_ARRAY;
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_HGLOBAL;
691 index++;
693 if (containsExistingFiles && localonly)
695 m_formats[index].cfFormat = CF_HDROP;
696 m_formats[index].dwAspect = DVASPECT_CONTENT;
697 m_formats[index].lindex = -1;
698 m_formats[index].ptd = nullptr;
699 m_formats[index].tymed = TYMED_HGLOBAL;
700 index++;
702 else if (containsExistingFiles)
704 m_formats[index].cfFormat = CF_FILECONTENTS;
705 m_formats[index].dwAspect = DVASPECT_CONTENT;
706 m_formats[index].lindex = -1;
707 m_formats[index].ptd = nullptr;
708 m_formats[index].tymed = TYMED_ISTREAM;
709 index++;
711 m_formats[index].cfFormat = CF_FILEDESCRIPTOR;
712 m_formats[index].dwAspect = DVASPECT_CONTENT;
713 m_formats[index].lindex = -1;
714 m_formats[index].ptd = nullptr;
715 m_formats[index].tymed = TYMED_HGLOBAL;
716 index++;
718 // clear possible leftovers
719 while (index < GITDATAOBJECT_NUMFORMATS)
721 m_formats[index].cfFormat = 0;
722 m_formats[index].dwAspect = 0;
723 m_formats[index].lindex = -1;
724 m_formats[index].ptd = nullptr;
725 m_formats[index].tymed = 0;
726 index++;
730 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC>& vec, bool localonly, bool containsExistingFiles)
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 CGitEnumFormatEtc::CGitEnumFormatEtc(const std::vector<FORMATETC*>& vec, bool localonly, bool containsExistingFiles)
740 : m_localonly(localonly)
741 , m_containsExistingFiles(containsExistingFiles)
743 for (size_t i = 0; i < vec.size(); ++i)
744 m_vecFormatEtc.push_back(*vec[i]);
745 Init(localonly, containsExistingFiles);
748 STDMETHODIMP CGitEnumFormatEtc::QueryInterface(REFIID refiid, void** ppv)
750 if (!ppv)
751 return E_POINTER;
752 *ppv = nullptr;
753 if (IID_IUnknown == refiid || IID_IEnumFORMATETC == refiid)
754 *ppv = static_cast<IEnumFORMATETC*>(this);
755 else
756 return E_NOINTERFACE;
758 AddRef();
759 return S_OK;
762 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::AddRef()
764 return ++m_cRefCount;
767 STDMETHODIMP_(ULONG) CGitEnumFormatEtc::Release()
769 --m_cRefCount;
770 if (m_cRefCount == 0)
772 delete this;
773 return 0;
775 return m_cRefCount;
778 STDMETHODIMP CGitEnumFormatEtc::Next(ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched)
780 if (celt <= 0)
781 return E_INVALIDARG;
782 if (!pceltFetched && celt != 1) // pceltFetched can be NULL only for 1 item request
783 return E_POINTER;
784 if (!lpFormatEtc)
785 return E_POINTER;
787 if (pceltFetched)
788 *pceltFetched = 0;
790 if (m_iCur >= GITDATAOBJECT_NUMFORMATS)
791 return S_FALSE;
793 ULONG cReturn = celt;
795 while (m_iCur < (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()) && cReturn > 0)
797 if (m_iCur < GITDATAOBJECT_NUMFORMATS)
798 *lpFormatEtc++ = m_formats[m_iCur++];
799 else
800 *lpFormatEtc++ = m_vecFormatEtc[m_iCur++ - GITDATAOBJECT_NUMFORMATS];
801 --cReturn;
804 if (pceltFetched)
805 *pceltFetched = celt - cReturn;
807 return (cReturn == 0) ? S_OK : S_FALSE;
810 STDMETHODIMP CGitEnumFormatEtc::Skip(ULONG celt)
812 if ((m_iCur + int(celt)) >= (GITDATAOBJECT_NUMFORMATS + m_vecFormatEtc.size()))
813 return S_FALSE;
814 m_iCur += celt;
815 return S_OK;
818 STDMETHODIMP CGitEnumFormatEtc::Reset()
820 m_iCur = 0;
821 return S_OK;
824 STDMETHODIMP CGitEnumFormatEtc::Clone(IEnumFORMATETC** ppCloneEnumFormatEtc)
826 if (!ppCloneEnumFormatEtc)
827 return E_POINTER;
829 CGitEnumFormatEtc *newEnum = new (std::nothrow) CGitEnumFormatEtc(m_vecFormatEtc, m_localonly, m_containsExistingFiles);
830 if (!newEnum)
831 return E_OUTOFMEMORY;
833 newEnum->AddRef();
834 newEnum->m_iCur = m_iCur;
835 *ppCloneEnumFormatEtc = newEnum;
836 return S_OK;