|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include <shlobj.h> |
|
7 |
|
8 #include "nsDataObjCollection.h" |
|
9 #include "nsClipboard.h" |
|
10 #include "IEnumFE.h" |
|
11 |
|
12 #include <ole2.h> |
|
13 |
|
14 // {25589C3E-1FAC-47b9-BF43-CAEA89B79533} |
|
15 const IID IID_IDataObjCollection = |
|
16 {0x25589c3e, 0x1fac, 0x47b9, {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}}; |
|
17 |
|
18 /* |
|
19 * Class nsDataObjCollection |
|
20 */ |
|
21 |
|
22 nsDataObjCollection::nsDataObjCollection() |
|
23 : m_cRef(0), mIsAsyncMode(FALSE), mIsInOperation(FALSE) |
|
24 { |
|
25 m_enumFE = new CEnumFormatEtc(); |
|
26 m_enumFE->AddRef(); |
|
27 } |
|
28 |
|
29 nsDataObjCollection::~nsDataObjCollection() |
|
30 { |
|
31 mDataFlavors.Clear(); |
|
32 mDataObjects.Clear(); |
|
33 |
|
34 m_enumFE->Release(); |
|
35 } |
|
36 |
|
37 |
|
38 // IUnknown interface methods - see iunknown.h for documentation |
|
39 STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) |
|
40 { |
|
41 *ppv=nullptr; |
|
42 |
|
43 if ( (IID_IUnknown == riid) || (IID_IDataObject == riid) ) { |
|
44 *ppv = static_cast<IDataObject*>(this); |
|
45 AddRef(); |
|
46 return NOERROR; |
|
47 } |
|
48 |
|
49 if ( IID_IDataObjCollection == riid ) { |
|
50 *ppv = static_cast<nsIDataObjCollection*>(this); |
|
51 AddRef(); |
|
52 return NOERROR; |
|
53 } |
|
54 |
|
55 return E_NOINTERFACE; |
|
56 } |
|
57 |
|
58 STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() |
|
59 { |
|
60 return ++m_cRef; |
|
61 } |
|
62 |
|
63 STDMETHODIMP_(ULONG) nsDataObjCollection::Release() |
|
64 { |
|
65 if (0 != --m_cRef) |
|
66 return m_cRef; |
|
67 |
|
68 delete this; |
|
69 |
|
70 return 0; |
|
71 } |
|
72 |
|
73 BOOL nsDataObjCollection::FormatsMatch(const FORMATETC& source, |
|
74 const FORMATETC& target) const |
|
75 { |
|
76 if ((source.cfFormat == target.cfFormat) && |
|
77 (source.dwAspect & target.dwAspect) && |
|
78 (source.tymed & target.tymed)) { |
|
79 return TRUE; |
|
80 } else { |
|
81 return FALSE; |
|
82 } |
|
83 } |
|
84 |
|
85 // IDataObject methods |
|
86 STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) |
|
87 { |
|
88 static CLIPFORMAT fileDescriptorFlavorA = |
|
89 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); |
|
90 static CLIPFORMAT fileDescriptorFlavorW = |
|
91 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); |
|
92 static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS); |
|
93 |
|
94 switch (pFE->cfFormat) { |
|
95 case CF_TEXT: |
|
96 case CF_UNICODETEXT: |
|
97 return GetText(pFE, pSTM); |
|
98 case CF_HDROP: |
|
99 return GetFile(pFE, pSTM); |
|
100 default: |
|
101 if (pFE->cfFormat == fileDescriptorFlavorA || |
|
102 pFE->cfFormat == fileDescriptorFlavorW) { |
|
103 return GetFileDescriptors(pFE, pSTM); |
|
104 } |
|
105 if (pFE->cfFormat == fileFlavor) { |
|
106 return GetFileContents(pFE, pSTM); |
|
107 } |
|
108 } |
|
109 return GetFirstSupporting(pFE, pSTM); |
|
110 } |
|
111 |
|
112 STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) |
|
113 { |
|
114 return E_FAIL; |
|
115 } |
|
116 |
|
117 // Other objects querying to see if we support a particular format |
|
118 STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) |
|
119 { |
|
120 UINT format = nsClipboard::GetFormat(MULTI_MIME); |
|
121 |
|
122 if (format == pFE->cfFormat) { |
|
123 return S_OK; |
|
124 } |
|
125 |
|
126 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
127 IDataObject * dataObj = mDataObjects.ElementAt(i); |
|
128 if (S_OK == dataObj->QueryGetData(pFE)) { |
|
129 return S_OK; |
|
130 } |
|
131 } |
|
132 |
|
133 return DV_E_FORMATETC; |
|
134 } |
|
135 |
|
136 STDMETHODIMP nsDataObjCollection::GetCanonicalFormatEtc(LPFORMATETC pFEIn, |
|
137 LPFORMATETC pFEOut) |
|
138 { |
|
139 return E_NOTIMPL; |
|
140 } |
|
141 |
|
142 STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, |
|
143 LPSTGMEDIUM pSTM, |
|
144 BOOL fRelease) |
|
145 { |
|
146 // Set arbitrary data formats on the first object in the collection and let |
|
147 // it handle the heavy lifting |
|
148 if (mDataObjects.Length() == 0) |
|
149 return E_FAIL; |
|
150 return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease); |
|
151 } |
|
152 |
|
153 STDMETHODIMP nsDataObjCollection::EnumFormatEtc(DWORD dwDir, |
|
154 LPENUMFORMATETC *ppEnum) |
|
155 { |
|
156 if (dwDir == DATADIR_GET) { |
|
157 // Clone addref's the new enumerator. |
|
158 m_enumFE->Clone(ppEnum); |
|
159 if (!(*ppEnum)) |
|
160 return E_FAIL; |
|
161 (*ppEnum)->Reset(); |
|
162 return S_OK; |
|
163 } |
|
164 |
|
165 return E_NOTIMPL; |
|
166 } |
|
167 |
|
168 STDMETHODIMP nsDataObjCollection::DAdvise(LPFORMATETC pFE, |
|
169 DWORD dwFlags, |
|
170 LPADVISESINK pIAdviseSink, |
|
171 DWORD* pdwConn) |
|
172 { |
|
173 return OLE_E_ADVISENOTSUPPORTED; |
|
174 } |
|
175 |
|
176 STDMETHODIMP nsDataObjCollection::DUnadvise(DWORD dwConn) |
|
177 { |
|
178 return OLE_E_ADVISENOTSUPPORTED; |
|
179 } |
|
180 |
|
181 STDMETHODIMP nsDataObjCollection::EnumDAdvise(LPENUMSTATDATA *ppEnum) |
|
182 { |
|
183 return OLE_E_ADVISENOTSUPPORTED; |
|
184 } |
|
185 |
|
186 // GetData and SetData helper functions |
|
187 HRESULT nsDataObjCollection::AddSetFormat(FORMATETC& aFE) |
|
188 { |
|
189 return S_OK; |
|
190 } |
|
191 |
|
192 HRESULT nsDataObjCollection::AddGetFormat(FORMATETC& aFE) |
|
193 { |
|
194 return S_OK; |
|
195 } |
|
196 |
|
197 // Registers a DataFlavor/FE pair |
|
198 void nsDataObjCollection::AddDataFlavor(const char * aDataFlavor, |
|
199 LPFORMATETC aFE) |
|
200 { |
|
201 // Add the FormatEtc to our list if it's not already there. We don't care |
|
202 // about the internal aDataFlavor because nsDataObj handles that. |
|
203 IEnumFORMATETC * ifEtc; |
|
204 FORMATETC fEtc; |
|
205 ULONG num; |
|
206 if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) |
|
207 return; |
|
208 while (S_OK == ifEtc->Next(1, &fEtc, &num)) { |
|
209 NS_ASSERTION(1 == num, |
|
210 "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor"); |
|
211 if (FormatsMatch(fEtc, *aFE)) { |
|
212 ifEtc->Release(); |
|
213 return; |
|
214 } |
|
215 } // If we didn't find a matching format, add this one |
|
216 ifEtc->Release(); |
|
217 m_enumFE->AddFormatEtc(aFE); |
|
218 } |
|
219 |
|
220 // We accept ownership of the nsDataObj which we free on destruction |
|
221 void nsDataObjCollection::AddDataObject(IDataObject * aDataObj) |
|
222 { |
|
223 nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj); |
|
224 mDataObjects.AppendElement(dataObj); |
|
225 } |
|
226 |
|
227 // Methods for getting data |
|
228 HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) |
|
229 { |
|
230 STGMEDIUM workingmedium; |
|
231 FORMATETC fe = *pFE; |
|
232 HGLOBAL hGlobalMemory; |
|
233 HRESULT hr; |
|
234 // Make enough space for the header and the trailing null |
|
235 uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t); |
|
236 uint32_t alloclen = 0; |
|
237 char16_t* realbuffer; |
|
238 nsAutoString filename; |
|
239 |
|
240 hGlobalMemory = GlobalAlloc(GHND, buffersize); |
|
241 |
|
242 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
243 nsDataObj* dataObj = mDataObjects.ElementAt(i); |
|
244 hr = dataObj->GetData(&fe, &workingmedium); |
|
245 if (hr != S_OK) { |
|
246 switch (hr) { |
|
247 case DV_E_FORMATETC: |
|
248 continue; |
|
249 default: |
|
250 return hr; |
|
251 } |
|
252 } |
|
253 // Now we need to pull out the filename |
|
254 char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); |
|
255 if (buffer == nullptr) |
|
256 return E_FAIL; |
|
257 buffer += sizeof(DROPFILES)/sizeof(char16_t); |
|
258 filename = buffer; |
|
259 GlobalUnlock(workingmedium.hGlobal); |
|
260 ReleaseStgMedium(&workingmedium); |
|
261 // Now put the filename into our buffer |
|
262 alloclen = (filename.Length() + 1) * sizeof(char16_t); |
|
263 hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); |
|
264 if (hGlobalMemory == nullptr) |
|
265 return E_FAIL; |
|
266 realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); |
|
267 if (!realbuffer) |
|
268 return E_FAIL; |
|
269 realbuffer--; // Overwrite the preceding null |
|
270 memcpy(realbuffer, filename.get(), alloclen); |
|
271 GlobalUnlock(hGlobalMemory); |
|
272 buffersize += alloclen; |
|
273 } |
|
274 // We get the last null (on the double null terminator) for free since we used |
|
275 // the zero memory flag when we allocated. All we need to do is fill the |
|
276 // DROPFILES structure |
|
277 DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory); |
|
278 if (!df) |
|
279 return E_FAIL; |
|
280 df->pFiles = sizeof(DROPFILES); //Offset to start of file name string |
|
281 df->fNC = 0; |
|
282 df->pt.x = 0; |
|
283 df->pt.y = 0; |
|
284 df->fWide = TRUE; // utf-16 chars |
|
285 GlobalUnlock(hGlobalMemory); |
|
286 // Finally fill out the STGMEDIUM struct |
|
287 pSTM->tymed = TYMED_HGLOBAL; |
|
288 pSTM->pUnkForRelease = nullptr; // Caller gets to free the data |
|
289 pSTM->hGlobal = hGlobalMemory; |
|
290 return S_OK; |
|
291 } |
|
292 |
|
293 HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) |
|
294 { |
|
295 STGMEDIUM workingmedium; |
|
296 FORMATETC fe = *pFE; |
|
297 HGLOBAL hGlobalMemory; |
|
298 HRESULT hr; |
|
299 uint32_t buffersize = 1; |
|
300 uint32_t alloclen = 0; |
|
301 |
|
302 hGlobalMemory = GlobalAlloc(GHND, buffersize); |
|
303 |
|
304 if (pFE->cfFormat == CF_TEXT) { |
|
305 nsAutoCString text; |
|
306 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
307 nsDataObj* dataObj = mDataObjects.ElementAt(i); |
|
308 hr = dataObj->GetData(&fe, &workingmedium); |
|
309 if (hr != S_OK) { |
|
310 switch (hr) { |
|
311 case DV_E_FORMATETC: |
|
312 continue; |
|
313 default: |
|
314 return hr; |
|
315 } |
|
316 } |
|
317 // Now we need to pull out the text |
|
318 char* buffer = (char*)GlobalLock(workingmedium.hGlobal); |
|
319 if (buffer == nullptr) |
|
320 return E_FAIL; |
|
321 text = buffer; |
|
322 GlobalUnlock(workingmedium.hGlobal); |
|
323 ReleaseStgMedium(&workingmedium); |
|
324 // Now put the text into our buffer |
|
325 alloclen = text.Length(); |
|
326 hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, |
|
327 GHND); |
|
328 if (hGlobalMemory == nullptr) |
|
329 return E_FAIL; |
|
330 buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize); |
|
331 if (!buffer) |
|
332 return E_FAIL; |
|
333 buffer--; // Overwrite the preceding null |
|
334 memcpy(buffer, text.get(), alloclen); |
|
335 GlobalUnlock(hGlobalMemory); |
|
336 buffersize += alloclen; |
|
337 } |
|
338 pSTM->tymed = TYMED_HGLOBAL; |
|
339 pSTM->pUnkForRelease = nullptr; // Caller gets to free the data |
|
340 pSTM->hGlobal = hGlobalMemory; |
|
341 return S_OK; |
|
342 } |
|
343 if (pFE->cfFormat == CF_UNICODETEXT) { |
|
344 buffersize = sizeof(char16_t); |
|
345 nsAutoString text; |
|
346 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
347 nsDataObj* dataObj = mDataObjects.ElementAt(i); |
|
348 hr = dataObj->GetData(&fe, &workingmedium); |
|
349 if (hr != S_OK) { |
|
350 switch (hr) { |
|
351 case DV_E_FORMATETC: |
|
352 continue; |
|
353 default: |
|
354 return hr; |
|
355 } |
|
356 } |
|
357 // Now we need to pull out the text |
|
358 char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); |
|
359 if (buffer == nullptr) |
|
360 return E_FAIL; |
|
361 text = buffer; |
|
362 GlobalUnlock(workingmedium.hGlobal); |
|
363 ReleaseStgMedium(&workingmedium); |
|
364 // Now put the text into our buffer |
|
365 alloclen = text.Length() * sizeof(char16_t); |
|
366 hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, |
|
367 GHND); |
|
368 if (hGlobalMemory == nullptr) |
|
369 return E_FAIL; |
|
370 buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); |
|
371 if (!buffer) |
|
372 return E_FAIL; |
|
373 buffer--; // Overwrite the preceding null |
|
374 memcpy(buffer, text.get(), alloclen); |
|
375 GlobalUnlock(hGlobalMemory); |
|
376 buffersize += alloclen; |
|
377 } |
|
378 pSTM->tymed = TYMED_HGLOBAL; |
|
379 pSTM->pUnkForRelease = nullptr; // Caller gets to free the data |
|
380 pSTM->hGlobal = hGlobalMemory; |
|
381 return S_OK; |
|
382 } |
|
383 |
|
384 return E_FAIL; |
|
385 } |
|
386 |
|
387 HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE, |
|
388 LPSTGMEDIUM pSTM) |
|
389 { |
|
390 STGMEDIUM workingmedium; |
|
391 FORMATETC fe = *pFE; |
|
392 HGLOBAL hGlobalMemory; |
|
393 HRESULT hr; |
|
394 uint32_t buffersize = sizeof(FILEGROUPDESCRIPTOR); |
|
395 uint32_t alloclen = sizeof(FILEDESCRIPTOR); |
|
396 |
|
397 hGlobalMemory = GlobalAlloc(GHND, buffersize); |
|
398 |
|
399 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
400 nsDataObj* dataObj = mDataObjects.ElementAt(i); |
|
401 hr = dataObj->GetData(&fe, &workingmedium); |
|
402 if (hr != S_OK) { |
|
403 switch (hr) { |
|
404 case DV_E_FORMATETC: |
|
405 continue; |
|
406 default: |
|
407 return hr; |
|
408 } |
|
409 } |
|
410 // Now we need to pull out the filedescriptor |
|
411 FILEDESCRIPTOR* buffer = |
|
412 (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) + sizeof(UINT)); |
|
413 if (buffer == nullptr) |
|
414 return E_FAIL; |
|
415 hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); |
|
416 if (hGlobalMemory == nullptr) |
|
417 return E_FAIL; |
|
418 FILEGROUPDESCRIPTOR* realbuffer = |
|
419 (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory); |
|
420 if (!realbuffer) |
|
421 return E_FAIL; |
|
422 FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize); |
|
423 memcpy(copyloc, buffer, sizeof(FILEDESCRIPTOR)); |
|
424 realbuffer->cItems++; |
|
425 GlobalUnlock(hGlobalMemory); |
|
426 GlobalUnlock(workingmedium.hGlobal); |
|
427 ReleaseStgMedium(&workingmedium); |
|
428 buffersize += alloclen; |
|
429 } |
|
430 pSTM->tymed = TYMED_HGLOBAL; |
|
431 pSTM->pUnkForRelease = nullptr; // Caller gets to free the data |
|
432 pSTM->hGlobal = hGlobalMemory; |
|
433 return S_OK; |
|
434 } |
|
435 |
|
436 HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM) |
|
437 { |
|
438 ULONG num = 0; |
|
439 ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex; |
|
440 FORMATETC fEtc = *pFE; |
|
441 fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone |
|
442 |
|
443 // The key for this data type is to figure out which data object the index |
|
444 // corresponds to and then just pass it along |
|
445 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
446 nsDataObj* dataObj = mDataObjects.ElementAt(i); |
|
447 if (dataObj->QueryGetData(&fEtc) != S_OK) |
|
448 continue; |
|
449 if (num == numwanted) |
|
450 return dataObj->GetData(pFE, pSTM); |
|
451 numwanted++; |
|
452 } |
|
453 return DV_E_LINDEX; |
|
454 } |
|
455 |
|
456 HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE, |
|
457 LPSTGMEDIUM pSTM) |
|
458 { |
|
459 // There is no way to pass more than one of this, so just find the first data |
|
460 // object that supports it and pass it along |
|
461 for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { |
|
462 if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK) |
|
463 return mDataObjects.ElementAt(i)->GetData(pFE, pSTM); |
|
464 } |
|
465 return DV_E_FORMATETC; |
|
466 } |