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.
20 #include "GitDataObject.h"
22 #include "UnicodeUtils.h"
23 #include "PathUtils.h"
25 #include "StringUtils.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
)
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;
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 //////////////////////////////////////////////////////////////////////////
65 //////////////////////////////////////////////////////////////////////////
67 STDMETHODIMP
GitDataObject::QueryInterface(REFIID riid
, void** ppvObject
)
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);
83 STDMETHODIMP_(ULONG
) GitDataObject::AddRef()
88 STDMETHODIMP_(ULONG
) GitDataObject::Release()
99 //////////////////////////////////////////////////////////////////////////
101 //////////////////////////////////////////////////////////////////////////
102 STDMETHODIMP
GitDataObject::GetData(FORMATETC
* pformatetcIn
, STGMEDIUM
* pmedium
)
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
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
]);
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
);
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
;
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())
163 m_allPaths
.push_back(m_gitPaths
[i
]);
167 if (HRESULT ret
= SizeTMult(max(size_t(1), m_allPaths
.size()) - 1, sizeof(FILEDESCRIPTOR
), &dataSize
); ret
!= S_OK
)
169 if (HRESULT ret
= SizeTAdd(sizeof(FILEGROUPDESCRIPTOR
), dataSize
, &dataSize
); ret
!= S_OK
)
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());
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
));
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;
203 pmedium
->hGlobal
= data
;
204 pmedium
->tymed
= TYMED_HGLOBAL
;
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
));
215 return E_OUTOFMEMORY
;
216 auto effect
= static_cast<DWORD
*>(GlobalLock(data
));
220 return E_OUTOFMEMORY
;
222 (*effect
) = DROPEFFECT_COPY
;
224 pmedium
->hGlobal
= data
;
225 pmedium
->tymed
= TYMED_HGLOBAL
;
228 else if ((pformatetcIn
->tymed
& TYMED_HGLOBAL
) && (pformatetcIn
->dwAspect
== DVASPECT_CONTENT
) && (pformatetcIn
->cfFormat
== CF_TEXT
))
231 // create the string from the path list
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();
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;
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
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();
267 text
+= g_Git
.CombinePath(m_gitPaths
[i
]);
271 pmedium
->tymed
= TYMED_HGLOBAL
;
273 if (HRESULT ret
= SizeTMult(static_cast<size_t>(text
.GetLength()) + 1, sizeof(wchar_t), &bufferSize
); ret
!= S_OK
)
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;
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())
293 if (HRESULT ret
= SizeTAdd(g_Git
.CombinePath(m_gitPaths
[i
]).GetLength(), nBufferSize
, &nBufferSize
); ret
!= S_OK
)
295 if (HRESULT ret
= SizeTAdd(1, nBufferSize
, &nBufferSize
); ret
!= S_OK
) // '\0' separator
299 if (HRESULT ret
= SizeTAdd(1, nBufferSize
, &nBufferSize
); ret
!= S_OK
)
301 if (HRESULT ret
= SizeTMult(nBufferSize
, sizeof(wchar_t), &nBufferSize
); ret
!= S_OK
)
303 if (HRESULT ret
= SizeTAdd(sizeof(DROPFILES
), nBufferSize
, &nBufferSize
); ret
!= S_OK
)
305 auto pBuffer
= std::make_unique
<char[]>(nBufferSize
);
307 auto df
= reinterpret_cast<DROPFILES
*>(pBuffer
.get());
308 df
->pFiles
= sizeof(DROPFILES
);
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())
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
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
);
332 memcpy(pMem
, pBuffer
.get(), nBufferSize
);
333 GlobalUnlock(pmedium
->hGlobal
);
335 pmedium
->pUnkForRelease
= nullptr;
338 else if ((pformatetcIn
->tymed
& TYMED_HGLOBAL
) && (pformatetcIn
->dwAspect
== DVASPECT_CONTENT
) && (pformatetcIn
->cfFormat
== CF_FILE_ATTRIBUTES_ARRAY
))
341 if (HRESULT ret
= SizeTMult(m_gitPaths
.GetCount(), sizeof(DWORD
), &nBufferSize
); ret
!= S_OK
)
343 if (HRESULT ret
= SizeTAdd(nBufferSize
, sizeof(FILE_ATTRIBUTES_ARRAY
), &nBufferSize
); ret
!= S_OK
)
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
);
365 memcpy(pMem
, pBuffer
.get(), nBufferSize
);
366 GlobalUnlock(pmedium
->hGlobal
);
368 pmedium
->pUnkForRelease
= nullptr;
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
]);
383 return DV_E_FORMATETC
;
386 STDMETHODIMP
GitDataObject::GetDataHere(FORMATETC
* /*pformatetc*/, STGMEDIUM
* /*pmedium*/)
391 STDMETHODIMP
GitDataObject::QueryGetData(FORMATETC
* pformatetc
)
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
)
406 if ((pformatetc
->tymed
& TYMED_HGLOBAL
) &&
407 (pformatetc
->dwAspect
== DVASPECT_CONTENT
) &&
408 ((pformatetc
->cfFormat
== CF_TEXT
) || (pformatetc
->cfFormat
== CF_UNICODETEXT
) || (pformatetc
->cfFormat
== CF_PREFERREDDROPEFFECT
)))
412 if ((pformatetc
->tymed
& TYMED_HGLOBAL
) &&
413 (pformatetc
->dwAspect
== DVASPECT_CONTENT
) &&
414 (pformatetc
->cfFormat
== CF_FILEDESCRIPTOR
) &&
415 !m_revision
.IsEmpty() &&
416 m_containsExistingFiles
)
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
)
428 if ((pformatetc
->tymed
& TYMED_HGLOBAL
) &&
429 (pformatetc
->dwAspect
== DVASPECT_CONTENT
) &&
430 (pformatetc
->cfFormat
== CF_FILE_ATTRIBUTES_ARRAY
) &&
431 m_containsExistingFiles
)
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
))
447 STDMETHODIMP
GitDataObject::GetCanonicalFormatEtc(FORMATETC
* /*pformatectIn*/, FORMATETC
* pformatetcOut
)
451 return DATA_S_SAMEFORMATETC
;
454 STDMETHODIMP
GitDataObject::SetData(FORMATETC
* pformatetc
, STGMEDIUM
* pmedium
, BOOL fRelease
)
456 if (!pformatetc
|| !pmedium
)
459 FORMATETC
* fetc
= new (std::nothrow
) FORMATETC
;
460 STGMEDIUM
* pStgMed
= new (std::nothrow
) STGMEDIUM
;
462 if (!fetc
|| !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
);
491 m_vecFormatEtc
.push_back(fetc
);
496 CopyMedium(pStgMed
, pmedium
, pformatetc
);
497 m_vecStgMedium
.push_back(pStgMed
);
502 STDMETHODIMP
GitDataObject::EnumFormatEtc(DWORD dwDirection
, IEnumFORMATETC
** ppenumFormatEtc
)
504 if (!ppenumFormatEtc
)
507 *ppenumFormatEtc
= nullptr;
511 *ppenumFormatEtc
= new (std::nothrow
) CGitEnumFormatEtc(m_vecFormatEtc
, m_revision
.IsEmpty(), m_containsExistingFiles
);
512 if (!*ppenumFormatEtc
)
513 return E_OUTOFMEMORY
;
514 (*ppenumFormatEtc
)->AddRef();
522 STDMETHODIMP
GitDataObject::DAdvise(FORMATETC
* /*pformatetc*/, DWORD
/*advf*/, IAdviseSink
* /*pAdvSink*/, DWORD
* /*pdwConnection*/)
524 return OLE_E_ADVISENOTSUPPORTED
;
527 STDMETHODIMP
GitDataObject::DUnadvise(DWORD
/*dwConnection*/)
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
)
542 pMedDest
->hGlobal
= static_cast<HGLOBAL
>(OleDuplicateData(pMedSrc
->hGlobal
, pFmtSrc
->cfFormat
, 0));
545 pMedDest
->hBitmap
= static_cast<HBITMAP
>(OleDuplicateData(pMedSrc
->hBitmap
, pFmtSrc
->cfFormat
, 0));
548 pMedDest
->hMetaFilePict
= static_cast<HMETAFILEPICT
>(OleDuplicateData(pMedSrc
->hMetaFilePict
, pFmtSrc
->cfFormat
, 0));
551 pMedDest
->hEnhMetaFile
= static_cast<HENHMETAFILE
>(OleDuplicateData(pMedSrc
->hEnhMetaFile
, pFmtSrc
->cfFormat
, 0));
554 pMedSrc
->lpszFileName
= static_cast<LPOLESTR
>(OleDuplicateData(pMedSrc
->lpszFileName
, pFmtSrc
->cfFormat
, 0));
557 pMedDest
->pstm
= pMedSrc
->pstm
;
558 pMedSrc
->pstm
->AddRef();
561 pMedDest
->pstg
= pMedSrc
->pstg
;
562 pMedSrc
->pstg
->AddRef();
568 pMedDest
->tymed
= pMedSrc
->tymed
;
569 pMedDest
->pUnkForRelease
= nullptr;
570 if (pMedSrc
->pUnkForRelease
)
572 pMedDest
->pUnkForRelease
= pMedSrc
->pUnkForRelease
;
573 pMedSrc
->pUnkForRelease
->AddRef();
578 //////////////////////////////////////////////////////////////////////////
580 //////////////////////////////////////////////////////////////////////////
581 HRESULT STDMETHODCALLTYPE
GitDataObject::SetAsyncMode(BOOL fDoOpAsync
)
583 m_bIsAsync
= fDoOpAsync
;
587 HRESULT STDMETHODCALLTYPE
GitDataObject::GetAsyncMode(BOOL
* pfIsOpAsync
)
592 *pfIsOpAsync
= m_bIsAsync
;
597 HRESULT STDMETHODCALLTYPE
GitDataObject::StartOperation(IBindCtx
* /*pbcReserved*/)
599 m_bInOperation
= TRUE
;
603 HRESULT STDMETHODCALLTYPE
GitDataObject::InOperation(BOOL
* pfInAsyncOp
)
608 *pfInAsyncOp
= m_bInOperation
;
613 HRESULT STDMETHODCALLTYPE
GitDataObject::EndOperation(HRESULT
/*hResult*/, IBindCtx
* /*pbcReserved*/, DWORD
/*dwEffects*/)
615 m_bInOperation
= FALSE
;
619 HRESULT
GitDataObject::SetDropDescription(DROPIMAGETYPE image
, LPCWSTR format
, LPCWSTR insert
)
621 if (!format
|| !insert
)
624 FORMATETC fetc
= { 0 };
625 fetc
.cfFormat
= static_cast<CLIPFORMAT
>(RegisterClipboardFormat(CFSTR_DROPDESCRIPTION
));
626 fetc
.dwAspect
= DVASPECT_CONTENT
;
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
)
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
)
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
;
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
;
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
;
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
;
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
;
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
;
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
;
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
;
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
;
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;
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
)
753 if (IID_IUnknown
== refiid
|| IID_IEnumFORMATETC
== refiid
)
754 *ppv
= static_cast<IEnumFORMATETC
*>(this);
756 return E_NOINTERFACE
;
762 STDMETHODIMP_(ULONG
) CGitEnumFormatEtc::AddRef()
764 return ++m_cRefCount
;
767 STDMETHODIMP_(ULONG
) CGitEnumFormatEtc::Release()
770 if (m_cRefCount
== 0)
778 STDMETHODIMP
CGitEnumFormatEtc::Next(ULONG celt
, LPFORMATETC lpFormatEtc
, ULONG
* pceltFetched
)
782 if (!pceltFetched
&& celt
!= 1) // pceltFetched can be NULL only for 1 item request
790 if (m_iCur
>= GITDATAOBJECT_NUMFORMATS
)
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
++];
800 *lpFormatEtc
++ = m_vecFormatEtc
[m_iCur
++ - GITDATAOBJECT_NUMFORMATS
];
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()))
818 STDMETHODIMP
CGitEnumFormatEtc::Reset()
824 STDMETHODIMP
CGitEnumFormatEtc::Clone(IEnumFORMATETC
** ppCloneEnumFormatEtc
)
826 if (!ppCloneEnumFormatEtc
)
829 CGitEnumFormatEtc
*newEnum
= new (std::nothrow
) CGitEnumFormatEtc(m_vecFormatEtc
, m_localonly
, m_containsExistingFiles
);
831 return E_OUTOFMEMORY
;
834 newEnum
->m_iCur
= m_iCur
;
835 *ppCloneEnumFormatEtc
= newEnum
;