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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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