Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsmimeregistry.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtGui/private/qinternalmimedata_p.h>
8#include <QtCore/qbytearraymatcher.h>
9#include <QtCore/qmap.h>
10#include <QtCore/qurl.h>
11#include <QtCore/qdir.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qbuffer.h>
14#include <QtGui/qimagereader.h>
15#include <QtGui/qimagewriter.h>
16
17#include <shlobj.h>
18#include <algorithm>
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24/* The MSVC compilers allows multi-byte characters, that has the behavior of
25 * that each character gets shifted into position. 0x73524742 below is for MSVC
26 * equivalent to doing 'sRGB', but this does of course not work
27 * on conformant C++ compilers. */
28#define BMP_LCS_sRGB 0x73524742
29#define BMP_LCS_GM_IMAGES 0x00000004L
30
31struct _CIEXYZ {
33};
34
38
65
66static const char dibFormatC[] = "dib";
67
68static inline QByteArray msgConversionError(const char *func, const char *format)
69{
70 QByteArray msg = func;
71 msg += ": Unable to convert DIB image. The image converter plugin for '";
72 msg += format;
73 msg += "' is not available. Available formats: ";
74 const auto &formats = QImageReader::supportedImageFormats();
75 for (const QByteArray &af : formats) {
76 msg += af;
77 msg += ' ';
78 }
79 return msg;
80}
81
82static inline bool readDib(QBuffer &buffer, QImage &img)
83{
84 QImageReader reader(&buffer, dibFormatC);
85 if (!reader.canRead()) {
86 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
87 return false;
88 }
89 img = reader.read();
90 return true;
91}
92
93static QByteArray writeDib(const QImage &img)
94{
95 QByteArray ba;
96 QBuffer buffer(&ba);
97 buffer.open(QIODevice::ReadWrite);
98 QImageWriter writer(&buffer, dibFormatC);
99 if (!writer.canWrite()) {
100 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
101 return ba;
102 }
103 if (!writer.write(img))
104 ba.clear();
105 return ba;
106}
107
108static bool qt_write_dibv5(QDataStream &s, QImage image)
109{
110 QIODevice* d = s.device();
111 if (!d->isWritable())
112 return false;
113
114 //depth will be always 32
115 qsizetype bpl_bmp = qsizetype(image.width()) * 4;
116 qsizetype size = bpl_bmp * image.height();
117 if (qsizetype(DWORD(size)) != size)
118 return false;
119
121 ZeroMemory(&bi, sizeof(bi));
122 bi.bV5Size = sizeof(BMP_BITMAPV5HEADER);
123 bi.bV5Width = image.width();
124 bi.bV5Height = image.height();
125 bi.bV5Planes = 1;
126 bi.bV5BitCount = 32;
127 bi.bV5Compression = BI_BITFIELDS;
128 bi.bV5SizeImage = DWORD(bpl_bmp * image.height());
129 bi.bV5XPelsPerMeter = 0;
130 bi.bV5YPelsPerMeter = 0;
131 bi.bV5ClrUsed = 0;
132 bi.bV5ClrImportant = 0;
133 bi.bV5BlueMask = 0x000000ff;
134 bi.bV5GreenMask = 0x0000ff00;
135 bi.bV5RedMask = 0x00ff0000;
136 bi.bV5AlphaMask = 0xff000000;
137 bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB
138 bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES
139
140 d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
141 if (s.status() != QDataStream::Ok)
142 return false;
143
144 d->write(reinterpret_cast<const char *>(&bi.bV5RedMask), sizeof(bi.bV5RedMask));
145 if (s.status() != QDataStream::Ok)
146 return false;
147
148 d->write(reinterpret_cast<const char *>(&bi.bV5GreenMask), sizeof(bi.bV5GreenMask));
149 if (s.status() != QDataStream::Ok)
150 return false;
151
152 d->write(reinterpret_cast<const char *>(&bi.bV5BlueMask), sizeof(bi.bV5BlueMask));
153 if (s.status() != QDataStream::Ok)
154 return false;
155
156 if (image.format() != QImage::Format_ARGB32)
157 image = std::move(image).convertToFormat(QImage::Format_ARGB32);
158
159 auto *buf = new uchar[bpl_bmp];
160
161 memset(buf, 0, size_t(bpl_bmp));
162 for (int y=image.height()-1; y>=0; y--) {
163 // write the image bits
164 const QRgb *p = reinterpret_cast<const QRgb *>(image.constScanLine(y));
165 const QRgb *end = p + image.width();
166 uchar *b = buf;
167 while (p < end) {
168 int alpha = qAlpha(*p);
169 if (alpha) {
170 *b++ = uchar(qBlue(*p));
171 *b++ = uchar(qGreen(*p));
172 *b++ = uchar(qRed(*p));
173 } else {
174 //white for fully transparent pixels.
175 *b++ = 0xff;
176 *b++ = 0xff;
177 *b++ = 0xff;
178 }
179 *b++ = uchar(alpha);
180 p++;
181 }
182 d->write(reinterpret_cast<const char *>(buf), bpl_bmp);
183 if (s.status() != QDataStream::Ok) {
184 delete[] buf;
185 return false;
186 }
187 }
188 delete[] buf;
189 return true;
190}
191
192// helpers for using global memory
193
194static int getCf(const FORMATETC &formatetc)
195{
196 return formatetc.cfFormat;
197}
198
199static FORMATETC setCf(int cf)
200{
201 FORMATETC formatetc;
202 formatetc.cfFormat = CLIPFORMAT(cf);
203 formatetc.dwAspect = DVASPECT_CONTENT;
204 formatetc.lindex = -1;
205 formatetc.ptd = nullptr;
206 formatetc.tymed = TYMED_HGLOBAL;
207 return formatetc;
208}
209
210static bool setData(const QByteArray &data, STGMEDIUM *pmedium)
211{
212 HGLOBAL hData = GlobalAlloc(0, SIZE_T(data.size()));
213 if (!hData)
214 return false;
215
216 void *out = GlobalLock(hData);
217 memcpy(out, data.data(), size_t(data.size()));
218 GlobalUnlock(hData);
219 pmedium->tymed = TYMED_HGLOBAL;
220 pmedium->hGlobal = hData;
221 pmedium->pUnkForRelease = nullptr;
222 return true;
223}
224
225static QByteArray getData(int cf, IDataObject *pDataObj, int lindex = -1)
226{
227 QByteArray data;
228 FORMATETC formatetc = setCf(cf);
229 formatetc.lindex = lindex;
230 STGMEDIUM s;
231 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
232 const void *val = GlobalLock(s.hGlobal);
233 data = QByteArray::fromRawData(reinterpret_cast<const char *>(val), int(GlobalSize(s.hGlobal)));
234 data.detach();
235 GlobalUnlock(s.hGlobal);
236 ReleaseStgMedium(&s);
237 } else {
238 //Try reading IStream data
239 formatetc.tymed = TYMED_ISTREAM;
240 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
241 char szBuffer[4096];
242 ULONG actualRead = 0;
243 LARGE_INTEGER pos = {{0, 0}};
244 //Move to front (can fail depending on the data model implemented)
245 HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
246 while(SUCCEEDED(hr)){
247 hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
248 if (SUCCEEDED(hr) && actualRead > 0) {
249 data += QByteArray::fromRawData(szBuffer, int(actualRead));
250 }
251 if (actualRead != sizeof(szBuffer))
252 break;
253 }
254 data.detach();
255 ReleaseStgMedium(&s);
256 }
257 }
258 return data;
259}
260
261static bool canGetData(int cf, IDataObject * pDataObj)
262{
263 FORMATETC formatetc = setCf(cf);
264 if (pDataObj->QueryGetData(&formatetc) != S_OK){
265 formatetc.tymed = TYMED_ISTREAM;
266 return pDataObj->QueryGetData(&formatetc) == S_OK;
267 }
268 return true;
269}
270
271#ifndef QT_NO_DEBUG_STREAM
272QDebug operator<<(QDebug d, const FORMATETC &tc)
273{
274 QDebugStateSaver saver(d);
275 d.nospace();
276 d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
277 switch (tc.cfFormat) {
278 case CF_TEXT:
279 d << "CF_TEXT";
280 break;
281 case CF_BITMAP:
282 d << "CF_BITMAP";
283 break;
284 case CF_TIFF:
285 d << "CF_TIFF";
286 break;
287 case CF_OEMTEXT:
288 d << "CF_OEMTEXT";
289 break;
290 case CF_DIB:
291 d << "CF_DIB";
292 break;
293 case CF_DIBV5:
294 d << "CF_DIBV5";
295 break;
296 case CF_UNICODETEXT:
297 d << "CF_UNICODETEXT";
298 break;
299 case CF_ENHMETAFILE:
300 d << "CF_ENHMETAFILE";
301 break;
302 default:
303 d << QWindowsMimeRegistry::clipboardFormatName(tc.cfFormat);
304 break;
305 }
306 d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
307 << ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
308 return d;
309}
310
311QDebug operator<<(QDebug d, IDataObject *dataObj)
312{
313 QDebugStateSaver saver(d);
314 d.nospace();
315 d.noquote();
316 d << "IDataObject(";
317 if (dataObj) { // Output formats contained in IDataObject.
318 IEnumFORMATETC *enumFormatEtc;
319 if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
320 FORMATETC formatEtc[1];
321 ULONG fetched;
322 if (SUCCEEDED(enumFormatEtc->Reset())) {
323 while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
324 d << formatEtc[0] << ',';
325 enumFormatEtc->Release();
326 }
327 }
328 } else {
329 d << "0x0";
330 }
331 d << ')';
332 return d;
333}
334#endif // !QT_NO_DEBUG_STREAM
335
337{
338public:
339 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
340 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
341 QString mimeForFormat(const FORMATETC &formatetc) const override;
342 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
343 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
344 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
345};
346
347bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
348{
349 int cf = getCf(formatetc);
350 return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
351}
352
353/*
354text/plain is defined as using CRLF, but so many programs don't,
355and programmers just look for '\n' in strings.
356Windows really needs CRLF, so we ensure it here.
357*/
358bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
359{
360 if (canConvertFromMime(formatetc, mimeData)) {
361 QByteArray data;
362 int cf = getCf(formatetc);
363 if (cf == CF_TEXT) {
364 data = mimeData->text().toLocal8Bit();
365 // Anticipate required space for CRLFs at 1/40
366 int maxsize=data.size()+data.size()/40+3;
367 QByteArray r(maxsize, '\0');
368 char* o = r.data();
369 const char* d = data.data();
370 const int s = data.size();
371 bool cr=false;
372 int j=0;
373 for (int i=0; i<s; i++) {
374 char c = d[i];
375 if (c=='\r')
376 cr=true;
377 else {
378 if (c=='\n') {
379 if (!cr)
380 o[j++]='\r';
381 }
382 cr=false;
383 }
384 o[j++]=c;
385 if (j+3 >= maxsize) {
386 maxsize += maxsize/4;
387 r.resize(maxsize);
388 o = r.data();
389 }
390 }
391 o[j]=0;
392 return setData(r, pmedium);
393 }
394 if (cf == CF_UNICODETEXT) {
395 QString str = mimeData->text();
396 const QChar *u = str.unicode();
397 QString res;
398 const int s = str.length();
399 int maxsize = s + s/40 + 3;
400 res.resize(maxsize);
401 int ri = 0;
402 bool cr = false;
403 for (int i=0; i < s; ++i) {
404 if (*u == u'\r')
405 cr = true;
406 else {
407 if (*u == u'\n' && !cr)
408 res[ri++] = u'\r';
409 cr = false;
410 }
411 res[ri++] = *u;
412 if (ri+3 >= maxsize) {
413 maxsize += maxsize/4;
414 res.resize(maxsize);
415 }
416 ++u;
417 }
418 res.truncate(ri);
419 const int byteLength = res.length() * int(sizeof(ushort));
420 QByteArray r(byteLength + 2, '\0');
421 memcpy(r.data(), res.unicode(), size_t(byteLength));
422 r[byteLength] = 0;
423 r[byteLength+1] = 0;
424 return setData(r, pmedium);
425 }
426 }
427 return false;
428}
429
430bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
431{
432 return mimeType.startsWith(u"text/plain")
433 && (canGetData(CF_UNICODETEXT, pDataObj)
434 || canGetData(CF_TEXT, pDataObj));
435}
436
437QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
438{
439 int cf = getCf(formatetc);
440 if (cf == CF_UNICODETEXT || cf == CF_TEXT)
441 return u"text/plain"_s;
442 return QString();
443}
444
445
446QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
447{
448 QList<FORMATETC> formatics;
449 if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
450 formatics += setCf(CF_UNICODETEXT);
451 if (GetACP() != CP_UTF8)
452 formatics += setCf(CF_TEXT);
453 }
454 return formatics;
455}
456
457QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const
458{
459 QVariant ret;
460
461 if (canConvertToMime(mime, pDataObj)) {
462 QString str;
463 QByteArray data = getData(CF_UNICODETEXT, pDataObj);
464 if (!data.isEmpty()) {
465 str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
466 str.replace("\r\n"_L1, "\n"_L1);
467 } else {
468 data = getData(CF_TEXT, pDataObj);
469 if (!data.isEmpty()) {
470 const char* d = data.data();
471 const unsigned s = unsigned(qstrlen(d));
472 QByteArray r(data.size()+1, '\0');
473 char* o = r.data();
474 int j=0;
475 for (unsigned i = 0; i < s; ++i) {
476 char c = d[i];
477 if (c!='\r')
478 o[j++]=c;
479 }
480 o[j]=0;
481 str = QString::fromLocal8Bit(r);
482 }
483 }
484 if (preferredType.id() == QMetaType::QString)
485 ret = str;
486 else
487 ret = std::move(str).toUtf8();
488 }
489 qCDebug(lcQpaMime) << __FUNCTION__ << ret;
490 return ret;
491}
492
494{
495public:
497 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
498 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
499 QString mimeForFormat(const FORMATETC &formatetc) const override;
500 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
501 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
502 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
503private:
504 int CF_INETURL_W; // wide char version
505 int CF_INETURL;
506};
507
509{
510 CF_INETURL_W = registerMimeType(u"UniformResourceLocatorW"_s);
511 CF_INETURL = registerMimeType(u"UniformResourceLocator"_s);
512}
513
514bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
515{
516 if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
517 const auto urls = mimeData->urls();
518 for (const QUrl &url : urls) {
519 if (url.isLocalFile())
520 return true;
521 }
522 }
523 return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
524}
525
526bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
527{
528 if (canConvertFromMime(formatetc, mimeData)) {
529 if (getCf(formatetc) == CF_HDROP) {
530 const auto &urls = mimeData->urls();
531 QStringList fileNames;
532 size_t size = sizeof(DROPFILES) + 2;
533 for (const QUrl &url : urls) {
534 const QString fn = QDir::toNativeSeparators(url.toLocalFile());
535 if (!fn.isEmpty()) {
536 size += sizeof(ushort) * size_t(fn.length() + 1);
537 fileNames.append(fn);
538 }
539 }
540
541 QByteArray result(int(size), '\0');
542 auto* d = reinterpret_cast<DROPFILES *>(result.data());
543 d->pFiles = sizeof(DROPFILES);
544 GetCursorPos(&d->pt); // try
545 d->fNC = true;
546 char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
547
548 d->fWide = true;
549 auto *f = reinterpret_cast<wchar_t *>(files);
550 for (int i=0; i<fileNames.size(); i++) {
551 const auto l = size_t(fileNames.at(i).length());
552 memcpy(f, fileNames.at(i).data(), l * sizeof(ushort));
553 f += l;
554 *f++ = 0;
555 }
556 *f = 0;
557
558 return setData(result, pmedium);
559 }
560 if (getCf(formatetc) == CF_INETURL_W) {
561 const auto urls = mimeData->urls();
562 QByteArray result;
563 if (!urls.isEmpty()) {
564 const QString url = urls.at(0).toString();
565 result = QByteArray(reinterpret_cast<const char *>(url.data()),
566 url.length() * int(sizeof(ushort)));
567 }
568 result.append('\0');
569 result.append('\0');
570 return setData(result, pmedium);
571 }
572 if (getCf(formatetc) == CF_INETURL) {
573 const auto urls = mimeData->urls();
574 QByteArray result;
575 if (!urls.isEmpty())
576 result = urls.at(0).toString().toLocal8Bit();
577 return setData(result, pmedium);
578 }
579 }
580
581 return false;
582}
583
584bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
585{
586 return mimeType == u"text/uri-list"
587 && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
588}
589
590QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
591{
592 QString format;
593 if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
594 format = u"text/uri-list"_s;
595 return format;
596}
597
598QList<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
599{
600 QList<FORMATETC> formatics;
601 if (mimeType == u"text/uri-list") {
602 if (canConvertFromMime(setCf(CF_HDROP), mimeData))
603 formatics += setCf(CF_HDROP);
604 if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
605 formatics += setCf(CF_INETURL_W);
606 if (canConvertFromMime(setCf(CF_INETURL), mimeData))
607 formatics += setCf(CF_INETURL);
608 }
609 return formatics;
610}
611
612QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QMetaType preferredType) const
613{
614 if (mimeType == u"text/uri-list") {
615 if (canGetData(CF_HDROP, pDataObj)) {
616 QList<QVariant> urls;
617
618 QByteArray data = getData(CF_HDROP, pDataObj);
619 if (data.isEmpty())
620 return QVariant();
621
622 const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.constData());
623 if (hdrop->fWide) {
624 const auto *filesw = reinterpret_cast<const wchar_t *>(data.constData() + hdrop->pFiles);
625 int i = 0;
626 while (filesw[i]) {
627 QString fileurl = QString::fromWCharArray(filesw + i);
628 urls += QUrl::fromLocalFile(fileurl);
629 i += fileurl.length()+1;
630 }
631 } else {
632 const char* files = reinterpret_cast<const char *>(data.constData() + hdrop->pFiles);
633 int i=0;
634 while (files[i]) {
635 urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
636 i += int(strlen(files+i))+1;
637 }
638 }
639
640 if (preferredType.id() == QMetaType::QUrl && urls.size() == 1)
641 return urls.at(0);
642 if (!urls.isEmpty())
643 return urls;
644 } else if (canGetData(CF_INETURL_W, pDataObj)) {
645 QByteArray data = getData(CF_INETURL_W, pDataObj);
646 if (data.isEmpty())
647 return QVariant();
648 return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
649 } else if (canGetData(CF_INETURL, pDataObj)) {
650 QByteArray data = getData(CF_INETURL, pDataObj);
651 if (data.isEmpty())
652 return QVariant();
653 return QUrl(QString::fromLocal8Bit(data.constData()));
654 }
655 }
656 return QVariant();
657}
658
660{
661public:
663
664 // for converting from Qt
665 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
666 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
667 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
668
669 // for converting to Qt
670 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
671 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
672 QString mimeForFormat(const FORMATETC &formatetc) const override;
673
674private:
675 int CF_HTML;
676};
677
679{
680 CF_HTML = registerMimeType(u"HTML Format"_s);
681}
682
683QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
684{
685 QList<FORMATETC> formatetcs;
686 if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
687 formatetcs += setCf(CF_HTML);
688 return formatetcs;
689}
690
691QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
692{
693 if (getCf(formatetc) == CF_HTML)
694 return u"text/html"_s;
695 return QString();
696}
697
698bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
699{
700 return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
701}
702
703
704bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
705{
706 return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
707}
708
709/*
710The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
711in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
712
713 Version: 1.0
714 StartHTML:xxxxxxxxxx
715 EndHTML:xxxxxxxxxx
716 StartFragment:xxxxxxxxxx
717 EndFragment:xxxxxxxxxx
718 ...html...
719
720*/
721QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const
722{
723 Q_UNUSED(preferredType);
724 QVariant result;
725 if (canConvertToMime(mime, pDataObj)) {
726 QByteArray html = getData(CF_HTML, pDataObj);
727 static constexpr auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
728 static constexpr auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:");
729 qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
730 int start = startMatcher.indexIn(html);
731 int end = endMatcher.indexIn(html);
732
733 if (start != -1) {
734 int startOffset = start + 10;
735 int i = startOffset;
736 while (html.at(i) != '\r' && html.at(i) != '\n')
737 ++i;
738 QByteArray bytecount = html.mid(startOffset, i - startOffset);
739 start = bytecount.toInt();
740 }
741
742 if (end != -1) {
743 int endOffset = end + 8;
744 int i = endOffset ;
745 while (html.at(i) != '\r' && html.at(i) != '\n')
746 ++i;
747 QByteArray bytecount = html.mid(endOffset , i - endOffset);
748 end = bytecount.toInt();
749 }
750
751 if (end > start && start > 0) {
752 html = html.mid(start, end - start);
753 html.replace('\r', "");
754 result = QString::fromUtf8(html);
755 }
756 }
757 return result;
758}
759
760bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
761{
762 if (canConvertFromMime(formatetc, mimeData)) {
763 QByteArray data = mimeData->html().toUtf8();
764 QByteArray result =
765 "Version:1.0\r\n" // 0-12
766 "StartHTML:0000000107\r\n" // 13-35
767 "EndHTML:0000000000\r\n" // 36-55
768 "StartFragment:0000000000\r\n" // 56-81
769 "EndFragment:0000000000\r\n\r\n"; // 82-107
770
771 static constexpr auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
772 static constexpr auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
773
774 if (startFragmentMatcher.indexIn(data) == -1)
775 result += "<!--StartFragment-->";
776 result += data;
777 if (endFragmentMatcher.indexIn(data) == -1)
778 result += "<!--EndFragment-->";
779
780 // set the correct number for EndHTML
781 QByteArray pos = QByteArray::number(result.size());
782 memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
783
784 // set correct numbers for StartFragment and EndFragment
785 pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
786 memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
787 pos = QByteArray::number(endFragmentMatcher.indexIn(result));
788 memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
789
790 return setData(result, pmedium);
791 }
792 return false;
793}
794
795
796#ifndef QT_NO_IMAGEFORMAT_BMP
798{
799public:
801 // for converting from Qt
802 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
803 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
804 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
805
806 // for converting to Qt
807 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
808 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
809 QString mimeForFormat(const FORMATETC &formatetc) const override;
810private:
811 bool hasOriginalDIBV5(IDataObject *pDataObj) const;
812 UINT CF_PNG;
813};
814
816{
817 CF_PNG = RegisterClipboardFormat(L"PNG");
818}
819
820QList<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
821{
822 QList<FORMATETC> formatetcs;
823 if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
824 //add DIBV5 if image has alpha channel. Do not add CF_PNG here as it will confuse MS Office (QTBUG47656).
825 auto image = qvariant_cast<QImage>(mimeData->imageData());
826 if (!image.isNull() && image.hasAlphaChannel())
827 formatetcs += setCf(CF_DIBV5);
828 formatetcs += setCf(CF_DIB);
829 }
830 if (!formatetcs.isEmpty())
831 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
832 return formatetcs;
833}
834
835QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
836{
837 int cf = getCf(formatetc);
838 if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
839 return u"application/x-qt-image"_s;
840 return QString();
841}
842
843bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
844{
845 return mimeType == u"application/x-qt-image"
846 && (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
847}
848
849bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
850{
851 int cf = getCf(formatetc);
852 if (!mimeData->hasImage())
853 return false;
854 const QImage image = qvariant_cast<QImage>(mimeData->imageData());
855 if (image.isNull())
856 return false;
857 // QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
858 // cannot handle it.
859 return cf == CF_DIBV5 || cf == CF_DIB
860 || (cf == int(CF_PNG) && image.hasAlphaChannel());
861}
862
863bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
864{
865 int cf = getCf(formatetc);
866 if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
867 auto img = qvariant_cast<QImage>(mimeData->imageData());
868 if (img.isNull())
869 return false;
870 QByteArray ba;
871 if (cf == CF_DIB) {
872 if (img.format() > QImage::Format_ARGB32)
873 img = std::move(img).convertToFormat(QImage::Format_RGB32);
874 const QByteArray ba = writeDib(img);
875 if (!ba.isEmpty())
876 return setData(ba, pmedium);
877 } else if (cf == int(CF_PNG)) {
878 QBuffer buffer(&ba);
879 const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
880 buffer.close();
881 if (written)
882 return setData(ba, pmedium);
883 } else {
884 QDataStream s(&ba, QIODevice::WriteOnly);
885 s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
886 if (qt_write_dibv5(s, std::move(img)))
887 return setData(ba, pmedium);
888 }
889 }
890 return false;
891}
892
893bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
894{
895 bool isSynthesized = true;
896 IEnumFORMATETC *pEnum = nullptr;
897 HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
898 if (res == S_OK && pEnum) {
899 FORMATETC fc;
900 while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
901 if (fc.ptd)
902 CoTaskMemFree(fc.ptd);
903 if (fc.cfFormat == CF_DIB)
904 break;
905 if (fc.cfFormat == CF_DIBV5) {
906 isSynthesized = false;
907 break;
908 }
909 }
910 pEnum->Release();
911 }
912 return !isSynthesized;
913}
914
915QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
916{
917 Q_UNUSED(preferredType);
918 QVariant result;
919 if (mimeType != u"application/x-qt-image")
920 return result;
921 // Try to convert from DIBV5 as it is the most widespread format that supports transparency,
922 // but avoid synthesizing it, as that typically loses transparency, e.g. from Office
923 const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
924 const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
925 qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
926 // PNG, MS Office place this (undocumented)
927 if (!hasOrigDibV5 && canGetData(CF_PNG, pDataObj)) {
928 qCDebug(lcQpaMime) << "Decoding PNG";
929 QImage img;
930 QByteArray data = getData(CF_PNG, pDataObj);
931 if (img.loadFromData(data, "PNG")) {
932 return img;
933 }
934 }
935
936 if (canGetDibV5 || canGetData(CF_DIB, pDataObj)) {
937 qCDebug(lcQpaMime) << "Decoding DIB";
938 QImage img;
939 QByteArray data = getData(canGetDibV5 ? CF_DIBV5 : CF_DIB, pDataObj);
940 QBuffer buffer(&data);
941 if (readDib(buffer, img))
942 return img;
943 }
944 // Failed
945 return result;
946}
947#endif
948
950{
951public:
953
954 // for converting from Qt
955 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
956 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
957 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
958
959 // for converting to Qt
960 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
961 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
962 QString mimeForFormat(const FORMATETC &formatetc) const override;
963
964private:
965 QMap<int, QString> outFormats;
966 QMap<int, QString> inFormats;
967};
968
971{
972 outFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
973 inFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
974}
975
976bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
977{
978 // really check
979 return formatetc.tymed & TYMED_HGLOBAL
980 && outFormats.contains(formatetc.cfFormat)
981 && mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
982}
983
984bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
985{
986 if (canConvertFromMime(formatetc, mimeData)) {
987 QByteArray data;
988 if (outFormats.value(getCf(formatetc)) == u"text/html") {
989 // text/html is in wide chars on windows (compatible with mozillia)
990 QString html = mimeData->html();
991 // same code as in the text converter up above
992 const QChar *u = html.unicode();
993 QString res;
994 const int s = html.length();
995 int maxsize = s + s/40 + 3;
996 res.resize(maxsize);
997 int ri = 0;
998 bool cr = false;
999 for (int i=0; i < s; ++i) {
1000 if (*u == u'\r')
1001 cr = true;
1002 else {
1003 if (*u == u'\n' && !cr)
1004 res[ri++] = u'\r';
1005 cr = false;
1006 }
1007 res[ri++] = *u;
1008 if (ri+3 >= maxsize) {
1009 maxsize += maxsize/4;
1010 res.resize(maxsize);
1011 }
1012 ++u;
1013 }
1014 res.truncate(ri);
1015 const int byteLength = res.length() * int(sizeof(ushort));
1016 QByteArray r(byteLength + 2, '\0');
1017 memcpy(r.data(), res.unicode(), size_t(byteLength));
1018 r[byteLength] = 0;
1019 r[byteLength+1] = 0;
1020 data = r;
1021 } else {
1022#if QT_CONFIG(draganddrop)
1023 data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
1024#endif // QT_CONFIG(draganddrop)
1025 }
1026 return setData(data, pmedium);
1027 }
1028 return false;
1029}
1030
1031QList<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1032{
1033 QList<FORMATETC> formatetcs;
1034 const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
1035 if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
1036 formatetcs += setCf(mit.key());
1037 return formatetcs;
1038}
1039
1040bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1041{
1042 const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
1043 return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
1044}
1045
1046QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1047{
1048 QVariant val;
1049 if (canConvertToMime(mimeType, pDataObj)) {
1050 QByteArray data = getData(inFormats.key(mimeType), pDataObj);
1051 if (!data.isEmpty()) {
1052 qCDebug(lcQpaMime) << __FUNCTION__;
1053 if (mimeType == u"text/html" && preferredType == QMetaType(QMetaType::QString)) {
1054 // text/html is in wide chars on windows (compatible with Mozilla)
1055 val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
1056 } else {
1057 val = data; // it should be enough to return the data and let QMimeData do the rest.
1058 }
1059 }
1060 }
1061 return val;
1062}
1063
1064QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
1065{
1066 return inFormats.value(getCf(formatetc));
1067}
1068
1069
1071{
1072public:
1073
1075 // for converting from Qt
1076 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1077 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1078 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1079
1080 // for converting to Qt
1081 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1082 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
1083 QString mimeForFormat(const FORMATETC &formatetc) const override;
1084
1085private:
1086 mutable QMap<int, QString> formats;
1087 static QStringList ianaTypes;
1088 static QStringList excludeList;
1089};
1090
1091QStringList QLastResortMimes::ianaTypes;
1092QStringList QLastResortMimes::excludeList;
1093
1095{
1096 //MIME Media-Types
1097 if (ianaTypes.isEmpty()) {
1098 ianaTypes.append(u"application/"_s);
1099 ianaTypes.append(u"audio/"_s);
1100 ianaTypes.append(u"example/"_s);
1101 ianaTypes.append(u"image/"_s);
1102 ianaTypes.append(u"message/"_s);
1103 ianaTypes.append(u"model/"_s);
1104 ianaTypes.append(u"multipart/"_s);
1105 ianaTypes.append(u"text/"_s);
1106 ianaTypes.append(u"video/"_s);
1107 }
1108 //Types handled by other classes
1109 if (excludeList.isEmpty()) {
1110 excludeList.append(u"HTML Format"_s);
1111 excludeList.append(u"UniformResourceLocator"_s);
1112 excludeList.append(u"text/html"_s);
1113 excludeList.append(u"text/plain"_s);
1114 excludeList.append(u"text/uri-list"_s);
1115 excludeList.append(u"application/x-qt-image"_s);
1116 excludeList.append(u"application/x-color"_s);
1117 }
1118}
1119
1120bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1121{
1122 // really check
1123#if QT_CONFIG(draganddrop)
1124 return formatetc.tymed & TYMED_HGLOBAL
1125 && (formats.contains(formatetc.cfFormat)
1126 && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
1127#else
1128 Q_UNUSED(mimeData);
1129 Q_UNUSED(formatetc);
1130 return formatetc.tymed & TYMED_HGLOBAL
1131 && formats.contains(formatetc.cfFormat);
1132#endif // QT_CONFIG(draganddrop)
1133}
1134
1135bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1136{
1137#if QT_CONFIG(draganddrop)
1138 return canConvertFromMime(formatetc, mimeData)
1139 && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
1140#else
1141 Q_UNUSED(mimeData);
1142 Q_UNUSED(formatetc);
1143 Q_UNUSED(pmedium);
1144 return false;
1145#endif // QT_CONFIG(draganddrop)
1146}
1147
1148QList<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
1149{
1150 QList<FORMATETC> formatetcs;
1151 auto mit = std::find(formats.begin(), formats.end(), mimeType);
1152 // register any other available formats
1153 if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
1154 mit = formats.insert(registerMimeType(mimeType), mimeType);
1155 if (mit != formats.end())
1156 formatetcs += setCf(mit.key());
1157
1158 if (!formatetcs.isEmpty())
1159 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1160 return formatetcs;
1161}
1162static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
1163
1164static bool isCustomMimeType(const QString &mimeType)
1165{
1166 return mimeType.startsWith(QLatin1StringView(x_qt_windows_mime), Qt::CaseInsensitive);
1167}
1168
1169static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
1170{
1171 int len = sizeof(x_qt_windows_mime) - 1;
1172 int n = mimeType.lastIndexOf(u'\"') - len;
1173 QString ret = mimeType.mid(len, n);
1174
1175 const int beginPos = mimeType.indexOf(u";index=");
1176 if (beginPos > -1) {
1177 const int endPos = mimeType.indexOf(u';', beginPos + 1);
1178 const int indexStartPos = beginPos + 7;
1179 if (lindex)
1180 *lindex = QStringView{mimeType}.mid(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
1181 } else {
1182 if (lindex)
1183 *lindex = -1;
1184 }
1185 return ret;
1186}
1187
1188bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1189{
1190 if (isCustomMimeType(mimeType)) {
1191 // MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
1192 QString clipFormat = customMimeType(mimeType);
1193 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1194 return canGetData(int(cf), pDataObj);
1195 }
1196 // if it is not in there then register it and see if we can get it
1197 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1198 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1199 return canGetData(cf, pDataObj);
1200}
1201
1202QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1203{
1204 Q_UNUSED(preferredType);
1205 QVariant val;
1206 if (canConvertToMime(mimeType, pDataObj)) {
1207 QByteArray data;
1208 if (isCustomMimeType(mimeType)) {
1209 int lindex;
1210 QString clipFormat = customMimeType(mimeType, &lindex);
1211 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1212 data = getData(int(cf), pDataObj, lindex);
1213 } else {
1214 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1215 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1216 data = getData(cf, pDataObj);
1217 }
1218 if (!data.isEmpty())
1219 val = data; // it should be enough to return the data and let QMimeData do the rest.
1220 }
1221 return val;
1222}
1223
1224QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
1225{
1226 QString format = formats.value(getCf(formatetc));
1227 if (!format.isEmpty())
1228 return format;
1229
1230 const QString clipFormat = QWindowsMimeRegistry::clipboardFormatName(getCf(formatetc));
1231 if (!clipFormat.isEmpty()) {
1232#if QT_CONFIG(draganddrop)
1233 if (QInternalMimeData::canReadData(clipFormat))
1234 format = clipFormat;
1235 else if ((formatetc.cfFormat >= 0xC000)){
1236 //create the mime as custom. not registered.
1237 if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
1238 //check if this is a mime type
1239 bool ianaType = false;
1240 int sz = ianaTypes.size();
1241 for (int i = 0; i < sz; i++) {
1242 if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
1243 ianaType = true;
1244 break;
1245 }
1246 }
1247 if (!ianaType)
1248 format = QLatin1StringView(x_qt_windows_mime) + clipFormat + u'"';
1249 else
1250 format = clipFormat;
1251 }
1252 }
1253#endif // QT_CONFIG(draganddrop)
1254 }
1255
1256 return format;
1257}
1258
1259/*!
1260 \class QWindowsMimeRegistry
1261 \brief Manages the list of QWindowsMimeConverter instances.
1262 \internal
1263 \sa QWindowsMimeConverter
1264*/
1265
1267
1269{
1270 qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
1271}
1272
1273QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
1274{
1275 ensureInitialized();
1276 for (int i = m_mimes.size()-1; i >= 0; --i) {
1277 if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
1278 return m_mimes.at(i);
1279 }
1280 return nullptr;
1281}
1282
1284{
1285 qCDebug(lcQpaMime) << "QWindowsMimeConverter::allMimesForFormats()";
1286 ensureInitialized();
1287 QStringList formats;
1288 LPENUMFORMATETC FAR fmtenum;
1289 HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
1290
1291 if (hr == NOERROR) {
1292 FORMATETC fmtetc;
1293 while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
1294 for (int i= m_mimes.size() - 1; i >= 0; --i) {
1295 QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
1296 if (!format.isEmpty() && !formats.contains(format)) {
1297 formats += format;
1298 if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
1299 qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
1300 }
1301 }
1302 // as documented in MSDN to avoid possible memleak
1303 if (fmtetc.ptd)
1304 CoTaskMemFree(fmtetc.ptd);
1305 }
1306 fmtenum->Release();
1307 }
1308 qCDebug(lcQpaMime) << pDataObj << formats;
1309 return formats;
1310}
1311
1312QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1313{
1314 ensureInitialized();
1315 qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
1316 for (int i = m_mimes.size()-1; i >= 0; --i) {
1317 if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
1318 return m_mimes.at(i);
1319 }
1320 return nullptr;
1321}
1322
1323QList<FORMATETC> QWindowsMimeRegistry::allFormatsForMime(const QMimeData *mimeData) const
1324{
1325 ensureInitialized();
1326 QList<FORMATETC> formatics;
1327#if !QT_CONFIG(draganddrop)
1328 Q_UNUSED(mimeData);
1329#else
1330 formatics.reserve(20);
1331 const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
1332 for (int f = 0; f < formats.size(); ++f) {
1333 for (int i = m_mimes.size() - 1; i >= 0; --i)
1334 formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
1335 }
1336#endif // QT_CONFIG(draganddrop)
1337 return formatics;
1338}
1339
1340void QWindowsMimeRegistry::ensureInitialized() const
1341{
1342 if (m_internalMimeCount == 0) {
1343 m_internalMimeCount = -1; // prevent reentrancy when types register themselves
1344#ifndef QT_NO_IMAGEFORMAT_BMP
1345 (void)new QWindowsMimeImage;
1346#endif //QT_NO_IMAGEFORMAT_BMP
1347 (void)new QLastResortMimes;
1348 (void)new QWindowsMimeText;
1349 (void)new QWindowsMimeURI;
1350 (void)new QWindowsMimeHtml;
1351 (void)new QBuiltInMimes;
1352 m_internalMimeCount = m_mimes.size();
1353 Q_ASSERT(m_internalMimeCount > 0);
1354 }
1355}
1356
1357QString QWindowsMimeRegistry::clipboardFormatName(int cf)
1358{
1359 wchar_t buf[256] = {0};
1360 return GetClipboardFormatName(UINT(cf), buf, 255)
1361 ? QString::fromWCharArray(buf) : QString();
1362}
1363
1364QVariant QWindowsMimeRegistry::convertToMime(const QStringList &mimeTypes,
1365 IDataObject *pDataObj,
1366 QMetaType preferredType,
1367 QString *formatIn /* = 0 */) const
1368{
1369 for (const QString &format : mimeTypes) {
1370 if (const QWindowsMimeConverter *converter = converterToMime(format, pDataObj)) {
1371 if (converter->canConvertToMime(format, pDataObj)) {
1372 const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
1373 if (dataV.isValid()) {
1374 qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
1375 << format << pDataObj << " returns " << dataV;
1376 if (formatIn)
1377 *formatIn = format;
1378 return dataV;
1379 }
1380 }
1381 }
1382 }
1383 qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType.id();
1384 return QVariant();
1385}
1386
1387void QWindowsMimeRegistry::registerMime(QWindowsMimeConverter *mime)
1388{
1389 ensureInitialized();
1390 m_mimes.append(mime);
1391}
1392
1393/*!
1394 Registers the MIME type \a mime, and returns an ID number
1395 identifying the format on Windows.
1396
1397 A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
1398 registered as the clipboard format for \c WindowsType.
1399*/
1400int QWindowsMimeRegistry::registerMimeType(const QString &mime)
1401{
1402 const QString mimeType = isCustomMimeType(mime) ? customMimeType(mime) : mime;
1403 const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mimeType.utf16()));
1404 if (!f) {
1405 qErrnoWarning("QWindowsMimeRegistry::registerMimeType: Failed to register clipboard format "
1406 "for %s", qPrintable(mime));
1407 }
1408
1409 return int(f);
1410}
1411
1412QT_END_NAMESPACE
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
Manages the list of QWindowsMimeConverter instances.
QVariant convertToMime(const QStringList &mimeTypes, IDataObject *pDataObj, QMetaType preferredType, QString *format=nullptr) const
QWindowsMimeConverter * converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
void registerMime(QWindowsMimeConverter *mime)
QStringList allMimesForFormats(IDataObject *pDataObj) const
QList< FORMATETC > allFormatsForMime(const QMimeData *mimeData) const
QWindowsMimeConverter * converterToMime(const QString &mimeType, IDataObject *pDataObj) const
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
#define BMP_LCS_GM_IMAGES
static QByteArray msgConversionError(const char *func, const char *format)
static const char x_qt_windows_mime[]
static bool canGetData(int cf, IDataObject *pDataObj)
static FORMATETC setCf(int cf)
static bool isCustomMimeType(const QString &mimeType)
static QByteArray writeDib(const QImage &img)
static QByteArray getData(int cf, IDataObject *pDataObj, int lindex=-1)
static QString customMimeType(const QString &mimeType, int *lindex=nullptr)
static bool readDib(QBuffer &buffer, QImage &img)
static bool qt_write_dibv5(QDataStream &s, QImage image)
static int getCf(const FORMATETC &formatetc)
static const char dibFormatC[]
static bool setData(const QByteArray &data, STGMEDIUM *pmedium)
#define BMP_LCS_sRGB