1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 #include <QtTransferable.hxx>
13 #include <comphelper/sequence.hxx>
14 #include <sal/log.hxx>
16 #include <QtWidgets/QApplication>
18 #include <QtInstance.hxx>
19 #include <QtTools.hxx>
23 static bool lcl_textMimeInfo(const OUString
& rMimeString
, bool& bHaveNoCharset
, bool& bHaveUTF16
,
27 if (rMimeString
.getToken(0, ';', nIndex
) == "text/plain")
29 OUString
aToken(rMimeString
.getToken(0, ';', nIndex
));
30 if (aToken
== "charset=utf-16")
32 else if (aToken
== "charset=utf-8")
34 else if (aToken
.isEmpty())
35 bHaveNoCharset
= true;
36 else // we just handle UTF-16 and UTF-8, everything else is "bytes"
43 QtTransferable::QtTransferable(const QMimeData
* pMimeData
)
44 : m_pMimeData(pMimeData
)
45 , m_bProvideUTF16FromOtherEncoding(false)
50 css::uno::Sequence
<css::datatransfer::DataFlavor
> SAL_CALL
QtTransferable::getTransferDataFlavors()
52 // it's just filled once, ever, so just try to get it without locking first
53 if (m_aMimeTypeSeq
.hasElements())
54 return m_aMimeTypeSeq
;
56 // better safe then sorry; preventing broken usage
57 // DnD should not be shared and Clipboard access runs in the GUI thread
58 osl::MutexGuard
aGuard(m_aMutex
);
59 if (m_aMimeTypeSeq
.hasElements())
60 return m_aMimeTypeSeq
;
62 QStringList
aFormatList(m_pMimeData
->formats());
63 // we might add the UTF-16 mime text variant later
64 const int nMimeTypeSeqSize
= aFormatList
.size() + 1;
65 bool bHaveNoCharset
= false, bHaveUTF16
= false, bHaveUTF8
= false;
66 css::uno::Sequence
<css::datatransfer::DataFlavor
> aMimeTypeSeq(nMimeTypeSeqSize
);
67 auto pMimeTypeSeq
= aMimeTypeSeq
.getArray();
69 css::datatransfer::DataFlavor aFlavor
;
70 int nMimeTypeCount
= 0;
72 for (const QString
& rMimeType
: aFormatList
)
74 // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
75 if (rMimeType
.indexOf('/') == -1)
78 // gtk3 thinks it is not well defined - skip too
79 if (rMimeType
== QStringLiteral("text/plain;charset=unicode"))
82 // LO doesn't like 'text/plain', so we have to provide UTF-16
83 bool bIsNoCharset
= false, bIsUTF16
= false, bIsUTF8
= false;
84 if (lcl_textMimeInfo(toOUString(rMimeType
), bIsNoCharset
, bIsUTF16
, bIsUTF8
))
86 bHaveNoCharset
|= bIsNoCharset
;
87 bHaveUTF16
|= bIsUTF16
;
90 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
92 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
95 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
97 aFlavor
.MimeType
= toOUString(rMimeType
);
98 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
99 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
103 m_bProvideUTF16FromOtherEncoding
= (bHaveNoCharset
|| bHaveUTF8
) && !bHaveUTF16
;
104 if (m_bProvideUTF16FromOtherEncoding
)
106 aFlavor
.MimeType
= "text/plain;charset=utf-16";
107 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
108 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
109 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
113 aMimeTypeSeq
.realloc(nMimeTypeCount
);
115 m_aMimeTypeSeq
= aMimeTypeSeq
;
116 return m_aMimeTypeSeq
;
120 QtTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
122 const auto aSeq
= getTransferDataFlavors();
123 return std::any_of(aSeq
.begin(), aSeq
.end(), [&](const css::datatransfer::DataFlavor
& aFlavor
) {
124 return rFlavor
.MimeType
== aFlavor
.MimeType
;
128 css::uno::Any SAL_CALL
QtTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
131 if (!isDataFlavorSupported(rFlavor
))
134 if (rFlavor
.MimeType
== "text/plain;charset=utf-16")
137 if (m_bProvideUTF16FromOtherEncoding
)
139 if (m_pMimeData
->hasFormat("text/plain;charset=utf-8"))
141 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain;charset=utf-8")));
142 aString
= OUString::fromUtf8(reinterpret_cast<const char*>(aByteData
.data()));
146 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain")));
147 aString
= OUString(reinterpret_cast<const char*>(aByteData
.data()),
148 aByteData
.size(), osl_getThreadTextEncoding());
153 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
154 aString
= OUString(reinterpret_cast<const sal_Unicode
*>(aByteData
.data()),
155 aByteData
.size() / 2);
161 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
162 css::uno::Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(aByteData
.data()),
170 QtClipboardTransferable::QtClipboardTransferable(const QClipboard::Mode aMode
,
171 const QMimeData
* pMimeData
)
172 : QtTransferable(pMimeData
)
177 bool QtClipboardTransferable::hasInFlightChanged() const
179 const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode
));
180 SAL_WARN_IF(bChanged
, "vcl.qt", "In flight clipboard change detected - broken clipboard read!");
184 css::uno::Any SAL_CALL
185 QtClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
188 auto* pSalInst(static_cast<QtInstance
*>(GetSalData()->m_pInstance
));
190 pSalInst
->RunInMainThread([&, this]() {
191 if (!hasInFlightChanged())
192 aAny
= QtTransferable::getTransferData(rFlavor
);
197 css::uno::Sequence
<css::datatransfer::DataFlavor
>
198 SAL_CALL
QtClipboardTransferable::getTransferDataFlavors()
200 css::uno::Sequence
<css::datatransfer::DataFlavor
> aSeq
;
201 auto* pSalInst(static_cast<QtInstance
*>(GetSalData()->m_pInstance
));
203 pSalInst
->RunInMainThread([&, this]() {
204 if (!hasInFlightChanged())
205 aSeq
= QtTransferable::getTransferDataFlavors();
211 QtClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
213 bool bIsSupported
= false;
214 auto* pSalInst(static_cast<QtInstance
*>(GetSalData()->m_pInstance
));
216 pSalInst
->RunInMainThread([&, this]() {
217 if (!hasInFlightChanged())
218 bIsSupported
= QtTransferable::isDataFlavorSupported(rFlavor
);
223 QtMimeData::QtMimeData(const css::uno::Reference
<css::datatransfer::XTransferable
>& xTrans
)
224 : m_aContents(xTrans
)
225 , m_bHaveNoCharset(false)
231 bool QtMimeData::deepCopy(QMimeData
** const pMimeCopy
) const
236 QMimeData
* pMimeData
= new QMimeData();
237 for (QString
& format
: formats())
239 QByteArray aData
= data(format
);
240 // Checking for custom MIME types
241 if (format
.startsWith("application/x-qt"))
243 // Retrieving true format name
244 int indexBegin
= format
.indexOf('"') + 1;
245 int indexEnd
= format
.indexOf('"', indexBegin
);
246 format
= format
.mid(indexBegin
, indexEnd
- indexBegin
);
248 pMimeData
->setData(format
, aData
);
251 *pMimeCopy
= pMimeData
;
255 QStringList
QtMimeData::formats() const
257 if (!m_aMimeTypeList
.isEmpty())
258 return m_aMimeTypeList
;
260 const css::uno::Sequence
<css::datatransfer::DataFlavor
> aFormats
261 = m_aContents
->getTransferDataFlavors();
263 bool bHaveUTF16
= false;
265 for (const auto& rFlavor
: aFormats
)
267 aList
<< toQString(rFlavor
.MimeType
);
268 lcl_textMimeInfo(rFlavor
.MimeType
, m_bHaveNoCharset
, bHaveUTF16
, m_bHaveUTF8
);
271 // we provide a locale encoded and a UTF-8 variant, if missing
272 if (m_bHaveNoCharset
|| bHaveUTF16
|| m_bHaveUTF8
)
274 // if there is a text representation from LO point of view, it'll be UTF-16
277 aList
<< QStringLiteral("text/plain;charset=utf-8");
278 if (!m_bHaveNoCharset
)
279 aList
<< QStringLiteral("text/plain");
282 m_aMimeTypeList
= aList
;
283 return m_aMimeTypeList
;
286 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
287 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QVariant::Type
) const
289 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QMetaType
) const
292 if (!hasFormat(mimeType
))
295 css::datatransfer::DataFlavor aFlavor
;
296 aFlavor
.MimeType
= toOUString(mimeType
);
297 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
299 bool bWantNoCharset
= false, bWantUTF16
= false, bWantUTF8
= false;
300 if (lcl_textMimeInfo(aFlavor
.MimeType
, bWantNoCharset
, bWantUTF16
, bWantUTF8
))
302 if ((bWantNoCharset
&& !m_bHaveNoCharset
) || (bWantUTF8
&& !m_bHaveUTF8
))
304 aFlavor
.MimeType
= "text/plain;charset=utf-16";
305 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
308 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
311 css::uno::Any aValue
;
315 // tdf#129809 take a reference in case m_aContents is replaced during this call
316 css::uno::Reference
<com::sun::star::datatransfer::XTransferable
> xCurrentContents(
318 aValue
= xCurrentContents
->getTransferData(aFlavor
);
324 QByteArray aByteArray
;
325 if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRING
)
332 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
333 aByteArray
= QByteArray(reinterpret_cast<const char*>(aUTF8String
.getStr()),
334 aUTF8String
.getLength());
336 else if (bWantNoCharset
)
338 OString
aLocaleString(OUStringToOString(aString
, osl_getThreadTextEncoding()));
339 aByteArray
= QByteArray(reinterpret_cast<const char*>(aLocaleString
.getStr()),
340 aLocaleString
.getLength());
344 aByteArray
= QByteArray(reinterpret_cast<const char*>(aString
.getStr()),
345 aString
.getLength() * 2);
348 return QVariant(toQString(aString
));
352 css::uno::Sequence
<sal_Int8
> aData
;
355 = QByteArray(reinterpret_cast<const char*>(aData
.getConstArray()), aData
.getLength());
357 return QVariant::fromValue(aByteArray
);
360 bool QtMimeData::hasFormat(const QString
& mimeType
) const { return formats().contains(mimeType
); }
362 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */