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>
15 #include <o3tl/string_view.hxx>
17 #include <QtWidgets/QApplication>
19 #include <QtInstance.hxx>
20 #include <QtTools.hxx>
24 static bool lcl_textMimeInfo(std::u16string_view rMimeString
, bool& bHaveNoCharset
,
25 bool& bHaveUTF16
, bool& bHaveUTF8
)
28 if (o3tl::getToken(rMimeString
, 0, ';', nIndex
) == u
"text/plain")
30 std::u16string_view
aToken(o3tl::getToken(rMimeString
, 0, ';', nIndex
));
31 if (aToken
== u
"charset=utf-16")
33 else if (aToken
== u
"charset=utf-8")
35 else if (aToken
.empty())
36 bHaveNoCharset
= true;
37 else // we just handle UTF-16 and UTF-8, everything else is "bytes"
44 QtTransferable::QtTransferable(const QMimeData
* pMimeData
)
45 : m_pMimeData(pMimeData
)
46 , m_bProvideUTF16FromOtherEncoding(false)
51 css::uno::Sequence
<css::datatransfer::DataFlavor
> SAL_CALL
QtTransferable::getTransferDataFlavors()
53 // it's just filled once, ever, so just try to get it without locking first
54 if (m_aMimeTypeSeq
.hasElements())
55 return m_aMimeTypeSeq
;
57 // better safe then sorry; preventing broken usage
58 // DnD should not be shared and Clipboard access runs in the GUI thread
59 osl::MutexGuard
aGuard(m_aMutex
);
60 if (m_aMimeTypeSeq
.hasElements())
61 return m_aMimeTypeSeq
;
63 QStringList
aFormatList(m_pMimeData
->formats());
64 // we might add the UTF-16 mime text variant later
65 const int nMimeTypeSeqSize
= aFormatList
.size() + 1;
66 bool bHaveNoCharset
= false, bHaveUTF16
= false, bHaveUTF8
= false;
67 css::uno::Sequence
<css::datatransfer::DataFlavor
> aMimeTypeSeq(nMimeTypeSeqSize
);
68 auto pMimeTypeSeq
= aMimeTypeSeq
.getArray();
70 css::datatransfer::DataFlavor aFlavor
;
71 int nMimeTypeCount
= 0;
73 for (const QString
& rMimeType
: aFormatList
)
75 // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
76 if (rMimeType
.indexOf('/') == -1)
79 // gtk3 thinks it is not well defined - skip too
80 if (rMimeType
== QStringLiteral("text/plain;charset=unicode"))
83 // LO doesn't like 'text/plain', so we have to provide UTF-16
84 bool bIsNoCharset
= false, bIsUTF16
= false, bIsUTF8
= false;
85 if (lcl_textMimeInfo(toOUString(rMimeType
), bIsNoCharset
, bIsUTF16
, bIsUTF8
))
87 bHaveNoCharset
|= bIsNoCharset
;
88 bHaveUTF16
|= bIsUTF16
;
91 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
93 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
96 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
98 aFlavor
.MimeType
= toOUString(rMimeType
);
99 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
100 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
104 m_bProvideUTF16FromOtherEncoding
= (bHaveNoCharset
|| bHaveUTF8
) && !bHaveUTF16
;
105 if (m_bProvideUTF16FromOtherEncoding
)
107 aFlavor
.MimeType
= "text/plain;charset=utf-16";
108 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
109 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
110 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
114 aMimeTypeSeq
.realloc(nMimeTypeCount
);
116 m_aMimeTypeSeq
= aMimeTypeSeq
;
117 return m_aMimeTypeSeq
;
121 QtTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
123 const auto aSeq
= getTransferDataFlavors();
124 return std::any_of(aSeq
.begin(), aSeq
.end(), [&](const css::datatransfer::DataFlavor
& aFlavor
) {
125 return rFlavor
.MimeType
== aFlavor
.MimeType
;
129 css::uno::Any SAL_CALL
QtTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
132 if (!isDataFlavorSupported(rFlavor
))
135 if (rFlavor
.MimeType
== "text/plain;charset=utf-16")
138 if (m_bProvideUTF16FromOtherEncoding
)
140 if (m_pMimeData
->hasFormat("text/plain;charset=utf-8"))
142 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain;charset=utf-8")));
143 aString
= OUString::fromUtf8(reinterpret_cast<const char*>(aByteData
.data()));
147 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain")));
148 aString
= OUString(reinterpret_cast<const char*>(aByteData
.data()),
149 aByteData
.size(), osl_getThreadTextEncoding());
154 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
155 aString
= OUString(reinterpret_cast<const sal_Unicode
*>(aByteData
.data()),
156 aByteData
.size() / 2);
162 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
163 css::uno::Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(aByteData
.data()),
171 QtClipboardTransferable::QtClipboardTransferable(const QClipboard::Mode aMode
,
172 const QMimeData
* pMimeData
)
173 : QtTransferable(pMimeData
)
178 bool QtClipboardTransferable::hasInFlightChanged() const
180 const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode
));
181 SAL_WARN_IF(bChanged
, "vcl.qt", "In flight clipboard change detected - broken clipboard read!");
185 css::uno::Any SAL_CALL
186 QtClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
189 auto* pSalInst(GetQtInstance());
191 pSalInst
->RunInMainThread([&, this]() {
192 if (!hasInFlightChanged())
193 aAny
= QtTransferable::getTransferData(rFlavor
);
198 css::uno::Sequence
<css::datatransfer::DataFlavor
>
199 SAL_CALL
QtClipboardTransferable::getTransferDataFlavors()
201 css::uno::Sequence
<css::datatransfer::DataFlavor
> aSeq
;
202 auto* pSalInst(GetQtInstance());
204 pSalInst
->RunInMainThread([&, this]() {
205 if (!hasInFlightChanged())
206 aSeq
= QtTransferable::getTransferDataFlavors();
212 QtClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
214 bool bIsSupported
= false;
215 auto* pSalInst(GetQtInstance());
217 pSalInst
->RunInMainThread([&, this]() {
218 if (!hasInFlightChanged())
219 bIsSupported
= QtTransferable::isDataFlavorSupported(rFlavor
);
224 QtMimeData::QtMimeData(const css::uno::Reference
<css::datatransfer::XTransferable
>& xTrans
)
225 : m_aContents(xTrans
)
226 , m_bHaveNoCharset(false)
232 bool QtMimeData::deepCopy(QMimeData
** const pMimeCopy
) const
237 QMimeData
* pMimeData
= new QMimeData();
238 for (QString
& format
: formats())
240 QByteArray aData
= data(format
);
241 // Checking for custom MIME types
242 if (format
.startsWith("application/x-qt"))
244 // Retrieving true format name
245 int indexBegin
= format
.indexOf('"') + 1;
246 int indexEnd
= format
.indexOf('"', indexBegin
);
247 format
= format
.mid(indexBegin
, indexEnd
- indexBegin
);
249 pMimeData
->setData(format
, aData
);
252 *pMimeCopy
= pMimeData
;
256 QStringList
QtMimeData::formats() const
258 if (!m_aMimeTypeList
.isEmpty())
259 return m_aMimeTypeList
;
261 const css::uno::Sequence
<css::datatransfer::DataFlavor
> aFormats
262 = m_aContents
->getTransferDataFlavors();
264 bool bHaveUTF16
= false;
266 for (const auto& rFlavor
: aFormats
)
268 aList
<< toQString(rFlavor
.MimeType
);
269 lcl_textMimeInfo(rFlavor
.MimeType
, m_bHaveNoCharset
, bHaveUTF16
, m_bHaveUTF8
);
272 // we provide a locale encoded and a UTF-8 variant, if missing
273 if (m_bHaveNoCharset
|| bHaveUTF16
|| m_bHaveUTF8
)
275 // if there is a text representation from LO point of view, it'll be UTF-16
278 aList
<< QStringLiteral("text/plain;charset=utf-8");
279 if (!m_bHaveNoCharset
)
280 aList
<< QStringLiteral("text/plain");
283 m_aMimeTypeList
= aList
;
284 return m_aMimeTypeList
;
287 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
288 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QVariant::Type
) const
290 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QMetaType
) const
293 if (!hasFormat(mimeType
))
296 css::datatransfer::DataFlavor aFlavor
;
297 aFlavor
.MimeType
= toOUString(mimeType
);
298 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
300 bool bWantNoCharset
= false, bWantUTF16
= false, bWantUTF8
= false;
301 if (lcl_textMimeInfo(aFlavor
.MimeType
, bWantNoCharset
, bWantUTF16
, bWantUTF8
))
303 if ((bWantNoCharset
&& !m_bHaveNoCharset
) || (bWantUTF8
&& !m_bHaveUTF8
))
305 aFlavor
.MimeType
= "text/plain;charset=utf-16";
306 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
309 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
312 css::uno::Any aValue
;
316 // tdf#129809 take a reference in case m_aContents is replaced during this call
317 css::uno::Reference
<com::sun::star::datatransfer::XTransferable
> xCurrentContents(
319 aValue
= xCurrentContents
->getTransferData(aFlavor
);
325 QByteArray aByteArray
;
326 if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRING
)
333 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
334 aByteArray
= QByteArray(aUTF8String
.getStr(), aUTF8String
.getLength());
336 else if (bWantNoCharset
)
338 OString
aLocaleString(OUStringToOString(aString
, osl_getThreadTextEncoding()));
339 aByteArray
= QByteArray(aLocaleString
.getStr(), aLocaleString
.getLength());
343 aByteArray
= QByteArray(reinterpret_cast<const char*>(aString
.getStr()),
344 aString
.getLength() * 2);
347 return QVariant(toQString(aString
));
351 css::uno::Sequence
<sal_Int8
> aData
;
354 = QByteArray(reinterpret_cast<const char*>(aData
.getConstArray()), aData
.getLength());
356 return QVariant::fromValue(aByteArray
);
359 bool QtMimeData::hasFormat(const QString
& mimeType
) const { return formats().contains(mimeType
); }
361 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */