Revision control

Copy as Markdown

// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qwindowsmimeregistry.h"
#include "qwindowscontext.h"
#include <QtGui/private/qinternalmimedata_p.h>
#include <QtCore/qbytearraymatcher.h>
#include <QtCore/qmap.h>
#include <QtCore/qurl.h>
#include <QtCore/qdir.h>
#include <QtCore/qdebug.h>
#include <QtCore/qbuffer.h>
#include <QtGui/qimagereader.h>
#include <QtGui/qimagewriter.h>
#include <shlobj.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
/* The MSVC compilers allows multi-byte characters, that has the behavior of
* that each character gets shifted into position. 0x73524742 below is for MSVC
* equivalent to doing 'sRGB', but this does of course not work
* on conformant C++ compilers. */
#define BMP_LCS_sRGB 0x73524742
#define BMP_LCS_GM_IMAGES 0x00000004L
struct _CIEXYZ {
long ciexyzX, ciexyzY, ciexyzZ;
};
struct _CIEXYZTRIPLE {
_CIEXYZ ciexyzRed, ciexyzGreen, ciexyzBlue;
};
struct BMP_BITMAPV5HEADER {
DWORD bV5Size;
LONG bV5Width;
LONG bV5Height;
WORD bV5Planes;
WORD bV5BitCount;
DWORD bV5Compression;
DWORD bV5SizeImage;
LONG bV5XPelsPerMeter;
LONG bV5YPelsPerMeter;
DWORD bV5ClrUsed;
DWORD bV5ClrImportant;
DWORD bV5RedMask;
DWORD bV5GreenMask;
DWORD bV5BlueMask;
DWORD bV5AlphaMask;
DWORD bV5CSType;
_CIEXYZTRIPLE bV5Endpoints;
DWORD bV5GammaRed;
DWORD bV5GammaGreen;
DWORD bV5GammaBlue;
DWORD bV5Intent;
DWORD bV5ProfileData;
DWORD bV5ProfileSize;
DWORD bV5Reserved;
};
static const char dibFormatC[] = "dib";
static inline QByteArray msgConversionError(const char *func, const char *format)
{
QByteArray msg = func;
msg += ": Unable to convert DIB image. The image converter plugin for '";
msg += format;
msg += "' is not available. Available formats: ";
const auto &formats = QImageReader::supportedImageFormats();
for (const QByteArray &af : formats) {
msg += af;
msg += ' ';
}
return msg;
}
static inline bool readDib(QBuffer &buffer, QImage &img)
{
QImageReader reader(&buffer, dibFormatC);
if (!reader.canRead()) {
qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
return false;
}
img = reader.read();
return true;
}
#if QT_CONFIG(imageformat_bmp)
static QByteArray writeDib(const QImage &img)
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::ReadWrite);
QImageWriter writer(&buffer, dibFormatC);
if (!writer.canWrite()) {
qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
return ba;
}
if (!writer.write(img))
ba.clear();
return ba;
}
static bool qt_write_dibv5(QDataStream &s, QImage image)
{
QIODevice* d = s.device();
if (!d->isWritable())
return false;
//depth will be always 32
qsizetype bpl_bmp = qsizetype(image.width()) * 4;
qsizetype size = bpl_bmp * image.height();
if (qsizetype(DWORD(size)) != size)
return false;
BMP_BITMAPV5HEADER bi;
ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(BMP_BITMAPV5HEADER);
bi.bV5Width = image.width();
bi.bV5Height = image.height();
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5SizeImage = DWORD(bpl_bmp * image.height());
bi.bV5XPelsPerMeter = 0;
bi.bV5YPelsPerMeter = 0;
bi.bV5ClrUsed = 0;
bi.bV5ClrImportant = 0;
bi.bV5BlueMask = 0x000000ff;
bi.bV5GreenMask = 0x0000ff00;
bi.bV5RedMask = 0x00ff0000;
bi.bV5AlphaMask = 0xff000000;
bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB
bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES
d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5RedMask), sizeof(bi.bV5RedMask));
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5GreenMask), sizeof(bi.bV5GreenMask));
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5BlueMask), sizeof(bi.bV5BlueMask));
if (s.status() != QDataStream::Ok)
return false;
if (image.format() != QImage::Format_ARGB32)
image = std::move(image).convertToFormat(QImage::Format_ARGB32);
auto *buf = new uchar[bpl_bmp];
memset(buf, 0, size_t(bpl_bmp));
for (int y=image.height()-1; y>=0; y--) {
// write the image bits
const QRgb *p = reinterpret_cast<const QRgb *>(image.constScanLine(y));
const QRgb *end = p + image.width();
uchar *b = buf;
while (p < end) {
int alpha = qAlpha(*p);
if (alpha) {
*b++ = uchar(qBlue(*p));
*b++ = uchar(qGreen(*p));
*b++ = uchar(qRed(*p));
} else {
//white for fully transparent pixels.
*b++ = 0xff;
*b++ = 0xff;
*b++ = 0xff;
}
*b++ = uchar(alpha);
p++;
}
d->write(reinterpret_cast<const char *>(buf), bpl_bmp);
if (s.status() != QDataStream::Ok) {
delete[] buf;
return false;
}
}
delete[] buf;
return true;
}
#endif //QT_CONFIG(imageformat_bmp)
// helpers for using global memory
static int getCf(const FORMATETC &formatetc)
{
return formatetc.cfFormat;
}
static FORMATETC setCf(int cf)
{
FORMATETC formatetc;
formatetc.cfFormat = CLIPFORMAT(cf);
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.ptd = nullptr;
formatetc.tymed = TYMED_HGLOBAL;
return formatetc;
}
static bool setData(const QByteArray &data, STGMEDIUM *pmedium, DWORD tymed = TYMED_HGLOBAL)
{
HGLOBAL hData = GlobalAlloc(0, SIZE_T(data.size()));
if (!hData)
return false;
void *out = GlobalLock(hData);
memcpy(out, data.data(), size_t(data.size()));
GlobalUnlock(hData);
// MS Office (and other consumers that prefer streamed delivery for image
// payloads) request CF_PNG via TYMED_ISTREAM rather than TYMED_HGLOBAL,
// and reject the result if the returned tymed doesn't match. Wrap the
// buffer in an IStream only when TYMED_HGLOBAL was not requested, so that
// legacy consumers asking for HGLOBAL (or HGLOBAL|ISTREAM) keep getting
// it. (QTBUG-126191)
if (!(tymed & TYMED_HGLOBAL) && (tymed & TYMED_ISTREAM)) {
IStream *stream = nullptr;
if (FAILED(::CreateStreamOnHGlobal(hData, /*fDeleteOnRelease*/ TRUE, &stream))) {
GlobalFree(hData);
return false;
}
pmedium->tymed = TYMED_ISTREAM;
pmedium->pstm = stream;
pmedium->pUnkForRelease = nullptr;
return true;
}
pmedium->tymed = TYMED_HGLOBAL;
pmedium->hGlobal = hData;
pmedium->pUnkForRelease = nullptr;
return true;
}
static QByteArray getData(int cf, IDataObject *pDataObj, int lindex = -1)
{
QByteArray data;
FORMATETC formatetc = setCf(cf);
formatetc.lindex = lindex;
STGMEDIUM s;
if (pDataObj->GetData(&formatetc, &s) == S_OK) {
// Explorer's zipped-folder shell extension returns S_OK with a null
// or zero-sized hGlobal for CFSTR_FILECONTENTS (QTBUG-126980); fall
// through to the IStream path in that case.
if (s.hGlobal && GlobalSize(s.hGlobal) > 0) {
if (const void *val = GlobalLock(s.hGlobal)) {
data = QByteArray::fromRawData(reinterpret_cast<const char *>(val),
qsizetype(GlobalSize(s.hGlobal)));
data.detach();
GlobalUnlock(s.hGlobal);
}
}
ReleaseStgMedium(&s);
}
if (data.isEmpty()) {
//Try reading IStream data
formatetc.tymed = TYMED_ISTREAM;
if (pDataObj->GetData(&formatetc, &s) == S_OK) {
char szBuffer[4096];
ULONG actualRead = 0;
LARGE_INTEGER pos = {{0, 0}};
//Move to front (can fail depending on the data model implemented)
HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
while(SUCCEEDED(hr)){
hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
if (SUCCEEDED(hr) && actualRead > 0) {
data += QByteArray::fromRawData(szBuffer, int(actualRead));
}
if (actualRead != sizeof(szBuffer))
break;
}
data.detach();
ReleaseStgMedium(&s);
}
}
return data;
}
static bool canGetData(int cf, IDataObject * pDataObj)
{
FORMATETC formatetc = setCf(cf);
if (pDataObj->QueryGetData(&formatetc) != S_OK){
formatetc.tymed = TYMED_ISTREAM;
return pDataObj->QueryGetData(&formatetc) == S_OK;
}
return true;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, const FORMATETC &tc)
{
QDebugStateSaver saver(d);
d.nospace();
d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
switch (tc.cfFormat) {
case CF_TEXT:
d << "CF_TEXT";
break;
case CF_BITMAP:
d << "CF_BITMAP";
break;
case CF_TIFF:
d << "CF_TIFF";
break;
case CF_OEMTEXT:
d << "CF_OEMTEXT";
break;
case CF_DIB:
d << "CF_DIB";
break;
case CF_DIBV5:
d << "CF_DIBV5";
break;
case CF_UNICODETEXT:
d << "CF_UNICODETEXT";
break;
case CF_ENHMETAFILE:
d << "CF_ENHMETAFILE";
break;
default:
d << QWindowsMimeRegistry::clipboardFormatName(tc.cfFormat);
break;
}
d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
<< ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
return d;
}
QDebug operator<<(QDebug d, IDataObject *dataObj)
{
QDebugStateSaver saver(d);
d.nospace();
d.noquote();
d << "IDataObject(";
if (dataObj) { // Output formats contained in IDataObject.
IEnumFORMATETC *enumFormatEtc;
if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
FORMATETC formatEtc[1];
ULONG fetched;
if (SUCCEEDED(enumFormatEtc->Reset())) {
while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
d << formatEtc[0] << ',';
enumFormatEtc->Release();
}
}
} else {
d << "0x0";
}
d << ')';
return d;
}
#endif // !QT_NO_DEBUG_STREAM
class QWindowsMimeText : public QWindowsMimeConverter
{
public:
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
};
bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
}
/*
text/plain is defined as using CRLF, but so many programs don't,
and programmers just look for '\n' in strings.
Windows really needs CRLF, so we ensure it here.
*/
bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data;
int cf = getCf(formatetc);
if (cf == CF_TEXT) {
data = mimeData->text().toLocal8Bit();
// Anticipate required space for CRLFs at 1/40
int maxsize=data.size()+data.size()/40+3;
QByteArray r(maxsize, '\0');
char* o = r.data();
const char* d = data.data();
const int s = data.size();
bool cr=false;
int j=0;
for (int i=0; i<s; i++) {
char c = d[i];
if (c=='\r')
cr=true;
else {
if (c=='\n') {
if (!cr)
o[j++]='\r';
}
cr=false;
}
o[j++]=c;
if (j+3 >= maxsize) {
maxsize += maxsize/4;
r.resize(maxsize);
o = r.data();
}
}
o[j]=0;
return setData(r, pmedium);
}
if (cf == CF_UNICODETEXT) {
QString str = mimeData->text();
const QChar *u = str.unicode();
QString res;
const int s = str.length();
int maxsize = s + s/40 + 3;
res.resize(maxsize);
int ri = 0;
bool cr = false;
for (int i=0; i < s; ++i) {
if (*u == u'\r')
cr = true;
else {
if (*u == u'\n' && !cr)
res[ri++] = u'\r';
cr = false;
}
res[ri++] = *u;
if (ri+3 >= maxsize) {
maxsize += maxsize/4;
res.resize(maxsize);
}
++u;
}
res.truncate(ri);
const int byteLength = res.length() * int(sizeof(ushort));
QByteArray r(byteLength + 2, '\0');
memcpy(r.data(), res.unicode(), size_t(byteLength));
r[byteLength] = 0;
r[byteLength+1] = 0;
return setData(r, pmedium);
}
}
return false;
}
bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType.startsWith(u"text/plain")
&& (canGetData(CF_UNICODETEXT, pDataObj)
|| canGetData(CF_TEXT, pDataObj));
}
QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_UNICODETEXT || cf == CF_TEXT)
return u"text/plain"_s;
return QString();
}
QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QList<FORMATETC> formatics;
if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
formatics += setCf(CF_UNICODETEXT);
if (GetACP() != CP_UTF8)
formatics += setCf(CF_TEXT);
}
return formatics;
}
QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const
{
QVariant ret;
if (canConvertToMime(mime, pDataObj)) {
QString str;
QByteArray data = getData(CF_UNICODETEXT, pDataObj);
if (!data.isEmpty()) {
str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
str.replace("\r\n"_L1, "\n"_L1);
} else {
data = getData(CF_TEXT, pDataObj);
if (!data.isEmpty()) {
const char* d = data.data();
const unsigned s = unsigned(qstrlen(d));
QByteArray r(data.size()+1, '\0');
char* o = r.data();
int j=0;
for (unsigned i = 0; i < s; ++i) {
char c = d[i];
if (c!='\r')
o[j++]=c;
}
o[j]=0;
str = QString::fromLocal8Bit(r);
}
}
if (preferredType.id() == QMetaType::QString)
ret = str;
else
ret = std::move(str).toUtf8();
}
qCDebug(lcQpaMime) << __FUNCTION__ << ret;
return ret;
}
class QWindowsMimeURI : public QWindowsMimeConverter
{
public:
QWindowsMimeURI();
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
private:
int CF_INETURL_W; // wide char version
int CF_INETURL;
};
QWindowsMimeURI::QWindowsMimeURI()
{
CF_INETURL_W = registerMimeType(u"UniformResourceLocatorW"_s);
CF_INETURL = registerMimeType(u"UniformResourceLocator"_s);
}
bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
const auto urls = mimeData->urls();
for (const QUrl &url : urls) {
if (url.isLocalFile())
return true;
}
}
return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
}
bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
if (getCf(formatetc) == CF_HDROP) {
const auto &urls = mimeData->urls();
QStringList fileNames;
size_t size = sizeof(DROPFILES) + 2;
for (const QUrl &url : urls) {
const QString fn = QDir::toNativeSeparators(url.toLocalFile());
if (!fn.isEmpty()) {
size += sizeof(ushort) * size_t(fn.length() + 1);
fileNames.append(fn);
}
}
QByteArray result(int(size), '\0');
auto* d = reinterpret_cast<DROPFILES *>(result.data());
d->pFiles = sizeof(DROPFILES);
GetCursorPos(&d->pt); // try
d->fNC = true;
char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
d->fWide = true;
auto *f = reinterpret_cast<wchar_t *>(files);
for (int i=0; i<fileNames.size(); i++) {
const auto l = size_t(fileNames.at(i).length());
memcpy(f, fileNames.at(i).data(), l * sizeof(ushort));
f += l;
*f++ = 0;
}
*f = 0;
return setData(result, pmedium);
}
if (getCf(formatetc) == CF_INETURL_W) {
const auto urls = mimeData->urls();
QByteArray result;
if (!urls.isEmpty()) {
const QString url = urls.at(0).toString();
result = QByteArray(reinterpret_cast<const char *>(url.data()),
url.length() * int(sizeof(ushort)));
}
result.append('\0');
result.append('\0');
return setData(result, pmedium);
}
if (getCf(formatetc) == CF_INETURL) {
const auto urls = mimeData->urls();
QByteArray result;
if (!urls.isEmpty())
result = urls.at(0).toString().toLocal8Bit();
return setData(result, pmedium);
}
}
return false;
}
bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType == u"text/uri-list"
&& (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
}
QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
{
QString format;
if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
format = u"text/uri-list"_s;
return format;
}
QList<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QList<FORMATETC> formatics;
if (mimeType == u"text/uri-list") {
if (canConvertFromMime(setCf(CF_HDROP), mimeData))
formatics += setCf(CF_HDROP);
if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
formatics += setCf(CF_INETURL_W);
if (canConvertFromMime(setCf(CF_INETURL), mimeData))
formatics += setCf(CF_INETURL);
}
return formatics;
}
QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QMetaType preferredType) const
{
if (mimeType == u"text/uri-list") {
if (canGetData(CF_HDROP, pDataObj)) {
QList<QVariant> urls;
QByteArray data = getData(CF_HDROP, pDataObj);
if (data.size() < qsizetype(sizeof(DROPFILES)))
return QVariant();
// Don't use constData. It falls back to the one-byte QByteArray::_empty for a null
// array, which makes MinGW report the DROPFILES access as out of bounds.
const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.cbegin());
if (hdrop->fWide) {
const auto *filesw = reinterpret_cast<const wchar_t *>(data.cbegin() + hdrop->pFiles);
int i = 0;
while (filesw[i]) {
QString fileurl = QString::fromWCharArray(filesw + i);
urls += QUrl::fromLocalFile(fileurl);
i += fileurl.length()+1;
}
} else {
const char* files = reinterpret_cast<const char *>(data.cbegin() + hdrop->pFiles);
int i=0;
while (files[i]) {
urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
i += int(strlen(files+i))+1;
}
}
if (preferredType.id() == QMetaType::QUrl && urls.size() == 1)
return urls.at(0);
if (!urls.isEmpty())
return urls;
} else if (canGetData(CF_INETURL_W, pDataObj)) {
QByteArray data = getData(CF_INETURL_W, pDataObj);
if (data.isEmpty())
return QVariant();
return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
} else if (canGetData(CF_INETURL, pDataObj)) {
QByteArray data = getData(CF_INETURL, pDataObj);
if (data.isEmpty())
return QVariant();
return QUrl(QString::fromLocal8Bit(data.constData()));
}
}
return QVariant();
}
class QWindowsMimeHtml : public QWindowsMimeConverter
{
public:
QWindowsMimeHtml();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
private:
int CF_HTML;
};
QWindowsMimeHtml::QWindowsMimeHtml()
{
CF_HTML = registerMimeType(u"HTML Format"_s);
}
QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QList<FORMATETC> formatetcs;
if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
formatetcs += setCf(CF_HTML);
return formatetcs;
}
QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
{
if (getCf(formatetc) == CF_HTML)
return u"text/html"_s;
return QString();
}
bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
}
bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
}
/*
The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
Version: 1.0
StartHTML:xxxxxxxxxx
EndHTML:xxxxxxxxxx
StartFragment:xxxxxxxxxx
EndFragment:xxxxxxxxxx
...html...
*/
QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const
{
Q_UNUSED(preferredType);
QVariant result;
if (canConvertToMime(mime, pDataObj)) {
QByteArray html = getData(CF_HTML, pDataObj);
static constexpr auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
static constexpr auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:");
qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
int start = startMatcher.indexIn(html);
int end = endMatcher.indexIn(html);
if (start != -1) {
int startOffset = start + 10;
int i = startOffset;
while (html.at(i) != '\r' && html.at(i) != '\n')
++i;
QByteArray bytecount = html.mid(startOffset, i - startOffset);
start = bytecount.toInt();
}
if (end != -1) {
int endOffset = end + 8;
int i = endOffset ;
while (html.at(i) != '\r' && html.at(i) != '\n')
++i;
QByteArray bytecount = html.mid(endOffset , i - endOffset);
end = bytecount.toInt();
}
if (end > start && start > 0) {
html = html.mid(start, end - start);
html.replace('\r', "");
result = QString::fromUtf8(html);
}
}
return result;
}
bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data = mimeData->html().toUtf8();
QByteArray result =
"Version:1.0\r\n" // 0-12
"StartHTML:0000000107\r\n" // 13-35
"EndHTML:0000000000\r\n" // 36-55
"StartFragment:0000000000\r\n" // 56-81
"EndFragment:0000000000\r\n\r\n"; // 82-107
static constexpr auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
static constexpr auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
if (startFragmentMatcher.indexIn(data) == -1)
result += "<!--StartFragment-->";
result += data;
if (endFragmentMatcher.indexIn(data) == -1)
result += "<!--EndFragment-->";
// set the correct number for EndHTML
QByteArray pos = QByteArray::number(result.size());
memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
// set correct numbers for StartFragment and EndFragment
pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
pos = QByteArray::number(endFragmentMatcher.indexIn(result));
memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
return setData(result, pmedium);
}
return false;
}
#if QT_CONFIG(imageformat_bmp)
class QWindowsMimeImage : public QWindowsMimeConverter
{
public:
QWindowsMimeImage();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
private:
bool hasOriginalDIBV5(IDataObject *pDataObj) const;
UINT CF_PNG;
};
QWindowsMimeImage::QWindowsMimeImage()
{
CF_PNG = RegisterClipboardFormat(L"PNG");
}
QList<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QList<FORMATETC> formatetcs;
if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
auto image = qvariant_cast<QImage>(mimeData->imageData());
if (!image.isNull() && image.hasAlphaChannel()) {
// CF_PNG is the only format modern MS Office (and other apps that
// deliver image data via TYMED_ISTREAM) reads with alpha
// preserved (QTBUG-126191).
formatetcs += setCf(CF_PNG);
formatetcs += setCf(CF_DIBV5);
}
formatetcs += setCf(CF_DIB);
}
if (!formatetcs.isEmpty())
qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
return formatetcs;
}
QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
return u"application/x-qt-image"_s;
return QString();
}
bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
return mimeType == u"application/x-qt-image"
&& (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
}
bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
if (!mimeData->hasImage())
return false;
const QImage image = qvariant_cast<QImage>(mimeData->imageData());
if (image.isNull())
return false;
// QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
// cannot handle it.
return cf == CF_DIBV5 || cf == CF_DIB
|| (cf == int(CF_PNG) && image.hasAlphaChannel());
}
bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
int cf = getCf(formatetc);
if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
auto img = qvariant_cast<QImage>(mimeData->imageData());
if (img.isNull())
return false;
QByteArray ba;
if (cf == CF_DIB) {
if (img.format() > QImage::Format_ARGB32)
img = std::move(img).convertToFormat(QImage::Format_RGB32);
const QByteArray ba = writeDib(img);
if (!ba.isEmpty())
return setData(ba, pmedium);
} else if (cf == int(CF_PNG)) {
QBuffer buffer(&ba);
const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
buffer.close();
if (written)
return setData(ba, pmedium, formatetc.tymed);
} else {
QDataStream s(&ba, QIODevice::WriteOnly);
s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
if (qt_write_dibv5(s, std::move(img)))
return setData(ba, pmedium);
}
}
return false;
}
bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
{
bool isSynthesized = true;
IEnumFORMATETC *pEnum = nullptr;
HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
if (res == S_OK && pEnum) {
FORMATETC fc;
while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
if (fc.ptd)
CoTaskMemFree(fc.ptd);
if (fc.cfFormat == CF_DIB)
break;
if (fc.cfFormat == CF_DIBV5) {
isSynthesized = false;
break;
}
}
pEnum->Release();
}
return !isSynthesized;
}
QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
{
Q_UNUSED(preferredType);
QVariant result;
if (mimeType != u"application/x-qt-image")
return result;
// Try to convert from DIBV5 as it is the most widespread format that supports transparency,
// but avoid synthesizing it, as that typically loses transparency, e.g. from Office
const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
// PNG, MS Office place this (undocumented)
if (!hasOrigDibV5 && canGetData(CF_PNG, pDataObj)) {
qCDebug(lcQpaMime) << "Decoding PNG";
QImage img;
QByteArray data = getData(CF_PNG, pDataObj);
if (img.loadFromData(data, "PNG")) {
return img;
}
}
if (canGetDibV5 || canGetData(CF_DIB, pDataObj)) {
qCDebug(lcQpaMime) << "Decoding DIB";
QImage img;
QByteArray data = getData(canGetDibV5 ? CF_DIBV5 : CF_DIB, pDataObj);
QBuffer buffer(&data);
if (readDib(buffer, img))
return img;
}
// Failed
return result;
}
#endif //QT_CONFIG(imageformat_bmp)
class QBuiltInMimes : public QWindowsMimeConverter
{
public:
QBuiltInMimes();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
private:
QMap<int, QString> outFormats;
QMap<int, QString> inFormats;
};
QBuiltInMimes::QBuiltInMimes()
: QWindowsMimeConverter()
{
outFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
inFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
}
bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
// really check
return formatetc.tymed & TYMED_HGLOBAL
&& outFormats.contains(formatetc.cfFormat)
&& mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
}
bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
if (canConvertFromMime(formatetc, mimeData)) {
QByteArray data;
if (outFormats.value(getCf(formatetc)) == u"text/html") {
// text/html is in wide chars on windows (compatible with mozillia)
QString html = mimeData->html();
// same code as in the text converter up above
const QChar *u = html.unicode();
QString res;
const int s = html.length();
int maxsize = s + s/40 + 3;
res.resize(maxsize);
int ri = 0;
bool cr = false;
for (int i=0; i < s; ++i) {
if (*u == u'\r')
cr = true;
else {
if (*u == u'\n' && !cr)
res[ri++] = u'\r';
cr = false;
}
res[ri++] = *u;
if (ri+3 >= maxsize) {
maxsize += maxsize/4;
res.resize(maxsize);
}
++u;
}
res.truncate(ri);
const int byteLength = res.length() * int(sizeof(ushort));
QByteArray r(byteLength + 2, '\0');
memcpy(r.data(), res.unicode(), size_t(byteLength));
r[byteLength] = 0;
r[byteLength+1] = 0;
data = r;
} else {
#if QT_CONFIG(draganddrop)
data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
#endif // QT_CONFIG(draganddrop)
}
return setData(data, pmedium);
}
return false;
}
QList<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
{
QList<FORMATETC> formatetcs;
const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
formatetcs += setCf(mit.key());
return formatetcs;
}
bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
}
QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
{
QVariant val;
if (canConvertToMime(mimeType, pDataObj)) {
QByteArray data = getData(inFormats.key(mimeType), pDataObj);
if (!data.isEmpty()) {
qCDebug(lcQpaMime) << __FUNCTION__;
if (mimeType == u"text/html" && preferredType == QMetaType(QMetaType::QString)) {
// text/html is in wide chars on windows (compatible with Mozilla)
val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
} else {
val = data; // it should be enough to return the data and let QMimeData do the rest.
}
}
}
return val;
}
QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
{
return inFormats.value(getCf(formatetc));
}
class QLastResortMimes : public QWindowsMimeConverter
{
public:
QLastResortMimes();
// for converting from Qt
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
// for converting to Qt
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
private:
mutable QMap<int, QString> formats;
static QStringList ianaTypes;
static QStringList excludeList;
};
QStringList QLastResortMimes::ianaTypes;
QStringList QLastResortMimes::excludeList;
QLastResortMimes::QLastResortMimes()
{
//MIME Media-Types
if (ianaTypes.isEmpty()) {
ianaTypes.append(u"application/"_s);
ianaTypes.append(u"audio/"_s);
ianaTypes.append(u"example/"_s);
ianaTypes.append(u"image/"_s);
ianaTypes.append(u"message/"_s);
ianaTypes.append(u"model/"_s);
ianaTypes.append(u"multipart/"_s);
ianaTypes.append(u"text/"_s);
ianaTypes.append(u"video/"_s);
}
//Types handled by other classes
if (excludeList.isEmpty()) {
excludeList.append(u"HTML Format"_s);
excludeList.append(u"UniformResourceLocator"_s);
excludeList.append(u"text/html"_s);
excludeList.append(u"text/plain"_s);
excludeList.append(u"text/uri-list"_s);
excludeList.append(u"application/x-qt-image"_s);
excludeList.append(u"application/x-color"_s);
}
}
bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
// really check
#if QT_CONFIG(draganddrop)
return formatetc.tymed & TYMED_HGLOBAL
&& (formats.contains(formatetc.cfFormat)
&& QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
#else
Q_UNUSED(mimeData);
Q_UNUSED(formatetc);
return formatetc.tymed & TYMED_HGLOBAL
&& formats.contains(formatetc.cfFormat);
#endif // QT_CONFIG(draganddrop)
}
bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
{
#if QT_CONFIG(draganddrop)
return canConvertFromMime(formatetc, mimeData)
&& setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
#else
Q_UNUSED(mimeData);
Q_UNUSED(formatetc);
Q_UNUSED(pmedium);
return false;
#endif // QT_CONFIG(draganddrop)
}
QList<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
{
QList<FORMATETC> formatetcs;
auto mit = std::find(formats.begin(), formats.end(), mimeType);
// register any other available formats
if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
mit = formats.insert(registerMimeType(mimeType), mimeType);
if (mit != formats.end())
formatetcs += setCf(mit.key());
if (!formatetcs.isEmpty())
qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
return formatetcs;
}
static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
static bool isCustomMimeType(const QString &mimeType)
{
return mimeType.startsWith(QLatin1StringView(x_qt_windows_mime), Qt::CaseInsensitive);
}
static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
{
int len = sizeof(x_qt_windows_mime) - 1;
int n = mimeType.lastIndexOf(u'\"') - len;
QString ret = mimeType.mid(len, n);
const int beginPos = mimeType.indexOf(u";index=");
if (beginPos > -1) {
const int endPos = mimeType.indexOf(u';', beginPos + 1);
const int indexStartPos = beginPos + 7;
if (lindex)
*lindex = QStringView{mimeType}.mid(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
} else {
if (lindex)
*lindex = -1;
}
return ret;
}
bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
{
if (isCustomMimeType(mimeType)) {
// MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
QString clipFormat = customMimeType(mimeType);
const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
return canGetData(int(cf), pDataObj);
}
// if it is not in there then register it and see if we can get it
const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
return canGetData(cf, pDataObj);
}
QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
{
Q_UNUSED(preferredType);
QVariant val;
if (canConvertToMime(mimeType, pDataObj)) {
QByteArray data;
if (isCustomMimeType(mimeType)) {
int lindex;
QString clipFormat = customMimeType(mimeType, &lindex);
const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
data = getData(int(cf), pDataObj, lindex);
} else {
const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
data = getData(cf, pDataObj);
}
if (!data.isEmpty())
val = data; // it should be enough to return the data and let QMimeData do the rest.
}
return val;
}
QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
{
QString format = formats.value(getCf(formatetc));
if (!format.isEmpty())
return format;
const QString clipFormat = QWindowsMimeRegistry::clipboardFormatName(getCf(formatetc));
if (!clipFormat.isEmpty()) {
#if QT_CONFIG(draganddrop)
if (QInternalMimeData::canReadData(clipFormat))
format = clipFormat;
else if ((formatetc.cfFormat >= 0xC000)){
//create the mime as custom. not registered.
if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
//check if this is a mime type
bool ianaType = false;
int sz = ianaTypes.size();
for (int i = 0; i < sz; i++) {
if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
ianaType = true;
break;
}
}
if (!ianaType)
format = QLatin1StringView(x_qt_windows_mime) + clipFormat + u'"';
else
format = clipFormat;
}
}
#endif // QT_CONFIG(draganddrop)
}
return format;
}
/*!
\class QWindowsMimeRegistry
\brief Manages the list of QWindowsMimeConverter instances.
\internal
\sa QWindowsMimeConverter
*/
QWindowsMimeRegistry::QWindowsMimeRegistry() = default;
QWindowsMimeRegistry::~QWindowsMimeRegistry()
{
qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
}
QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
{
ensureInitialized();
for (int i = m_mimes.size()-1; i >= 0; --i) {
if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
return m_mimes.at(i);
}
return nullptr;
}
QStringList QWindowsMimeRegistry::allMimesForFormats(IDataObject *pDataObj) const
{
qCDebug(lcQpaMime) << "QWindowsMimeConverter::allMimesForFormats()";
ensureInitialized();
QStringList formats;
LPENUMFORMATETC FAR fmtenum;
HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
if (hr == NOERROR) {
FORMATETC fmtetc;
while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
for (int i= m_mimes.size() - 1; i >= 0; --i) {
QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
if (!format.isEmpty() && !formats.contains(format)) {
formats += format;
if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
}
}
// as documented in MSDN to avoid possible memleak
if (fmtetc.ptd)
CoTaskMemFree(fmtetc.ptd);
}
fmtenum->Release();
}
qCDebug(lcQpaMime) << pDataObj << formats;
return formats;
}
QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
ensureInitialized();
qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
for (int i = m_mimes.size()-1; i >= 0; --i) {
if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
return m_mimes.at(i);
}
return nullptr;
}
QList<FORMATETC> QWindowsMimeRegistry::allFormatsForMime(const QMimeData *mimeData) const
{
ensureInitialized();
QList<FORMATETC> formatics;
#if !QT_CONFIG(draganddrop)
Q_UNUSED(mimeData);
#else
formatics.reserve(20);
const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
for (int f = 0; f < formats.size(); ++f) {
for (int i = m_mimes.size() - 1; i >= 0; --i)
formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
}
#endif // QT_CONFIG(draganddrop)
return formatics;
}
void QWindowsMimeRegistry::ensureInitialized() const
{
if (m_internalMimeCount == 0) {
m_internalMimeCount = -1; // prevent reentrancy when types register themselves
#if QT_CONFIG(imageformat_bmp)
(void)new QWindowsMimeImage;
#endif //QT_CONFIG(imageformat_bmp)
(void)new QLastResortMimes;
(void)new QWindowsMimeText;
(void)new QWindowsMimeURI;
(void)new QWindowsMimeHtml;
(void)new QBuiltInMimes;
m_internalMimeCount = m_mimes.size();
Q_ASSERT(m_internalMimeCount > 0);
}
}
QString QWindowsMimeRegistry::clipboardFormatName(int cf)
{
wchar_t buf[256] = {0};
return GetClipboardFormatName(UINT(cf), buf, 255)
? QString::fromWCharArray(buf) : QString();
}
QVariant QWindowsMimeRegistry::convertToMime(const QStringList &mimeTypes,
IDataObject *pDataObj,
QMetaType preferredType,
QString *formatIn /* = 0 */) const
{
for (const QString &format : mimeTypes) {
if (const QWindowsMimeConverter *converter = converterToMime(format, pDataObj)) {
if (converter->canConvertToMime(format, pDataObj)) {
const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
if (dataV.isValid()) {
qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
<< format << pDataObj << " returns " << dataV;
if (formatIn)
*formatIn = format;
return dataV;
}
}
}
}
qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType.id();
return QVariant();
}
void QWindowsMimeRegistry::registerMime(QWindowsMimeConverter *mime)
{
ensureInitialized();
m_mimes.append(mime);
}
/*!
Registers the MIME type \a mime, and returns an ID number
identifying the format on Windows.
A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
registered as the clipboard format for \c WindowsType.
*/
int QWindowsMimeRegistry::registerMimeType(const QString &mime)
{
const QString mimeType = isCustomMimeType(mime) ? customMimeType(mime) : mime;
const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mimeType.utf16()));
if (!f) {
qErrnoWarning("QWindowsMimeRegistry::registerMimeType: Failed to register clipboard format "
"for %s", qPrintable(mime));
}
return int(f);
}
QT_END_NAMESPACE