1// Copyright (C) 2016 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
4#include "qmimedata.h"
5
6#include "private/qobject_p.h"
7#include "qurl.h"
8#include "qstringlist.h"
9#include "qstringconverter.h"
10
11QT_BEGIN_NAMESPACE
12
13using namespace Qt::StringLiterals;
14
15static inline QString textUriListLiteral() { return QStringLiteral("text/uri-list"); }
16static inline QString textHtmlLiteral() { return QStringLiteral("text/html"); }
17static inline QString textPlainLiteral() { return QStringLiteral("text/plain"); }
18static inline QString textPlainUtf8Literal() { return QStringLiteral("text/plain;charset=utf-8"); }
19static inline QString applicationXColorLiteral() { return QStringLiteral("application/x-color"); }
20static inline QString applicationXQtImageLiteral() { return QStringLiteral("application/x-qt-image"); }
21
22struct QMimeDataStruct
23{
24 QString format;
25 QVariant data;
26};
27Q_DECLARE_TYPEINFO(QMimeDataStruct, Q_RELOCATABLE_TYPE);
28
29class QMimeDataPrivate : public QObjectPrivate
30{
31 Q_DECLARE_PUBLIC(QMimeData)
32public:
33 void removeData(const QString &format);
34 void setData(const QString &format, const QVariant &data);
35 QVariant getData(const QString &format) const;
36
37 QVariant retrieveTypedData(const QString &format, QMetaType type) const;
38
39 std::vector<QMimeDataStruct>::iterator find(const QString &format) noexcept {
40 const auto formatEquals = [](const QString &format) {
41 return [&format](const QMimeDataStruct &s) { return s.format == format; };
42 };
43 return std::find_if(first: dataList.begin(), last: dataList.end(), pred: formatEquals(format));
44 }
45
46 std::vector<QMimeDataStruct>::const_iterator find(const QString &format) const noexcept {
47 return const_cast<QMimeDataPrivate*>(this)->find(format);
48 }
49
50 std::vector<QMimeDataStruct> dataList;
51};
52
53void QMimeDataPrivate::removeData(const QString &format)
54{
55 const auto it = find(format);
56 if (it != dataList.end())
57 dataList.erase(position: it);
58}
59
60void QMimeDataPrivate::setData(const QString &format, const QVariant &data)
61{
62 const auto it = find(format);
63 if (it == dataList.end())
64 dataList.push_back(x: {.format: format, .data: data});
65 else
66 it->data = data;
67}
68
69
70QVariant QMimeDataPrivate::getData(const QString &format) const
71{
72 const auto it = find(format);
73 if (it == dataList.cend())
74 return {};
75 else
76 return it->data;
77}
78
79QVariant QMimeDataPrivate::retrieveTypedData(const QString &format, QMetaType type) const
80{
81 Q_Q(const QMimeData);
82 int typeId = type.id();
83
84 QVariant data = q->retrieveData(mimetype: format, preferredType: type);
85
86 // Text data requested: fallback to URL data if available
87 if (format == "text/plain"_L1 && !data.isValid()) {
88 data = retrieveTypedData(format: textUriListLiteral(), type: QMetaType(QMetaType::QVariantList));
89 if (data.metaType().id() == QMetaType::QUrl) {
90 data = QVariant(data.toUrl().toDisplayString());
91 } else if (data.metaType().id() == QMetaType::QVariantList) {
92 QString text;
93 int numUrls = 0;
94 const QList<QVariant> list = data.toList();
95 for (int i = 0; i < list.size(); ++i) {
96 if (list.at(i).metaType().id() == QMetaType::QUrl) {
97 text += list.at(i).toUrl().toDisplayString() + u'\n';
98 ++numUrls;
99 }
100 }
101 if (numUrls == 1)
102 text.chop(n: 1); // no final '\n' if there's only one URL
103 data = QVariant(text);
104 }
105 }
106
107 if (data.metaType() == type || !data.isValid())
108 return data;
109
110 // provide more conversion possibilities than just what QVariant provides
111
112 // URLs can be lists as well...
113 if ((typeId == QMetaType::QUrl && data.metaType().id() == QMetaType::QVariantList)
114 || (typeId == QMetaType::QVariantList && data.metaType().id() == QMetaType::QUrl))
115 return data;
116
117 // images and pixmaps are interchangeable
118 if ((typeId == QMetaType::QPixmap && data.metaType().id() == QMetaType::QImage)
119 || (typeId == QMetaType::QImage && data.metaType().id() == QMetaType::QPixmap))
120 return data;
121
122 if (data.metaType().id() == QMetaType::QByteArray) {
123 // see if we can convert to the requested type
124 switch (typeId) {
125 case QMetaType::QString: {
126 const QByteArray ba = data.toByteArray();
127 if (ba.isNull())
128 return QVariant();
129 if (format == "text/html"_L1) {
130 QStringDecoder decoder = QStringDecoder::decoderForHtml(data: ba);
131 if (decoder.isValid()) {
132 return QString(decoder(ba));
133 }
134 // fall back to utf8
135 }
136 return QString::fromUtf8(ba);
137 }
138 case QMetaType::QColor: {
139 QVariant newData = data;
140 newData.convert(type: QMetaType(QMetaType::QColor));
141 return newData;
142 }
143 case QMetaType::QVariantList: {
144 if (format != "text/uri-list"_L1)
145 break;
146 Q_FALLTHROUGH();
147 }
148 case QMetaType::QUrl: {
149 QByteArray ba = data.toByteArray();
150 // Qt 3.x will send text/uri-list with a trailing
151 // null-terminator (that is *not* sent for any other
152 // text/* mime-type), so chop it off
153 if (ba.endsWith(c: '\0'))
154 ba.chop(n: 1);
155
156 QList<QByteArray> urls = ba.split(sep: '\n');
157 QList<QVariant> list;
158 for (int i = 0; i < urls.size(); ++i) {
159 QByteArray ba = urls.at(i).trimmed();
160 if (!ba.isEmpty())
161 list.append(t: QUrl::fromEncoded(url: ba));
162 }
163 return list;
164 }
165 default:
166 break;
167 }
168
169 } else if (typeId == QMetaType::QByteArray) {
170
171 // try to convert to bytearray
172 switch (data.metaType().id()) {
173 case QMetaType::QByteArray:
174 case QMetaType::QColor:
175 return data.toByteArray();
176 case QMetaType::QString:
177 return data.toString().toUtf8();
178 case QMetaType::QUrl:
179 return data.toUrl().toEncoded();
180 case QMetaType::QVariantList: {
181 // has to be list of URLs
182 QByteArray result;
183 QList<QVariant> list = data.toList();
184 for (int i = 0; i < list.size(); ++i) {
185 if (list.at(i).metaType().id() == QMetaType::QUrl) {
186 result += list.at(i).toUrl().toEncoded();
187 result += "\r\n";
188 }
189 }
190 if (!result.isEmpty())
191 return result;
192 break;
193 }
194 default:
195 break;
196 }
197 }
198 return data;
199}
200
201/*!
202 \class QMimeData
203 \inmodule QtCore
204 \brief The QMimeData class provides a container for data that records information
205 about its MIME type.
206
207 QMimeData is used to describe information that can be stored in
208 the \l{QClipboard}{clipboard}, and transferred via the \l{drag
209 and drop} mechanism. QMimeData objects associate the data that
210 they hold with the corresponding MIME types to ensure that
211 information can be safely transferred between applications, and
212 copied around within the same application.
213
214 QMimeData objects are usually created using \c new and supplied
215 to QDrag or QClipboard objects. This is to enable Qt to manage
216 the memory that they use.
217
218 A single QMimeData object can store the same data using several
219 different formats at the same time. The formats() function
220 returns a list of the available formats in order of preference.
221 The data() function returns the raw data associated with a MIME
222 type, and setData() allows you to set the data for a MIME type.
223
224 For the most common MIME types, QMimeData provides convenience
225 functions to access the data:
226
227 \table
228 \header \li Tester \li Getter \li Setter \li MIME Types
229 \row \li hasText() \li text() \li setText() \li \c text/plain
230 \row \li hasHtml() \li html() \li setHtml() \li \c text/html
231 \row \li hasUrls() \li urls() \li setUrls() \li \c text/uri-list
232 \row \li hasImage() \li imageData() \li setImageData() \li \c image/ *
233 \row \li hasColor() \li colorData() \li setColorData() \li \c application/x-color
234 \endtable
235
236 For example, if your write a widget that accepts URL drags, you
237 would end up writing code like this:
238
239 \snippet code/src_corelib_kernel_qmimedata.cpp 0
240
241 There are three approaches for storing custom data in a QMimeData
242 object:
243
244 \list 1
245 \li Custom data can be stored directly in a QMimeData object as a
246 QByteArray using setData(). For example:
247
248 \snippet code/src_corelib_kernel_qmimedata.cpp 1
249
250 \li We can subclass QMimeData and reimplement hasFormat(),
251 formats(), and retrieveData().
252
253 \li If the drag and drop operation occurs within a single
254 application, we can subclass QMimeData and add extra data in
255 it, and use a qobject_cast() in the receiver's drop event
256 handler. For example:
257
258 \snippet code/src_corelib_kernel_qmimedata.cpp 2
259 \endlist
260
261 \section1 Platform-Specific MIME Types
262
263 On Windows, formats() will also return custom formats available
264 in the MIME data, using the \c{x-qt-windows-mime} subtype to
265 indicate that they represent data in non-standard formats.
266 The formats will take the following form:
267
268 \snippet code/src_corelib_kernel_qmimedata.cpp 3
269
270 The following are examples of custom MIME types:
271
272 \snippet code/src_corelib_kernel_qmimedata.cpp 4
273
274 The \c value declaration of each format describes the way in which the
275 data is encoded.
276
277 In some cases (e.g. dropping multiple email attachments), multiple data
278 values are available. They can be accessed by adding an \c index value:
279
280 \snippet code/src_corelib_kernel_qmimedata.cpp 8
281
282 On Windows, the MIME format does not always map directly to the
283 clipboard formats. Qt provides QWindowsMimeConverter to map clipboard
284 formats to open-standard MIME formats. Similarly, the
285 QUtiMimeConverter maps MIME to Uniform Type Identifiers on macOS and iOS.
286
287 \sa QClipboard, QDragEnterEvent, QDragMoveEvent, QDropEvent, QDrag,
288 {Drag and Drop}
289*/
290
291/*!
292 Constructs a new MIME data object with no data in it.
293*/
294QMimeData::QMimeData()
295 : QObject(*new QMimeDataPrivate, nullptr)
296{
297}
298
299/*!
300 Destroys the MIME data object.
301*/
302QMimeData::~QMimeData()
303{
304}
305
306/*!
307 Returns a list of URLs contained within the MIME data object.
308
309 URLs correspond to the MIME type \c text/uri-list.
310
311 \sa hasUrls(), data()
312*/
313QList<QUrl> QMimeData::urls() const
314{
315 Q_D(const QMimeData);
316 QVariant data = d->retrieveTypedData(format: textUriListLiteral(), type: QMetaType(QMetaType::QVariantList));
317 QList<QUrl> urls;
318 if (data.metaType().id() == QMetaType::QUrl)
319 urls.append(t: data.toUrl());
320 else if (data.metaType().id() == QMetaType::QVariantList) {
321 QList<QVariant> list = data.toList();
322 for (int i = 0; i < list.size(); ++i) {
323 if (list.at(i).metaType().id() == QMetaType::QUrl)
324 urls.append(t: list.at(i).toUrl());
325 }
326 }
327 return urls;
328}
329
330/*!
331 Sets the URLs stored in the MIME data object to those specified by \a urls.
332
333 URLs correspond to the MIME type \c text/uri-list.
334
335 Since Qt 5.0, setUrls also exports the urls as plain text, if setText
336 was not called before, to make it possible to drop them into any lineedit
337 and text editor.
338
339 \sa hasUrls(), setData()
340*/
341void QMimeData::setUrls(const QList<QUrl> &urls)
342{
343 Q_D(QMimeData);
344 d->setData(format: textUriListLiteral(), data: QList<QVariant>(urls.cbegin(), urls.cend()));
345}
346
347/*!
348 Returns \c true if the object can return a list of urls; otherwise
349 returns \c false.
350
351 URLs correspond to the MIME type \c text/uri-list.
352
353 \sa setUrls(), urls(), hasFormat()
354*/
355bool QMimeData::hasUrls() const
356{
357 return hasFormat(mimetype: textUriListLiteral());
358}
359
360
361/*!
362 Returns a plain text (MIME type \c text/plain) representation of
363 the data.
364
365 \sa hasText(), html(), data()
366*/
367QString QMimeData::text() const
368{
369 Q_D(const QMimeData);
370 QVariant utf8Text = d->retrieveTypedData(format: textPlainUtf8Literal(), type: QMetaType(QMetaType::QString));
371 if (!utf8Text.isNull())
372 return utf8Text.toString();
373
374 QVariant data = d->retrieveTypedData(format: textPlainLiteral(), type: QMetaType(QMetaType::QString));
375 return data.toString();
376}
377
378/*!
379 Sets \a text as the plain text (MIME type \c text/plain) used to
380 represent the data.
381
382 \sa hasText(), setHtml(), setData()
383*/
384void QMimeData::setText(const QString &text)
385{
386 Q_D(QMimeData);
387 d->setData(format: textPlainLiteral(), data: text);
388}
389
390/*!
391 Returns \c true if the object can return plain text (MIME type \c
392 text/plain); otherwise returns \c false.
393
394 \sa setText(), text(), hasHtml(), hasFormat()
395*/
396bool QMimeData::hasText() const
397{
398 return hasFormat(mimetype: textPlainLiteral()) || hasUrls();
399}
400
401/*!
402 Returns a string if the data stored in the object is HTML (MIME
403 type \c text/html); otherwise returns an empty string.
404
405 \sa hasHtml(), setData()
406*/
407QString QMimeData::html() const
408{
409 Q_D(const QMimeData);
410 QVariant data = d->retrieveTypedData(format: textHtmlLiteral(), type: QMetaType(QMetaType::QString));
411 return data.toString();
412}
413
414/*!
415 Sets \a html as the HTML (MIME type \c text/html) used to
416 represent the data.
417
418 \sa hasHtml(), setText(), setData()
419*/
420void QMimeData::setHtml(const QString &html)
421{
422 Q_D(QMimeData);
423 d->setData(format: textHtmlLiteral(), data: html);
424}
425
426/*!
427 Returns \c true if the object can return HTML (MIME type \c
428 text/html); otherwise returns \c false.
429
430 \sa setHtml(), html(), hasFormat()
431*/
432bool QMimeData::hasHtml() const
433{
434 return hasFormat(mimetype: textHtmlLiteral());
435}
436
437/*!
438 Returns a QVariant storing a QImage if the object can return an
439 image; otherwise returns a null variant.
440
441 A QVariant is used because QMimeData belongs to the Qt Core
442 module, whereas QImage belongs to Qt GUI. To convert the
443 QVariant to a QImage, simply use qvariant_cast(). For example:
444
445 \snippet code/src_corelib_kernel_qmimedata.cpp 5
446
447 \sa hasImage()
448*/
449QVariant QMimeData::imageData() const
450{
451 Q_D(const QMimeData);
452 return d->retrieveTypedData(format: applicationXQtImageLiteral(), type: QMetaType(QMetaType::QImage));
453}
454
455/*!
456 Sets the data in the object to the given \a image.
457
458 A QVariant is used because QMimeData belongs to the Qt Core
459 module, whereas QImage belongs to Qt GUI. The conversion
460 from QImage to QVariant is implicit. For example:
461
462 \snippet code/src_corelib_kernel_qmimedata.cpp 6
463
464 \sa hasImage(), setData()
465*/
466void QMimeData::setImageData(const QVariant &image)
467{
468 Q_D(QMimeData);
469 d->setData(format: applicationXQtImageLiteral(), data: image);
470}
471
472/*!
473 Returns \c true if the object can return an image; otherwise returns
474 false.
475
476 \sa setImageData(), imageData(), hasFormat()
477*/
478bool QMimeData::hasImage() const
479{
480 return hasFormat(mimetype: applicationXQtImageLiteral());
481}
482
483/*!
484 Returns a color if the data stored in the object represents a
485 color (MIME type \c application/x-color); otherwise returns a
486 null variant.
487
488 A QVariant is used because QMimeData belongs to the Qt Core
489 module, whereas QColor belongs to Qt GUI. To convert the
490 QVariant to a QColor, simply use qvariant_cast(). For example:
491
492 \snippet code/src_corelib_kernel_qmimedata.cpp 7
493
494 \sa hasColor(), setColorData(), data()
495*/
496QVariant QMimeData::colorData() const
497{
498 Q_D(const QMimeData);
499 return d->retrieveTypedData(format: applicationXColorLiteral(), type: QMetaType(QMetaType::QColor));
500}
501
502/*!
503 Sets the color data in the object to the given \a color.
504
505 Colors correspond to the MIME type \c application/x-color.
506
507 \sa hasColor(), setData()
508*/
509void QMimeData::setColorData(const QVariant &color)
510{
511 Q_D(QMimeData);
512 d->setData(format: applicationXColorLiteral(), data: color);
513}
514
515
516/*!
517 Returns \c true if the object can return a color (MIME type \c
518 application/x-color); otherwise returns \c false.
519
520 \sa setColorData(), colorData(), hasFormat()
521*/
522bool QMimeData::hasColor() const
523{
524 return hasFormat(mimetype: applicationXColorLiteral());
525}
526
527/*!
528 Returns the data stored in the object in the format described by
529 the MIME type specified by \a mimeType.
530*/
531QByteArray QMimeData::data(const QString &mimeType) const
532{
533 Q_D(const QMimeData);
534 QVariant data = d->retrieveTypedData(format: mimeType, type: QMetaType(QMetaType::QByteArray));
535 return data.toByteArray();
536}
537
538/*!
539 Sets the data associated with the MIME type given by \a mimeType
540 to the specified \a data.
541
542 For the most common types of data, you can call the higher-level
543 functions setText(), setHtml(), setUrls(), setImageData(), and
544 setColorData() instead.
545
546 Note that if you want to use a custom data type in an item view drag and drop
547 operation, you must register it as a Qt \l{QMetaType}{meta type}, using the
548 Q_DECLARE_METATYPE() macro, and implement stream operators for it.
549
550 \sa hasFormat(), QMetaType, {QMetaType::}{Q_DECLARE_METATYPE()}
551*/
552void QMimeData::setData(const QString &mimeType, const QByteArray &data)
553{
554 Q_D(QMimeData);
555
556 if (mimeType == "text/uri-list"_L1) {
557 QByteArray ba = data;
558 if (ba.endsWith(c: '\0'))
559 ba.chop(n: 1);
560 QList<QByteArray> urls = ba.split(sep: '\n');
561 QList<QVariant> list;
562 for (int i = 0; i < urls.size(); ++i) {
563 QByteArray ba = urls.at(i).trimmed();
564 if (!ba.isEmpty())
565 list.append(t: QUrl::fromEncoded(url: ba));
566 }
567 d->setData(format: mimeType, data: list);
568 } else {
569 d->setData(format: mimeType, data: QVariant(data));
570 }
571}
572
573/*!
574 Returns \c true if the object can return data for the MIME type
575 specified by \a mimeType; otherwise returns \c false.
576
577 For the most common types of data, you can call the higher-level
578 functions hasText(), hasHtml(), hasUrls(), hasImage(), and
579 hasColor() instead.
580
581 \sa formats(), setData(), data()
582*/
583bool QMimeData::hasFormat(const QString &mimeType) const
584{
585 return formats().contains(str: mimeType);
586}
587
588/*!
589 Returns a list of formats supported by the object. This is a list
590 of MIME types for which the object can return suitable data. The
591 formats in the list are in a priority order.
592
593 For the most common types of data, you can call the higher-level
594 functions hasText(), hasHtml(), hasUrls(), hasImage(), and
595 hasColor() instead.
596
597 \sa hasFormat(), setData(), data()
598*/
599QStringList QMimeData::formats() const
600{
601 Q_D(const QMimeData);
602 QStringList list;
603 list.reserve(size: static_cast<int>(d->dataList.size()));
604 for (auto &e : d->dataList)
605 list += e.format;
606 return list;
607}
608
609/*!
610 Returns a variant with the given \a type containing data for the
611 MIME type specified by \a mimeType. If the object does not
612 support the MIME type or variant type given, a null variant is
613 returned instead.
614
615 This function is called by the general data() getter and by the
616 convenience getters (text(), html(), urls(), imageData(), and
617 colorData()). You can reimplement it if you want to store your
618 data using a custom data structure (instead of a QByteArray,
619 which is what setData() provides). You would then also need
620 to reimplement hasFormat() and formats().
621
622 \sa data()
623*/
624QVariant QMimeData::retrieveData(const QString &mimeType, QMetaType type) const
625{
626 Q_UNUSED(type);
627 Q_D(const QMimeData);
628 return d->getData(format: mimeType);
629}
630
631/*!
632 Removes all the MIME type and data entries in the object.
633*/
634void QMimeData::clear()
635{
636 Q_D(QMimeData);
637 d->dataList.clear();
638}
639
640/*!
641 \since 4.4
642
643 Removes the data entry for \a mimeType in the object.
644*/
645void QMimeData::removeFormat(const QString &mimeType)
646{
647 Q_D(QMimeData);
648 d->removeData(format: mimeType);
649}
650
651QT_END_NAMESPACE
652
653#include "moc_qmimedata.cpp"
654

source code of qtbase/src/corelib/kernel/qmimedata.cpp