michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "nsDataObjCollection.h" michael@0: #include "nsClipboard.h" michael@0: #include "IEnumFE.h" michael@0: michael@0: #include michael@0: michael@0: // {25589C3E-1FAC-47b9-BF43-CAEA89B79533} michael@0: const IID IID_IDataObjCollection = michael@0: {0x25589c3e, 0x1fac, 0x47b9, {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}}; michael@0: michael@0: /* michael@0: * Class nsDataObjCollection michael@0: */ michael@0: michael@0: nsDataObjCollection::nsDataObjCollection() michael@0: : m_cRef(0), mIsAsyncMode(FALSE), mIsInOperation(FALSE) michael@0: { michael@0: m_enumFE = new CEnumFormatEtc(); michael@0: m_enumFE->AddRef(); michael@0: } michael@0: michael@0: nsDataObjCollection::~nsDataObjCollection() michael@0: { michael@0: mDataFlavors.Clear(); michael@0: mDataObjects.Clear(); michael@0: michael@0: m_enumFE->Release(); michael@0: } michael@0: michael@0: michael@0: // IUnknown interface methods - see iunknown.h for documentation michael@0: STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) michael@0: { michael@0: *ppv=nullptr; michael@0: michael@0: if ( (IID_IUnknown == riid) || (IID_IDataObject == riid) ) { michael@0: *ppv = static_cast(this); michael@0: AddRef(); michael@0: return NOERROR; michael@0: } michael@0: michael@0: if ( IID_IDataObjCollection == riid ) { michael@0: *ppv = static_cast(this); michael@0: AddRef(); michael@0: return NOERROR; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() michael@0: { michael@0: return ++m_cRef; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) nsDataObjCollection::Release() michael@0: { michael@0: if (0 != --m_cRef) michael@0: return m_cRef; michael@0: michael@0: delete this; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: BOOL nsDataObjCollection::FormatsMatch(const FORMATETC& source, michael@0: const FORMATETC& target) const michael@0: { michael@0: if ((source.cfFormat == target.cfFormat) && michael@0: (source.dwAspect & target.dwAspect) && michael@0: (source.tymed & target.tymed)) { michael@0: return TRUE; michael@0: } else { michael@0: return FALSE; michael@0: } michael@0: } michael@0: michael@0: // IDataObject methods michael@0: STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: static CLIPFORMAT fileDescriptorFlavorA = michael@0: ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); michael@0: static CLIPFORMAT fileDescriptorFlavorW = michael@0: ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); michael@0: static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS); michael@0: michael@0: switch (pFE->cfFormat) { michael@0: case CF_TEXT: michael@0: case CF_UNICODETEXT: michael@0: return GetText(pFE, pSTM); michael@0: case CF_HDROP: michael@0: return GetFile(pFE, pSTM); michael@0: default: michael@0: if (pFE->cfFormat == fileDescriptorFlavorA || michael@0: pFE->cfFormat == fileDescriptorFlavorW) { michael@0: return GetFileDescriptors(pFE, pSTM); michael@0: } michael@0: if (pFE->cfFormat == fileFlavor) { michael@0: return GetFileContents(pFE, pSTM); michael@0: } michael@0: } michael@0: return GetFirstSupporting(pFE, pSTM); michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Other objects querying to see if we support a particular format michael@0: STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) michael@0: { michael@0: UINT format = nsClipboard::GetFormat(MULTI_MIME); michael@0: michael@0: if (format == pFE->cfFormat) { michael@0: return S_OK; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: IDataObject * dataObj = mDataObjects.ElementAt(i); michael@0: if (S_OK == dataObj->QueryGetData(pFE)) { michael@0: return S_OK; michael@0: } michael@0: } michael@0: michael@0: return DV_E_FORMATETC; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::GetCanonicalFormatEtc(LPFORMATETC pFEIn, michael@0: LPFORMATETC pFEOut) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, michael@0: LPSTGMEDIUM pSTM, michael@0: BOOL fRelease) michael@0: { michael@0: // Set arbitrary data formats on the first object in the collection and let michael@0: // it handle the heavy lifting michael@0: if (mDataObjects.Length() == 0) michael@0: return E_FAIL; michael@0: return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease); michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::EnumFormatEtc(DWORD dwDir, michael@0: LPENUMFORMATETC *ppEnum) michael@0: { michael@0: if (dwDir == DATADIR_GET) { michael@0: // Clone addref's the new enumerator. michael@0: m_enumFE->Clone(ppEnum); michael@0: if (!(*ppEnum)) michael@0: return E_FAIL; michael@0: (*ppEnum)->Reset(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::DAdvise(LPFORMATETC pFE, michael@0: DWORD dwFlags, michael@0: LPADVISESINK pIAdviseSink, michael@0: DWORD* pdwConn) michael@0: { michael@0: return OLE_E_ADVISENOTSUPPORTED; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::DUnadvise(DWORD dwConn) michael@0: { michael@0: return OLE_E_ADVISENOTSUPPORTED; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObjCollection::EnumDAdvise(LPENUMSTATDATA *ppEnum) michael@0: { michael@0: return OLE_E_ADVISENOTSUPPORTED; michael@0: } michael@0: michael@0: // GetData and SetData helper functions michael@0: HRESULT nsDataObjCollection::AddSetFormat(FORMATETC& aFE) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObjCollection::AddGetFormat(FORMATETC& aFE) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: // Registers a DataFlavor/FE pair michael@0: void nsDataObjCollection::AddDataFlavor(const char * aDataFlavor, michael@0: LPFORMATETC aFE) michael@0: { michael@0: // Add the FormatEtc to our list if it's not already there. We don't care michael@0: // about the internal aDataFlavor because nsDataObj handles that. michael@0: IEnumFORMATETC * ifEtc; michael@0: FORMATETC fEtc; michael@0: ULONG num; michael@0: if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) michael@0: return; michael@0: while (S_OK == ifEtc->Next(1, &fEtc, &num)) { michael@0: NS_ASSERTION(1 == num, michael@0: "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor"); michael@0: if (FormatsMatch(fEtc, *aFE)) { michael@0: ifEtc->Release(); michael@0: return; michael@0: } michael@0: } // If we didn't find a matching format, add this one michael@0: ifEtc->Release(); michael@0: m_enumFE->AddFormatEtc(aFE); michael@0: } michael@0: michael@0: // We accept ownership of the nsDataObj which we free on destruction michael@0: void nsDataObjCollection::AddDataObject(IDataObject * aDataObj) michael@0: { michael@0: nsDataObj* dataObj = reinterpret_cast(aDataObj); michael@0: mDataObjects.AppendElement(dataObj); michael@0: } michael@0: michael@0: // Methods for getting data michael@0: HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: STGMEDIUM workingmedium; michael@0: FORMATETC fe = *pFE; michael@0: HGLOBAL hGlobalMemory; michael@0: HRESULT hr; michael@0: // Make enough space for the header and the trailing null michael@0: uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t); michael@0: uint32_t alloclen = 0; michael@0: char16_t* realbuffer; michael@0: nsAutoString filename; michael@0: michael@0: hGlobalMemory = GlobalAlloc(GHND, buffersize); michael@0: michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: nsDataObj* dataObj = mDataObjects.ElementAt(i); michael@0: hr = dataObj->GetData(&fe, &workingmedium); michael@0: if (hr != S_OK) { michael@0: switch (hr) { michael@0: case DV_E_FORMATETC: michael@0: continue; michael@0: default: michael@0: return hr; michael@0: } michael@0: } michael@0: // Now we need to pull out the filename michael@0: char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); michael@0: if (buffer == nullptr) michael@0: return E_FAIL; michael@0: buffer += sizeof(DROPFILES)/sizeof(char16_t); michael@0: filename = buffer; michael@0: GlobalUnlock(workingmedium.hGlobal); michael@0: ReleaseStgMedium(&workingmedium); michael@0: // Now put the filename into our buffer michael@0: alloclen = (filename.Length() + 1) * sizeof(char16_t); michael@0: hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); michael@0: if (hGlobalMemory == nullptr) michael@0: return E_FAIL; michael@0: realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); michael@0: if (!realbuffer) michael@0: return E_FAIL; michael@0: realbuffer--; // Overwrite the preceding null michael@0: memcpy(realbuffer, filename.get(), alloclen); michael@0: GlobalUnlock(hGlobalMemory); michael@0: buffersize += alloclen; michael@0: } michael@0: // We get the last null (on the double null terminator) for free since we used michael@0: // the zero memory flag when we allocated. All we need to do is fill the michael@0: // DROPFILES structure michael@0: DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory); michael@0: if (!df) michael@0: return E_FAIL; michael@0: df->pFiles = sizeof(DROPFILES); //Offset to start of file name string michael@0: df->fNC = 0; michael@0: df->pt.x = 0; michael@0: df->pt.y = 0; michael@0: df->fWide = TRUE; // utf-16 chars michael@0: GlobalUnlock(hGlobalMemory); michael@0: // Finally fill out the STGMEDIUM struct michael@0: pSTM->tymed = TYMED_HGLOBAL; michael@0: pSTM->pUnkForRelease = nullptr; // Caller gets to free the data michael@0: pSTM->hGlobal = hGlobalMemory; michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: STGMEDIUM workingmedium; michael@0: FORMATETC fe = *pFE; michael@0: HGLOBAL hGlobalMemory; michael@0: HRESULT hr; michael@0: uint32_t buffersize = 1; michael@0: uint32_t alloclen = 0; michael@0: michael@0: hGlobalMemory = GlobalAlloc(GHND, buffersize); michael@0: michael@0: if (pFE->cfFormat == CF_TEXT) { michael@0: nsAutoCString text; michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: nsDataObj* dataObj = mDataObjects.ElementAt(i); michael@0: hr = dataObj->GetData(&fe, &workingmedium); michael@0: if (hr != S_OK) { michael@0: switch (hr) { michael@0: case DV_E_FORMATETC: michael@0: continue; michael@0: default: michael@0: return hr; michael@0: } michael@0: } michael@0: // Now we need to pull out the text michael@0: char* buffer = (char*)GlobalLock(workingmedium.hGlobal); michael@0: if (buffer == nullptr) michael@0: return E_FAIL; michael@0: text = buffer; michael@0: GlobalUnlock(workingmedium.hGlobal); michael@0: ReleaseStgMedium(&workingmedium); michael@0: // Now put the text into our buffer michael@0: alloclen = text.Length(); michael@0: hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, michael@0: GHND); michael@0: if (hGlobalMemory == nullptr) michael@0: return E_FAIL; michael@0: buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize); michael@0: if (!buffer) michael@0: return E_FAIL; michael@0: buffer--; // Overwrite the preceding null michael@0: memcpy(buffer, text.get(), alloclen); michael@0: GlobalUnlock(hGlobalMemory); michael@0: buffersize += alloclen; michael@0: } michael@0: pSTM->tymed = TYMED_HGLOBAL; michael@0: pSTM->pUnkForRelease = nullptr; // Caller gets to free the data michael@0: pSTM->hGlobal = hGlobalMemory; michael@0: return S_OK; michael@0: } michael@0: if (pFE->cfFormat == CF_UNICODETEXT) { michael@0: buffersize = sizeof(char16_t); michael@0: nsAutoString text; michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: nsDataObj* dataObj = mDataObjects.ElementAt(i); michael@0: hr = dataObj->GetData(&fe, &workingmedium); michael@0: if (hr != S_OK) { michael@0: switch (hr) { michael@0: case DV_E_FORMATETC: michael@0: continue; michael@0: default: michael@0: return hr; michael@0: } michael@0: } michael@0: // Now we need to pull out the text michael@0: char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); michael@0: if (buffer == nullptr) michael@0: return E_FAIL; michael@0: text = buffer; michael@0: GlobalUnlock(workingmedium.hGlobal); michael@0: ReleaseStgMedium(&workingmedium); michael@0: // Now put the text into our buffer michael@0: alloclen = text.Length() * sizeof(char16_t); michael@0: hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, michael@0: GHND); michael@0: if (hGlobalMemory == nullptr) michael@0: return E_FAIL; michael@0: buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); michael@0: if (!buffer) michael@0: return E_FAIL; michael@0: buffer--; // Overwrite the preceding null michael@0: memcpy(buffer, text.get(), alloclen); michael@0: GlobalUnlock(hGlobalMemory); michael@0: buffersize += alloclen; michael@0: } michael@0: pSTM->tymed = TYMED_HGLOBAL; michael@0: pSTM->pUnkForRelease = nullptr; // Caller gets to free the data michael@0: pSTM->hGlobal = hGlobalMemory; michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_FAIL; michael@0: } michael@0: michael@0: HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE, michael@0: LPSTGMEDIUM pSTM) michael@0: { michael@0: STGMEDIUM workingmedium; michael@0: FORMATETC fe = *pFE; michael@0: HGLOBAL hGlobalMemory; michael@0: HRESULT hr; michael@0: uint32_t buffersize = sizeof(FILEGROUPDESCRIPTOR); michael@0: uint32_t alloclen = sizeof(FILEDESCRIPTOR); michael@0: michael@0: hGlobalMemory = GlobalAlloc(GHND, buffersize); michael@0: michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: nsDataObj* dataObj = mDataObjects.ElementAt(i); michael@0: hr = dataObj->GetData(&fe, &workingmedium); michael@0: if (hr != S_OK) { michael@0: switch (hr) { michael@0: case DV_E_FORMATETC: michael@0: continue; michael@0: default: michael@0: return hr; michael@0: } michael@0: } michael@0: // Now we need to pull out the filedescriptor michael@0: FILEDESCRIPTOR* buffer = michael@0: (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) + sizeof(UINT)); michael@0: if (buffer == nullptr) michael@0: return E_FAIL; michael@0: hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); michael@0: if (hGlobalMemory == nullptr) michael@0: return E_FAIL; michael@0: FILEGROUPDESCRIPTOR* realbuffer = michael@0: (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory); michael@0: if (!realbuffer) michael@0: return E_FAIL; michael@0: FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize); michael@0: memcpy(copyloc, buffer, sizeof(FILEDESCRIPTOR)); michael@0: realbuffer->cItems++; michael@0: GlobalUnlock(hGlobalMemory); michael@0: GlobalUnlock(workingmedium.hGlobal); michael@0: ReleaseStgMedium(&workingmedium); michael@0: buffersize += alloclen; michael@0: } michael@0: pSTM->tymed = TYMED_HGLOBAL; michael@0: pSTM->pUnkForRelease = nullptr; // Caller gets to free the data michael@0: pSTM->hGlobal = hGlobalMemory; michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: ULONG num = 0; michael@0: ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex; michael@0: FORMATETC fEtc = *pFE; michael@0: fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone michael@0: michael@0: // The key for this data type is to figure out which data object the index michael@0: // corresponds to and then just pass it along michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: nsDataObj* dataObj = mDataObjects.ElementAt(i); michael@0: if (dataObj->QueryGetData(&fEtc) != S_OK) michael@0: continue; michael@0: if (num == numwanted) michael@0: return dataObj->GetData(pFE, pSTM); michael@0: numwanted++; michael@0: } michael@0: return DV_E_LINDEX; michael@0: } michael@0: michael@0: HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE, michael@0: LPSTGMEDIUM pSTM) michael@0: { michael@0: // There is no way to pass more than one of this, so just find the first data michael@0: // object that supports it and pass it along michael@0: for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { michael@0: if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK) michael@0: return mDataObjects.ElementAt(i)->GetData(pFE, pSTM); michael@0: } michael@0: return DV_E_FORMATETC; michael@0: }