1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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>
12 #include <QtTransferable.moc>
14 #include <comphelper/sequence.hxx>
15 #include <sal/log.hxx>
16 #include <o3tl/string_view.hxx>
17 #include <tools/debug.hxx>
18 #include <vcl/qt/QtUtils.hxx>
20 #include <QtWidgets/QApplication>
22 #include <QtInstance.hxx>
23 #include <QtTools.hxx>
27 static bool lcl_textMimeInfo(std::u16string_view rMimeString
, bool& bHaveNoCharset
,
28 bool& bHaveUTF16
, bool& bHaveUTF8
)
31 if (o3tl::getToken(rMimeString
, 0, ';', nIndex
) == u
"text/plain")
33 std::u16string_view
aToken(o3tl::getToken(rMimeString
, 0, ';', nIndex
));
34 if (aToken
== u
"charset=utf-16")
36 else if (aToken
== u
"charset=utf-8")
38 else if (aToken
.empty())
39 bHaveNoCharset
= true;
40 else // we just handle UTF-16 and UTF-8, everything else is "bytes"
47 QtTransferable::QtTransferable(const QMimeData
* pMimeData
)
48 : m_pMimeData(pMimeData
)
53 css::uno::Sequence
<css::datatransfer::DataFlavor
> SAL_CALL
QtTransferable::getTransferDataFlavors()
56 return css::uno::Sequence
<css::datatransfer::DataFlavor
>();
58 QStringList
aFormatList(m_pMimeData
->formats());
59 // we might add the UTF-16 mime text variant later
60 const int nMimeTypeSeqSize
= aFormatList
.size() + 1;
61 bool bHaveNoCharset
= false, bHaveUTF16
= false, bHaveUTF8
= false;
62 css::uno::Sequence
<css::datatransfer::DataFlavor
> aMimeTypeSeq(nMimeTypeSeqSize
);
63 auto pMimeTypeSeq
= aMimeTypeSeq
.getArray();
65 css::datatransfer::DataFlavor aFlavor
;
66 int nMimeTypeCount
= 0;
68 for (const QString
& rMimeType
: aFormatList
)
70 // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
71 if (rMimeType
.indexOf('/') == -1)
74 // gtk3 thinks it is not well defined - skip too
75 if (rMimeType
== QStringLiteral("text/plain;charset=unicode"))
78 // LO doesn't like 'text/plain', so we have to provide UTF-16
79 bool bIsNoCharset
= false, bIsUTF16
= false, bIsUTF8
= false;
80 if (lcl_textMimeInfo(toOUString(rMimeType
), bIsNoCharset
, bIsUTF16
, bIsUTF8
))
82 bHaveNoCharset
|= bIsNoCharset
;
83 bHaveUTF16
|= bIsUTF16
;
86 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
88 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
91 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
93 aFlavor
.MimeType
= toOUString(rMimeType
);
94 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
95 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
99 // in case of text/plain data, but no UTF-16 encoded one,
100 // QtTransferable::getTransferData converts from existing encoding to UTF-16
101 const bool bProvideUTF16FromOtherEncoding
= (bHaveNoCharset
|| bHaveUTF8
) && !bHaveUTF16
;
102 if (bProvideUTF16FromOtherEncoding
)
104 aFlavor
.MimeType
= "text/plain;charset=utf-16";
105 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
106 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
107 pMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
111 aMimeTypeSeq
.realloc(nMimeTypeCount
);
117 QtTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
119 const auto aSeq
= getTransferDataFlavors();
120 return std::any_of(aSeq
.begin(), aSeq
.end(), [&](const css::datatransfer::DataFlavor
& aFlavor
) {
121 return rFlavor
.MimeType
== aFlavor
.MimeType
;
125 css::uno::Any SAL_CALL
QtTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
128 if (!isDataFlavorSupported(rFlavor
))
131 if (rFlavor
.MimeType
== "text/plain;charset=utf-16")
134 // use existing UTF-16 encoded text/plain or convert to UTF-16 as needed
135 if (m_pMimeData
->hasFormat("text/plain;charset=utf-16"))
137 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
138 aString
= OUString(reinterpret_cast<const sal_Unicode
*>(aByteData
.data()),
139 aByteData
.size() / 2);
141 else if (m_pMimeData
->hasFormat("text/plain;charset=utf-8"))
143 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain;charset=utf-8")));
144 aString
= OUString::fromUtf8(reinterpret_cast<const char*>(aByteData
.data()));
148 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain")));
149 aString
= OUString(reinterpret_cast<const char*>(aByteData
.data()), aByteData
.size(),
150 osl_getThreadTextEncoding());
156 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
157 css::uno::Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(aByteData
.data()),
165 QtClipboardTransferable::QtClipboardTransferable(const QClipboard::Mode aMode
,
166 const QMimeData
* pMimeData
)
167 : QtTransferable(pMimeData
)
172 void QtClipboardTransferable::ensureConsistencyWithSystemClipboard()
174 const QMimeData
* pCurrentClipboardData
= QApplication::clipboard()->mimeData(m_aMode
);
175 if (mimeData() != pCurrentClipboardData
)
177 SAL_WARN("vcl.qt", "In flight clipboard change detected - updating mime data with current "
178 "clipboard contents.");
179 DBG_TESTSOLARMUTEX();
180 setMimeData(pCurrentClipboardData
);
184 bool QtClipboardTransferable::hasMimeData(const QMimeData
* pMimeData
) const
186 SolarMutexGuard aGuard
;
187 return QtTransferable::mimeData() == pMimeData
;
190 css::uno::Any SAL_CALL
191 QtClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
195 GetQtInstance().RunInMainThread([&, this]() {
196 ensureConsistencyWithSystemClipboard();
197 aAny
= QtTransferable::getTransferData(rFlavor
);
202 css::uno::Sequence
<css::datatransfer::DataFlavor
>
203 SAL_CALL
QtClipboardTransferable::getTransferDataFlavors()
205 css::uno::Sequence
<css::datatransfer::DataFlavor
> aSeq
;
207 GetQtInstance().RunInMainThread([&, this]() {
208 ensureConsistencyWithSystemClipboard();
209 aSeq
= QtTransferable::getTransferDataFlavors();
215 QtClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
217 bool bIsSupported
= false;
219 GetQtInstance().RunInMainThread([&, this]() {
220 ensureConsistencyWithSystemClipboard();
221 bIsSupported
= QtTransferable::isDataFlavorSupported(rFlavor
);
226 QtMimeData::QtMimeData(const css::uno::Reference
<css::datatransfer::XTransferable
>& xTrans
)
227 : m_aContents(xTrans
)
228 , m_bHaveNoCharset(false)
234 bool QtMimeData::deepCopy(QMimeData
** const pMimeCopy
) const
239 QMimeData
* pMimeData
= new QMimeData();
240 for (QString
& format
: formats())
242 QByteArray aData
= data(format
);
243 // Checking for custom MIME types
244 if (format
.startsWith("application/x-qt"))
246 // Retrieving true format name
247 int indexBegin
= format
.indexOf('"') + 1;
248 int indexEnd
= format
.indexOf('"', indexBegin
);
249 format
= format
.mid(indexBegin
, indexEnd
- indexBegin
);
251 pMimeData
->setData(format
, aData
);
254 *pMimeCopy
= pMimeData
;
258 QStringList
QtMimeData::formats() const
260 if (!m_aMimeTypeList
.isEmpty())
261 return m_aMimeTypeList
;
263 const css::uno::Sequence
<css::datatransfer::DataFlavor
> aFormats
264 = m_aContents
->getTransferDataFlavors();
266 bool bHaveUTF16
= false;
268 for (const auto& rFlavor
: aFormats
)
270 aList
<< toQString(rFlavor
.MimeType
);
271 lcl_textMimeInfo(rFlavor
.MimeType
, m_bHaveNoCharset
, bHaveUTF16
, m_bHaveUTF8
);
274 // we provide a locale encoded and a UTF-8 variant, if missing
275 if (m_bHaveNoCharset
|| bHaveUTF16
|| m_bHaveUTF8
)
277 // if there is a text representation from LO point of view, it'll be UTF-16
280 aList
<< QStringLiteral("text/plain;charset=utf-8");
281 if (!m_bHaveNoCharset
)
282 aList
<< QStringLiteral("text/plain");
285 m_aMimeTypeList
= aList
;
286 return m_aMimeTypeList
;
289 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
290 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QVariant::Type
) const
292 QVariant
QtMimeData::retrieveData(const QString
& mimeType
, QMetaType
) const
295 if (!hasFormat(mimeType
))
298 css::datatransfer::DataFlavor aFlavor
;
299 aFlavor
.MimeType
= toOUString(mimeType
);
300 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
302 bool bWantNoCharset
= false, bWantUTF16
= false, bWantUTF8
= false;
303 if (lcl_textMimeInfo(aFlavor
.MimeType
, bWantNoCharset
, bWantUTF16
, bWantUTF8
))
305 if ((bWantNoCharset
&& !m_bHaveNoCharset
) || (bWantUTF8
&& !m_bHaveUTF8
))
307 aFlavor
.MimeType
= "text/plain;charset=utf-16";
308 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
311 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
314 css::uno::Any aValue
;
318 // tdf#129809 take a reference in case m_aContents is replaced during this call
319 css::uno::Reference
<css::datatransfer::XTransferable
> xCurrentContents(m_aContents
);
320 aValue
= xCurrentContents
->getTransferData(aFlavor
);
326 QByteArray aByteArray
;
327 if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRING
)
334 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
335 aByteArray
= QByteArray(aUTF8String
.getStr(), aUTF8String
.getLength());
337 else if (bWantNoCharset
)
339 OString
aLocaleString(OUStringToOString(aString
, osl_getThreadTextEncoding()));
340 aByteArray
= QByteArray(aLocaleString
.getStr(), 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 cinoptions=b1,g0,N-s cinkeys+=0=break: */