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 <Qt5Transferable.hxx>
13 #include <comphelper/sequence.hxx>
14 #include <sal/log.hxx>
16 #include <QtWidgets/QApplication>
18 #include <Qt5Instance.hxx>
19 #include <Qt5Tools.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 Qt5Transferable::Qt5Transferable(const QMimeData
* pMimeData
)
44 : m_pMimeData(pMimeData
)
45 , m_bConvertFromLocale(false)
50 css::uno::Sequence
<css::datatransfer::DataFlavor
> SAL_CALL
Qt5Transferable::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;
66 css::uno::Sequence
<css::datatransfer::DataFlavor
> aMimeTypeSeq(nMimeTypeSeqSize
);
68 css::datatransfer::DataFlavor aFlavor
;
69 int nMimeTypeCount
= 0;
71 for (const QString
& rMimeType
: aFormatList
)
73 // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
74 if (rMimeType
.indexOf('/') == -1)
77 // gtk3 thinks it is not well defined - skip too
78 if (rMimeType
== QStringLiteral("text/plain;charset=unicode"))
81 // LO doesn't like 'text/plain', so we have to provide UTF-16
82 bool bIsNoCharset
= false, bIsUTF16
= false, bIsUTF8
= false;
83 if (lcl_textMimeInfo(toOUString(rMimeType
), bIsNoCharset
, bIsUTF16
, bIsUTF8
))
85 bHaveNoCharset
|= bIsNoCharset
;
86 bHaveUTF16
|= bIsUTF16
;
88 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
90 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
93 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
95 aFlavor
.MimeType
= toOUString(rMimeType
);
96 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
97 aMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
101 m_bConvertFromLocale
= bHaveNoCharset
&& !bHaveUTF16
;
102 if (m_bConvertFromLocale
)
104 aFlavor
.MimeType
= "text/plain;charset=utf-16";
105 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
106 assert(nMimeTypeCount
< nMimeTypeSeqSize
);
107 aMimeTypeSeq
[nMimeTypeCount
] = aFlavor
;
111 aMimeTypeSeq
.realloc(nMimeTypeCount
);
113 m_aMimeTypeSeq
= aMimeTypeSeq
;
114 return m_aMimeTypeSeq
;
118 Qt5Transferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
120 const auto aSeq
= getTransferDataFlavors();
121 return std::any_of(aSeq
.begin(), aSeq
.end(), [&](const css::datatransfer::DataFlavor
& aFlavor
) {
122 return rFlavor
.MimeType
== aFlavor
.MimeType
;
126 css::uno::Any SAL_CALL
127 Qt5Transferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
130 if (!isDataFlavorSupported(rFlavor
))
133 if (rFlavor
.MimeType
== "text/plain;charset=utf-16")
136 if (m_bConvertFromLocale
)
138 QByteArray
aByteData(m_pMimeData
->data(QStringLiteral("text/plain")));
139 aString
= OUString(reinterpret_cast<const sal_Char
*>(aByteData
.data()),
140 aByteData
.size(), osl_getThreadTextEncoding());
144 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
145 aString
= OUString(reinterpret_cast<const sal_Unicode
*>(aByteData
.data()),
146 aByteData
.size() / 2);
152 QByteArray
aByteData(m_pMimeData
->data(toQString(rFlavor
.MimeType
)));
153 css::uno::Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(aByteData
.data()),
161 Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode
,
162 const QMimeData
* pMimeData
)
163 : Qt5Transferable(pMimeData
)
168 bool Qt5ClipboardTransferable::hasInFlightChanged() const
170 const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode
));
171 SAL_WARN_IF(bChanged
, "vcl.qt5",
172 "In flight clipboard change detected - broken clipboard read!");
176 css::uno::Any SAL_CALL
177 Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor
& rFlavor
)
180 auto* pSalInst(static_cast<Qt5Instance
*>(GetSalData()->m_pInstance
));
182 pSalInst
->RunInMainThread([&, this]() {
183 if (!hasInFlightChanged())
184 aAny
= Qt5Transferable::getTransferData(rFlavor
);
189 css::uno::Sequence
<css::datatransfer::DataFlavor
>
190 SAL_CALL
Qt5ClipboardTransferable::getTransferDataFlavors()
192 css::uno::Sequence
<css::datatransfer::DataFlavor
> aSeq
;
193 auto* pSalInst(static_cast<Qt5Instance
*>(GetSalData()->m_pInstance
));
195 pSalInst
->RunInMainThread([&, this]() {
196 if (!hasInFlightChanged())
197 aSeq
= Qt5Transferable::getTransferDataFlavors();
203 Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
205 bool bIsSupported
= false;
206 auto* pSalInst(static_cast<Qt5Instance
*>(GetSalData()->m_pInstance
));
208 pSalInst
->RunInMainThread([&, this]() {
209 if (!hasInFlightChanged())
210 bIsSupported
= Qt5Transferable::isDataFlavorSupported(rFlavor
);
215 Qt5MimeData::Qt5MimeData(const css::uno::Reference
<css::datatransfer::XTransferable
>& xTrans
)
216 : m_aContents(xTrans
)
217 , m_bHaveNoCharset(false)
223 bool Qt5MimeData::deepCopy(QMimeData
** const pMimeCopy
) const
228 QMimeData
* pMimeData
= new QMimeData();
229 for (QString
& format
: formats())
231 QByteArray aData
= data(format
);
232 // Checking for custom MIME types
233 if (format
.startsWith("application/x-qt"))
235 // Retrieving true format name
236 int indexBegin
= format
.indexOf('"') + 1;
237 int indexEnd
= format
.indexOf('"', indexBegin
);
238 format
= format
.mid(indexBegin
, indexEnd
- indexBegin
);
240 pMimeData
->setData(format
, aData
);
243 *pMimeCopy
= pMimeData
;
247 QStringList
Qt5MimeData::formats() const
249 if (!m_aMimeTypeList
.isEmpty())
250 return m_aMimeTypeList
;
252 const css::uno::Sequence
<css::datatransfer::DataFlavor
> aFormats
253 = m_aContents
->getTransferDataFlavors();
255 bool bHaveUTF16
= false;
257 for (const auto& rFlavor
: aFormats
)
259 aList
<< toQString(rFlavor
.MimeType
);
260 lcl_textMimeInfo(rFlavor
.MimeType
, m_bHaveNoCharset
, bHaveUTF16
, m_bHaveUTF8
);
263 // we provide a locale encoded and a UTF-8 variant, if missing
264 if (m_bHaveNoCharset
|| bHaveUTF16
|| m_bHaveUTF8
)
266 // if there is a text representation from LO point of view, it'll be UTF-16
269 aList
<< QStringLiteral("text/plain;charset=utf-8");
270 if (!m_bHaveNoCharset
)
271 aList
<< QStringLiteral("text/plain");
274 m_aMimeTypeList
= aList
;
275 return m_aMimeTypeList
;
278 QVariant
Qt5MimeData::retrieveData(const QString
& mimeType
, QVariant::Type
) const
280 if (!hasFormat(mimeType
))
283 css::datatransfer::DataFlavor aFlavor
;
284 aFlavor
.MimeType
= toOUString(mimeType
);
285 aFlavor
.DataType
= cppu::UnoType
<css::uno::Sequence
<sal_Int8
>>::get();
287 bool bWantNoCharset
= false, bWantUTF16
= false, bWantUTF8
= false;
288 if (lcl_textMimeInfo(aFlavor
.MimeType
, bWantNoCharset
, bWantUTF16
, bWantUTF8
))
290 if ((bWantNoCharset
&& !m_bHaveNoCharset
) || (bWantUTF8
&& !m_bHaveUTF8
))
292 aFlavor
.MimeType
= "text/plain;charset=utf-16";
293 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
296 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
299 css::uno::Any aValue
;
303 aValue
= m_aContents
->getTransferData(aFlavor
);
309 QByteArray aByteArray
;
310 if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRING
)
317 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
318 aByteArray
= QByteArray(reinterpret_cast<const char*>(aUTF8String
.getStr()),
319 aUTF8String
.getLength());
321 else if (bWantNoCharset
)
323 OString
aLocaleString(OUStringToOString(aString
, osl_getThreadTextEncoding()));
324 aByteArray
= QByteArray(reinterpret_cast<const char*>(aLocaleString
.getStr()),
325 aLocaleString
.getLength());
328 return QVariant(toQString(aString
));
332 css::uno::Sequence
<sal_Int8
> aData
;
335 = QByteArray(reinterpret_cast<const char*>(aData
.getConstArray()), aData
.getLength());
337 return QVariant::fromValue(aByteArray
);
340 bool Qt5MimeData::hasFormat(const QString
& mimeType
) const { return formats().contains(mimeType
); }
342 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */