tdf#122239 Qt5 implement lazy clipboard handling
This changes the Qt5Clipboard to a lazy loading one, which will
just deliver data on read requests. This fixes not only the
PRIMARY selection problems with Writer, but will generally speed
up C'n'P, inside LO, because the data has not to be copied or
transferred via QMimeData.
This is mainly done by implementing the "mirror" interface of the
Qt5Transferable, the Qt5MimeData using the retrieveData override.
To prevent clipboard loss on shutdown, this sets a deep copied
QMimeData of the current XTransferable, to make it persistent.
This code explicitly doesn't use any of the QMimeData convenience
functions and relies completely on LO's string handling, so we
won't mix in eventual Qt bugs; all bugs are ours...
Change-Id: I43d92a95df8fcac88dc41b00021cea0b5f040413
Reviewed-on: https://gerrit.libreoffice.org/73288
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
(cherry picked from commit bcca1cf28cbd6c961d59bd8b8a8e58184dfc3823)
Reviewed-on: https://gerrit.libreoffice.org/74206
diff --git a/vcl/inc/qt5/Qt5Clipboard.hxx b/vcl/inc/qt5/Qt5Clipboard.hxx
index 2c2bb93..93ad36a 100644
--- a/vcl/inc/qt5/Qt5Clipboard.hxx
+++ b/vcl/inc/qt5/Qt5Clipboard.hxx
@@ -20,7 +20,14 @@
#include <QtGui/QClipboard>
class Qt5Clipboard
/**
* This implementation has two main functions, which handle the clipboard content:
* the XClipboard::setContent function and the QClipboard::change signal handler.
*
* The first just sets the respective clipboard to the expected content from LO,
* the latter will handle any reported changes.
**/
class Qt5Clipboard final
: public QObject,
public cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
css::datatransfer::clipboard::XFlushableClipboard,
@@ -29,61 +36,50 @@ class Qt5Clipboard
Q_OBJECT
osl::Mutex m_aMutex;
const OUString m_aClipboardName;
const QClipboard::Mode m_aClipboardMode;
// if not empty, this holds the setContents provided XTransferable or a Qt5ClipboardTransferable
css::uno::Reference<css::datatransfer::XTransferable> m_aContents;
// the owner of the current contents, which must be informed on content change
css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> m_aListeners;
OUString m_aClipboardName;
QClipboard::Mode m_aClipboardMode;
// custom MIME type to detect whether clipboard content was added by self or externally
const QString m_sMimeTypeUuid = "application/x-libreoffice-clipboard-uuid";
const QByteArray m_aUuid;
static bool isOwner(const QClipboard::Mode aMode);
static bool isSupported(const QClipboard::Mode aMode);
explicit Qt5Clipboard(const OUString& aModeString, const QClipboard::Mode aMode);
private Q_SLOTS:
void handleClipboardChange(QClipboard::Mode mode);
void handleChanged(QClipboard::Mode mode);
public:
explicit Qt5Clipboard(const OUString& aModeString);
virtual ~Qt5Clipboard() override;
// factory function to construct only valid Qt5Clipboard objects by name
static css::uno::Reference<css::uno::XInterface> create(const OUString& aModeString);
/*
* XServiceInfo
*/
// XServiceInfo
virtual OUString SAL_CALL getImplementationName() override;
virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
/*
* XClipboard
*/
// XClipboard
virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
virtual void SAL_CALL setContents(
const css::uno::Reference<css::datatransfer::XTransferable>& xTrans,
const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
override;
virtual OUString SAL_CALL getName() override;
/*
* XClipboardEx
*/
// XClipboardEx
virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
/*
* XFlushableClipboard
*/
// XFlushableClipboard
virtual void SAL_CALL flushClipboard() override;
/*
* XClipboardNotifier
*/
// XClipboardNotifier
virtual void SAL_CALL addClipboardListener(
const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
override;
virtual void SAL_CALL removeClipboardListener(
const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
override;
diff --git a/vcl/inc/qt5/Qt5Transferable.hxx b/vcl/inc/qt5/Qt5Transferable.hxx
index edc1490..f36216e 100644
--- a/vcl/inc/qt5/Qt5Transferable.hxx
+++ b/vcl/inc/qt5/Qt5Transferable.hxx
@@ -13,44 +13,110 @@
#include <cppuhelper/compbase.hxx>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <QtCore/QMimeData>
#include <QtCore/QStringList>
#include <QtGui/QClipboard>
/**
* Abstract XTransferable used for clipboard and D'n'D transfers
* Qt5Transferable classes are used to read QMimeData via the XTransferable
* interface. All the functionality is already implemented in the Qt5Transferable.
*
* The specialisations map to the two users, which provide QMimeData: the Clipboard
* and the Drag'n'Drop functionality.
*
* LO itself seem to just accept "text/plain;charset=utf-16", so it relies on the
* backend to convert to this charset, but still offers "text/plain" itself.
*
* It's the "mirror" interface of the Qt5MimeData, which is defined below.
**/
class Qt5Transferable : public cppu::WeakImplHelper<css::datatransfer::XTransferable>
{
Qt5Transferable() = delete;
Qt5Transferable(const Qt5Transferable&) = delete;
protected:
const QMimeData* m_pMimeData;
Qt5Transferable(const QMimeData* pMimeData);
std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector();
osl::Mutex m_aMutex;
bool m_bConvertFromLocale;
css::uno::Sequence<css::datatransfer::DataFlavor> m_aMimeTypeSeq;
public:
~Qt5Transferable() override;
Qt5Transferable(const QMimeData* pMimeData);
const QMimeData* mimeData() const { return m_pMimeData; }
css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
};
/**
* The QClipboard's QMimeData is volatile. As written in the QClipboard::mimeData
* documentation, "the pointer returned might become invalidated when the contents
* of the clipboard changes". Therefore it can just be accessed reliably inside
* the QClipboard's object thread, which is the QApplication's thread, so all of
* the access has to go through RunInMainThread().
*
* If we detect a QMimeData change, we simply drop reporting any content. In theory
* we can recover in the case where there hadn't been any calls of the XTransferable
* interface, but currently we don't. But we ensure to never report mixed content,
* so we'll just cease operation on QMimeData change.
**/
class Qt5ClipboardTransferable final : public Qt5Transferable
{
public:
explicit Qt5ClipboardTransferable(QClipboard::Mode aMode);
~Qt5ClipboardTransferable() override;
// to detect in-flight QMimeData changes
const QClipboard::Mode m_aMode;
bool hasInFlightChanged() const;
public:
explicit Qt5ClipboardTransferable(const QClipboard::Mode aMode, const QMimeData* pMimeData);
// these are the same then Qt5Transferable, except they go through RunInMainThread
css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor) override;
css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
};
class Qt5DnDTransferable final : public Qt5Transferable
{
public:
Qt5DnDTransferable(const QMimeData* pMimeData);
/**
* Convenience typedef for better code readability
*
* This just uses the QMimeData provided by the QWidgets D'n'D events.
**/
typedef Qt5Transferable Qt5DnDTransferable;
css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
/**
* A lazy loading QMimeData for XTransferable reads
*
* This is an interface class to make a XTransferable read accessible as a
* QMimeData. The mime data is just stored inside the XTransferable, never
* in the QMimeData itself! It's objects are just used for QClipboard to read
* the XTransferable data.
*
* Like XTransferable itself, this class should be considered an immutable
* container for mime data. There is no need to ever set any of its data.
*
* LO will offer at least UTF-16, if there is a viable text representation.
* If LO misses to offer an UTF-8 or a locale encoded string, these objects
* will offer them themselves and convert from UTF-16 on demand.
*
* It's the "mirror" interface of the Qt5Transferable.
**/
class Qt5MimeData final : public QMimeData
{
friend class Qt5ClipboardTransferable;
const css::uno::Reference<css::datatransfer::XTransferable> m_aContents;
mutable bool m_bHaveNoCharset; // = uses the locale charset
mutable bool m_bHaveUTF8;
mutable QStringList m_aMimeTypeList;
QVariant retrieveData(const QString& mimeType, QVariant::Type type) const override;
public:
explicit Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& aContents);
bool hasFormat(const QString& mimeType) const override;
QStringList formats() const override;
bool deepCopy(QMimeData** const) const;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Clipboard.cxx b/vcl/qt5/Qt5Clipboard.cxx
index 2fc97ca..50126e6 100644
--- a/vcl/qt5/Qt5Clipboard.cxx
+++ b/vcl/qt5/Qt5Clipboard.cxx
@@ -8,85 +8,73 @@
*
*/
#include <comphelper/solarmutex.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <vcl/svapp.hxx>
#include <sal/log.hxx>
#include <QtCore/QMimeData>
#include <QtCore/QUuid>
#include <QtWidgets/QApplication>
#include <Qt5Clipboard.hxx>
#include <Qt5Clipboard.moc>
#include <cppuhelper/supportsservice.hxx>
#include <sal/log.hxx>
#include <QtWidgets/QApplication>
#include <Qt5Instance.hxx>
#include <Qt5Transferable.hxx>
#include <Qt5Tools.hxx>
#include <cassert>
#include <map>
using namespace com::sun::star;
namespace
Qt5Clipboard::Qt5Clipboard(const OUString& aModeString, const QClipboard::Mode aMode)
: cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
css::datatransfer::clipboard::XFlushableClipboard,
XServiceInfo>(m_aMutex)
, m_aClipboardName(aModeString)
, m_aClipboardMode(aMode)
{
QClipboard::Mode getClipboardTypeFromName(const OUString& aString)
assert(isSupported(m_aClipboardMode));
// DirectConnection guarantess the changed slot runs in the same thread as the QClipboard
connect(QApplication::clipboard(), &QClipboard::changed, this, &Qt5Clipboard::handleChanged,
Qt::DirectConnection);
}
css::uno::Reference<css::uno::XInterface> Qt5Clipboard::create(const OUString& aModeString)
{
static const std::map<OUString, QClipboard::Mode> aNameToClipboardMap
= { { "CLIPBOARD", QClipboard::Clipboard }, { "PRIMARY", QClipboard::Selection } };
// default to QClipboard::Clipboard as fallback
QClipboard::Mode aMode = QClipboard::Clipboard;
assert(QApplication::clipboard()->thread() == qApp->thread());
auto iter = aNameToClipboardMap.find(aString);
if (iter != aNameToClipboardMap.end())
aMode = iter->second;
else
SAL_WARN("vcl.qt5", "Unrecognized clipboard type \""
<< aString << "\"; falling back to QClipboard::Clipboard");
return aMode;
}
void lcl_peekFormats(const css::uno::Sequence<css::datatransfer::DataFlavor>& rFormats,
bool& bHasHtml, bool& bHasImage)
{
for (int i = 0; i < rFormats.getLength(); ++i)
{
const css::datatransfer::DataFlavor& rFlavor = rFormats[i];
if (rFlavor.MimeType == "text/html")
bHasHtml = true;
else if (rFlavor.MimeType.startsWith("image"))
bHasImage = true;
}
}
}
Qt5Clipboard::Qt5Clipboard(const OUString& aModeString)
: cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
datatransfer::clipboard::XFlushableClipboard, XServiceInfo>(
m_aMutex)
, m_aClipboardName(aModeString)
, m_aClipboardMode(getClipboardTypeFromName(aModeString))
, m_aUuid(QUuid::createUuid().toByteArray())
{
connect(QApplication::clipboard(), &QClipboard::changed, this,
&Qt5Clipboard::handleClipboardChange, Qt::DirectConnection);
auto iter = aNameToClipboardMap.find(aModeString);
if (iter != aNameToClipboardMap.end() && isSupported(iter->second))
return static_cast<cppu::OWeakObject*>(new Qt5Clipboard(aModeString, iter->second));
SAL_WARN("vcl.qt5", "Ignoring unrecognized clipboard type: '" << aModeString << "'");
return css::uno::Reference<css::uno::XInterface>();
}
void Qt5Clipboard::flushClipboard()
{
SolarMutexGuard aGuard;
return;
}
auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
SolarMutexGuard g;
pSalInst->RunInMainThread([&, this]() {
if (!isOwner(m_aClipboardMode))
return;
Qt5Clipboard::~Qt5Clipboard() {}
QClipboard* pClipboard = QApplication::clipboard();
const Qt5MimeData* pQt5MimeData
= dynamic_cast<const Qt5MimeData*>(pClipboard->mimeData(m_aClipboardMode));
assert(pQt5MimeData);
QMimeData* pMimeCopy = nullptr;
if (pQt5MimeData && pQt5MimeData->deepCopy(&pMimeCopy))
pClipboard->setMimeData(pMimeCopy, m_aClipboardMode);
});
}
OUString Qt5Clipboard::getImplementationName()
{
return OUString("com.sun.star.datatransfer.Qt5Clipboard");
}
uno::Sequence<OUString> Qt5Clipboard::getSupportedServiceNames()
css::uno::Sequence<OUString> Qt5Clipboard::getSupportedServiceNames()
{
return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
}
@@ -96,162 +84,55 @@ sal_Bool Qt5Clipboard::supportsService(const OUString& ServiceName)
return cppu::supportsService(this, ServiceName);
}
uno::Reference<css::datatransfer::XTransferable> Qt5Clipboard::getContents()
css::uno::Reference<css::datatransfer::XTransferable> Qt5Clipboard::getContents()
{
if (!m_aContents.is())
m_aContents = new Qt5ClipboardTransferable(m_aClipboardMode);
osl::MutexGuard aGuard(m_aMutex);
// if we're the owner, we have the XTransferable from setContents
if (isOwner(m_aClipboardMode))
return m_aContents;
// check if we can still use the shared Qt5ClipboardTransferable
const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode);
if (m_aContents.is())
{
const auto* pTrans = dynamic_cast<Qt5ClipboardTransferable*>(m_aContents.get());
assert(pTrans);
if (pTrans && pTrans->mimeData() == pMimeData)
return m_aContents;
}
m_aContents = new Qt5ClipboardTransferable(m_aClipboardMode, pMimeData);
return m_aContents;
}
void Qt5Clipboard::setContents(
const uno::Reference<css::datatransfer::XTransferable>& xTrans,
const uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
const css::uno::Reference<css::datatransfer::XTransferable>& xTrans,
const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
{
// it's actually possible to get a non-empty xTrans and an empty xClipboardOwner!
osl::ClearableMutexGuard aGuard(m_aMutex);
uno::Reference<datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
uno::Reference<datatransfer::XTransferable> xOldContents(m_aContents);
css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
m_aContents = xTrans;
m_aOwner = xClipboardOwner;
std::vector<uno::Reference<datatransfer::clipboard::XClipboardListener>> aListeners(
m_aListeners);
datatransfer::clipboard::ClipboardEvent aEv;
QClipboard* clipboard = QApplication::clipboard();
switch (m_aClipboardMode)
{
case QClipboard::Selection:
if (!clipboard->supportsSelection())
{
return;
}
break;
case QClipboard::FindBuffer:
if (!clipboard->supportsFindBuffer())
{
return;
}
break;
case QClipboard::Clipboard:
default:
break;
}
// these will trigger QClipboard::changed / handleChanged
if (m_aContents.is())
QApplication::clipboard()->setMimeData(new Qt5MimeData(m_aContents), m_aClipboardMode);
else
{
css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
= xTrans->getTransferDataFlavors();
// Do not add non-text formats for the selection buffer,
// I don't think that one is ever used for anything else
// besides text and this gets called whenever something
// in LO gets selected (which may be e.g. an entire Calc sheet).
bool bHasHtml = false, bHasImage = false;
if (m_aClipboardMode != QClipboard::Selection)
lcl_peekFormats(aFormats, bHasHtml, bHasImage);
std::unique_ptr<QMimeData> pMimeData(new QMimeData);
// Add html data if present
if (bHasHtml)
{
css::datatransfer::DataFlavor aFlavor;
aFlavor.MimeType = "text/html";
aFlavor.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
uno::Any aValue;
try
{
aValue = xTrans->getTransferData(aFlavor);
}
catch (...)
{
}
if (aValue.getValueType() == cppu::UnoType<uno::Sequence<sal_Int8>>::get())
{
uno::Sequence<sal_Int8> aData;
aValue >>= aData;
OUString aHtmlAsString(reinterpret_cast<const char*>(aData.getConstArray()),
aData.getLength(), RTL_TEXTENCODING_UTF8);
pMimeData->setHtml(toQString(aHtmlAsString));
}
}
// Add image data if present
if (bHasImage)
{
css::datatransfer::DataFlavor aFlavor;
//FIXME: other image formats?
aFlavor.MimeType = "image/png";
aFlavor.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
uno::Any aValue;
try
{
aValue = xTrans->getTransferData(aFlavor);
}
catch (...)
{
}
if (aValue.getValueType() == cppu::UnoType<uno::Sequence<sal_Int8>>::get())
{
uno::Sequence<sal_Int8> aData;
aValue >>= aData;
QImage image;
image.loadFromData(reinterpret_cast<const uchar*>(aData.getConstArray()),
aData.getLength());
pMimeData->setImageData(image);
}
}
// Add text data
// TODO: consider checking if text of suitable type is present
{
css::datatransfer::DataFlavor aFlavor;
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
uno::Any aValue;
try
{
aValue = xTrans->getTransferData(aFlavor);
}
catch (...)
{
}
if (aValue.getValueTypeClass() == uno::TypeClass_STRING)
{
OUString aString;
aValue >>= aString;
pMimeData->setText(toQString(aString));
}
}
// set value for custom MIME type to indicate that content was added by this clipboard
pMimeData->setData(m_sMimeTypeUuid, m_aUuid);
clipboard->setMimeData(pMimeData.release(), m_aClipboardMode);
assert(!m_aOwner.is());
QApplication::clipboard()->clear(m_aClipboardMode);
}
aEv.Contents = getContents();
aGuard.clear();
// we have to notify only an owner change, since handleChanged can't
// access the previous owner anymore and can just handle lost ownership.
if (xOldOwner.is() && xOldOwner != xClipboardOwner)
xOldOwner->lostOwnership(this, xOldContents);
for (auto const& listener : aListeners)
{
listener->changedContents(aEv);
}
}
OUString Qt5Clipboard::getName() { return m_aClipboardName; }
@@ -259,32 +140,85 @@ OUString Qt5Clipboard::getName() { return m_aClipboardName; }
sal_Int8 Qt5Clipboard::getRenderingCapabilities() { return 0; }
void Qt5Clipboard::addClipboardListener(
const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener)
const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
{
osl::ClearableMutexGuard aGuard(m_aMutex);
osl::MutexGuard aGuard(m_aMutex);
m_aListeners.push_back(listener);
}
void Qt5Clipboard::removeClipboardListener(
const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener)
const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
{
osl::ClearableMutexGuard aGuard(m_aMutex);
osl::MutexGuard aGuard(m_aMutex);
m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener),
m_aListeners.end());
}
void Qt5Clipboard::handleClipboardChange(QClipboard::Mode aMode)
void Qt5Clipboard::handleChanged(QClipboard::Mode aMode)
{
// if system clipboard content has changed and current content was not created by
// this clipboard itself, clear the own current content
// (e.g. to take into account clipboard updates from other applications)
if (aMode == m_aClipboardMode
&& QApplication::clipboard()->mimeData(aMode)->data(m_sMimeTypeUuid) != m_aUuid)
if (aMode != m_aClipboardMode)
return;
osl::ClearableMutexGuard aGuard(m_aMutex);
css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
// ownership change from LO POV is handled in setContents
const bool bLostOwnership = !isOwner(m_aClipboardMode);
if (bLostOwnership)
{
m_aContents.clear();
m_aOwner.clear();
}
std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> aListeners(
m_aListeners);
css::datatransfer::clipboard::ClipboardEvent aEv;
aEv.Contents = getContents();
aGuard.clear();
if (bLostOwnership && xOldOwner.is())
xOldOwner->lostOwnership(this, xOldContents);
for (auto const& listener : aListeners)
listener->changedContents(aEv);
}
bool Qt5Clipboard::isSupported(const QClipboard::Mode aMode)
{
const QClipboard* pClipboard = QApplication::clipboard();
switch (aMode)
{
case QClipboard::Selection:
return pClipboard->supportsSelection();
case QClipboard::FindBuffer:
return pClipboard->supportsFindBuffer();
case QClipboard::Clipboard:
return true;
}
return false;
}
bool Qt5Clipboard::isOwner(const QClipboard::Mode aMode)
{
if (!isSupported(aMode))
return false;
const QClipboard* pClipboard = QApplication::clipboard();
switch (aMode)
{
case QClipboard::Selection:
return pClipboard->ownsSelection();
case QClipboard::FindBuffer:
return pClipboard->ownsFindBuffer();
case QClipboard::Clipboard:
return pClipboard->ownsClipboard();
}
return false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Instance.cxx b/vcl/qt5/Qt5Instance.cxx
index d260536..26e2e57 100644
--- a/vcl/qt5/Qt5Instance.cxx
+++ b/vcl/qt5/Qt5Instance.cxx
@@ -442,15 +442,18 @@ Qt5Instance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments)
css::uno::Reference<css::uno::XInterface>(), -1);
}
// This could also use RunInMain, but SolarMutexGuard is enough
// since at this point we're not accessing the clipbord, just get the
// accessor to the clipboard.
SolarMutexGuard aGuard;
auto it = m_aClipboards.find(sel);
if (it != m_aClipboards.end())
{
return it->second;
}
css::uno::Reference<css::uno::XInterface> xClipboard(
static_cast<cppu::OWeakObject*>(new Qt5Clipboard(sel)));
m_aClipboards[sel] = xClipboard;
css::uno::Reference<css::uno::XInterface> xClipboard = Qt5Clipboard::create(sel);
if (xClipboard.is())
m_aClipboards[sel] = xClipboard;
return xClipboard;
}
diff --git a/vcl/qt5/Qt5Transferable.cxx b/vcl/qt5/Qt5Transferable.cxx
index a78ff8b..65e2ad5 100644
--- a/vcl/qt5/Qt5Transferable.cxx
+++ b/vcl/qt5/Qt5Transferable.cxx
@@ -10,158 +10,333 @@
#include <Qt5Transferable.hxx>
#include <comphelper/solarmutex.hxx>
#include <comphelper/sequence.hxx>
#include <sal/log.hxx>
#include <QtCore/QBuffer>
#include <QtCore/QMimeData>
#include <QtCore/QUrl>
#include <QtWidgets/QApplication>
#include <Qt5Clipboard.hxx>
#include <Qt5Instance.hxx>
#include <Qt5Tools.hxx>
using namespace com::sun::star;
#include <cassert>
static bool lcl_textMimeInfo(const OUString& rMimeString, bool& bHaveNoCharset, bool& bHaveUTF16,
bool& bHaveUTF8)
{
sal_Int32 nIndex = 0;
if (rMimeString.getToken(0, ';', nIndex) == "text/plain")
{
OUString aToken(rMimeString.getToken(0, ';', nIndex));
if (aToken == "charset=utf-16")
bHaveUTF16 = true;
else if (aToken == "charset=utf-8")
bHaveUTF8 = true;
else if (aToken.isEmpty())
bHaveNoCharset = true;
else // we just handle UTF-16 and UTF-8, everything else is "bytes"
return false;
return true;
}
return false;
}
Qt5Transferable::Qt5Transferable(const QMimeData* pMimeData)
: m_pMimeData(pMimeData)
, m_bConvertFromLocale(false)
{
assert(pMimeData);
}
Qt5Transferable::~Qt5Transferable() {}
std::vector<css::datatransfer::DataFlavor> Qt5Transferable::getTransferDataFlavorsAsVector()
css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::getTransferDataFlavors()
{
std::vector<css::datatransfer::DataFlavor> aVector;
assert(m_pMimeData);
if (!m_pMimeData)
return aVector;
// it's just filled once, ever, so just try to get it without locking first
if (m_aMimeTypeSeq.hasElements())
return m_aMimeTypeSeq;
// better safe then sorry; preventing broken usage
// DnD should not be shared and Clipboard access runs in the GUI thread
osl::MutexGuard aGuard(m_aMutex);
if (m_aMimeTypeSeq.hasElements())
return m_aMimeTypeSeq;
QStringList aFormatList(m_pMimeData->formats());
// we might add the UTF-16 mime text variant later
const int nMimeTypeSeqSize = aFormatList.size() + 1;
bool bHaveNoCharset = false, bHaveUTF16 = false;
css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
css::datatransfer::DataFlavor aFlavor;
for (QString& rMimeType : m_pMimeData->formats())
int nMimeTypeCount = 0;
for (const QString& rMimeType : aFormatList)
{
// filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
if (rMimeType.indexOf('/') == -1)
continue;
aFlavor.MimeType = toOUString(rMimeType);
if (rMimeType.startsWith("text/plain"))
// gtk3 thinks it is not well defined - skip too
if (rMimeType == QStringLiteral("text/plain;charset=unicode"))
continue;
// LO doesn't like 'text/plain', so we have to provide UTF-16
bool bIsNoCharset = false, bIsUTF8 = false, bIsUTF16 = false;
if (lcl_textMimeInfo(toOUString(rMimeType), bIsNoCharset, bIsUTF16, bIsUTF8))
{
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
aVector.push_back(aFlavor);
bHaveNoCharset |= bIsNoCharset;
bHaveUTF16 |= bIsUTF16;
if (bIsUTF16)
aFlavor.DataType = cppu::UnoType<OUString>::get();
else
aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
}
else
{
aFlavor.MimeType = toOUString(rMimeType);
aFlavor.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
aVector.push_back(aFlavor);
}
aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
aFlavor.MimeType = toOUString(rMimeType);
assert(nMimeTypeCount < nMimeTypeSeqSize);
aMimeTypeSeq[nMimeTypeCount] = aFlavor;
nMimeTypeCount++;
}
return aVector;
}
m_bConvertFromLocale = bHaveNoCharset && !bHaveUTF16;
if (m_bConvertFromLocale)
{
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
assert(nMimeTypeCount < nMimeTypeSeqSize);
aMimeTypeSeq[nMimeTypeCount] = aFlavor;
nMimeTypeCount++;
}
css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::getTransferDataFlavors()
{
return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
aMimeTypeSeq.realloc(nMimeTypeCount);
m_aMimeTypeSeq = aMimeTypeSeq;
return m_aMimeTypeSeq;
}
sal_Bool SAL_CALL
Qt5Transferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
{
const std::vector<css::datatransfer::DataFlavor> aAll = getTransferDataFlavorsAsVector();
return std::any_of(aAll.begin(), aAll.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
const auto aSeq = getTransferDataFlavors();
return std::any_of(aSeq.begin(), aSeq.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
return rFlavor.MimeType == aFlavor.MimeType;
}); //FIXME
});
}
Qt5ClipboardTransferable::Qt5ClipboardTransferable(QClipboard::Mode aMode)
: Qt5Transferable(QApplication::clipboard()->mimeData(aMode))
css::uno::Any SAL_CALL
Qt5Transferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
{
css::uno::Any aAny;
if (!isDataFlavorSupported(rFlavor))
return aAny;
if (rFlavor.MimeType == "text/plain;charset=utf-16")
{
OUString aString;
if (m_bConvertFromLocale)
{
QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain")));
aString = OUString(reinterpret_cast<const sal_Char*>(aByteData.data()),
aByteData.size(), osl_getThreadTextEncoding());
}
else
{
QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
aString = OUString(reinterpret_cast<const sal_Unicode*>(aByteData.data()),
aByteData.size() / 2);
}
aAny <<= aString;
}
else
{
QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aByteData.data()),
aByteData.size());
aAny <<= aSeq;
}
return aAny;
}
Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
const QMimeData* pMimeData)
: Qt5Transferable(pMimeData)
, m_aMode(aMode)
{
}
Qt5ClipboardTransferable::~Qt5ClipboardTransferable() {}
bool Qt5ClipboardTransferable::hasInFlightChanged() const
{
const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
SAL_WARN_IF(bChanged, "vcl.qt5",
"In flight clipboard change detected - broken clipboard read!");
return bChanged;
}
css::uno::Any SAL_CALL
Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
{
css::uno::Any aAny;
assert(m_pMimeData);
if (!m_pMimeData)
return aAny;
if (rFlavor.MimeType == "text/plain;charset=utf-16")
{
QString clipboardContent = m_pMimeData->text();
OUString sContent = toOUString(clipboardContent);
aAny <<= sContent.replaceAll("\r\n", "\n");
}
else if (rFlavor.MimeType == "text/html")
{
QString clipboardContent = m_pMimeData->html();
std::string aStr = clipboardContent.toStdString();
uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aStr.c_str()),
aStr.length());
aAny <<= aSeq;
}
else if (rFlavor.MimeType.startsWith("image") && m_pMimeData->hasImage())
{
QImage image = qvariant_cast<QImage>(m_pMimeData->imageData());
QByteArray ba;
QBuffer buffer(&ba);
sal_Int32 nIndex = rFlavor.MimeType.indexOf('/');
OUString sFormat(nIndex != -1 ? rFlavor.MimeType.copy(nIndex + 1) : "png");
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, sFormat.toUtf8().getStr());
uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(ba.data()), ba.size());
aAny <<= aSeq;
}
auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
SolarMutexGuard g;
pSalInst->RunInMainThread([&, this]() {
if (!hasInFlightChanged())
aAny = Qt5Transferable::getTransferData(rFlavor);
});
return aAny;
}
Qt5DnDTransferable::Qt5DnDTransferable(const QMimeData* pMimeData)
: Qt5Transferable(pMimeData)
css::uno::Sequence<css::datatransfer::DataFlavor>
SAL_CALL Qt5ClipboardTransferable::getTransferDataFlavors()
{
css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
SolarMutexGuard g;
pSalInst->RunInMainThread([&, this]() {
if (!hasInFlightChanged())
aSeq = Qt5Transferable::getTransferDataFlavors();
});
return aSeq;
}
css::uno::Any Qt5DnDTransferable::getTransferData(const css::datatransfer::DataFlavor&)
sal_Bool SAL_CALL
Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
{
uno::Any aAny;
assert(m_pMimeData);
bool bIsSupported = false;
auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
SolarMutexGuard g;
pSalInst->RunInMainThread([&, this]() {
if (!hasInFlightChanged())
bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
});
return bIsSupported;
}
// FIXME: not sure if we should support more mimetypes here
// (how to carry out external DnD with anything else than [file] URL?)
if (m_pMimeData->hasUrls())
Qt5MimeData::Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
: m_aContents(xTrans)
, m_bHaveNoCharset(false)
, m_bHaveUTF8(false)
{
assert(xTrans.is());
}
bool Qt5MimeData::deepCopy(QMimeData** const pMimeCopy) const
{
if (!pMimeCopy)
return false;
QMimeData* pMimeData = new QMimeData();
for (QString& format : formats())
{
QList<QUrl> urlList = m_pMimeData->urls();
if (urlList.size() > 0)
QByteArray aData = data(format);
// Checking for custom MIME types
if (format.startsWith("application/x-qt"))
{
std::string aStr;
// transfer data is list of URLs
for (int i = 0; i < urlList.size(); ++i)
{
QString url = urlList.at(i).path();
aStr += url.toStdString();
// separated by newline if more than 1
if (i < urlList.size() - 1)
aStr += "\n";
}
uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aStr.c_str()),
aStr.length());
aAny <<= aSeq;
// Retrieving true format name
int indexBegin = format.indexOf('"') + 1;
int indexEnd = format.indexOf('"', indexBegin);
format = format.mid(indexBegin, indexEnd - indexBegin);
}
pMimeData->setData(format, aData);
}
return aAny;
*pMimeCopy = pMimeData;
return true;
}
QStringList Qt5MimeData::formats() const
{
if (!m_aMimeTypeList.isEmpty())
return m_aMimeTypeList;
css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
= m_aContents->getTransferDataFlavors();
QStringList aList;
bool bHaveUTF16;
for (const auto& rFlavor : aFormats)
{
aList << toQString(rFlavor.MimeType);
lcl_textMimeInfo(rFlavor.MimeType, m_bHaveNoCharset, bHaveUTF16, m_bHaveUTF8);
}
// we provide a locale encoded and an UTF-8 variant, if missing
if (m_bHaveNoCharset || bHaveUTF16 || m_bHaveUTF8)
{
// if there is a text representation from LO point of view, it'll be UTF-16
assert(bHaveUTF16);
if (!m_bHaveUTF8)
aList << QStringLiteral("text/plain;charset=utf-8");
if (!m_bHaveNoCharset)
aList << QStringLiteral("text/plain");
}
m_aMimeTypeList = aList;
return m_aMimeTypeList;
}
QVariant Qt5MimeData::retrieveData(const QString& mimeType, QVariant::Type) const
{
if (!hasFormat(mimeType))
return QVariant();
css::datatransfer::DataFlavor aFlavor;
aFlavor.MimeType = toOUString(mimeType);
aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
bool bWantNoCharset = false, bWantUTF8 = false, bWantUTF16 = false;
if (lcl_textMimeInfo(aFlavor.MimeType, bWantNoCharset, bWantUTF8, bWantUTF16))
{
if ((bWantNoCharset && !m_bHaveNoCharset) || (bWantUTF8 && !m_bHaveUTF8))
{
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
}
else if (bWantUTF16)
aFlavor.DataType = cppu::UnoType<OUString>::get();
}
css::uno::Any aValue;
try
{
aValue = m_aContents->getTransferData(aFlavor);
}
catch (...)
{
}
QByteArray aByteArray;
if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING)
{
OUString aString;
aValue >>= aString;
if (bWantUTF8)
{
OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
aByteArray = QByteArray(reinterpret_cast<const char*>(aUTF8String.getStr()),
aUTF8String.getLength());
}
else if (bWantNoCharset)
{
OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
aByteArray = QByteArray(reinterpret_cast<const char*>(aLocaleString.getStr()),
aLocaleString.getLength());
}
else
return QVariant(toQString(aString));
}
else
{
css::uno::Sequence<sal_Int8> aData;
aValue >>= aData;
aByteArray
= QByteArray(reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength());
}
return QVariant::fromValue(aByteArray);
}
bool Qt5MimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */