summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan-Marek Glogowski <glogow@fbihome.de>2019-05-31 16:40:34 +0000
committerMichael Weghorn <m.weghorn@posteo.de>2019-06-18 07:40:39 +0200
commit8c42f9841e4dcccdcb4f194660d79e1ed316f850 (patch)
treeffb00925d3a9c86a314c0ce1f52a7165c89c2350
parentQt5 refactor and move XTransferable implementations (diff)
downloadcore-8c42f9841e4dcccdcb4f194660d79e1ed316f850.tar.gz
core-8c42f9841e4dcccdcb4f194660d79e1ed316f850.zip
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
-rw-r--r--vcl/inc/qt5/Qt5Clipboard.hxx56
-rw-r--r--vcl/inc/qt5/Qt5Transferable.hxx90
-rw-r--r--vcl/qt5/Qt5Clipboard.cxx338
-rw-r--r--vcl/qt5/Qt5Instance.cxx13
-rw-r--r--vcl/qt5/Qt5Transferable.cxx355
5 files changed, 513 insertions, 339 deletions
diff --git a/vcl/inc/qt5/Qt5Clipboard.hxx b/vcl/inc/qt5/Qt5Clipboard.hxx
index 2c2bb93f5016..93ad36a0e672 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;
-
- /*
- * XServiceInfo
- */
+ // factory function to construct only valid Qt5Clipboard objects by name
+ static css::uno::Reference<css::uno::XInterface> create(const OUString& aModeString);
+ // 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 edc14904f769..f36216eed121 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
{
+ // to detect in-flight QMimeData changes
+ const QClipboard::Mode m_aMode;
+
+ bool hasInFlightChanged() const;
+
public:
- explicit Qt5ClipboardTransferable(QClipboard::Mode aMode);
- ~Qt5ClipboardTransferable() override;
+ 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
+/**
+ * Convenience typedef for better code readability
+ *
+ * This just uses the QMimeData provided by the QWidgets D'n'D events.
+ **/
+typedef Qt5Transferable Qt5DnDTransferable;
+
+/**
+ * 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:
- Qt5DnDTransferable(const QMimeData* pMimeData);
+ explicit Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& aContents);
- css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override;
+ 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 2fc97caeb16b..50126e6d3d4e 100644
--- a/vcl/qt5/Qt5Clipboard.cxx
+++ b/vcl/qt5/Qt5Clipboard.cxx
@@ -8,85 +8,73 @@
*
*/
-#include <comphelper/solarmutex.hxx>
-#include <comphelper/sequence.hxx>
+#include <Qt5Clipboard.hxx>
+#include <Qt5Clipboard.moc>
+
#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 <Qt5Instance.hxx>
#include <Qt5Transferable.hxx>
#include <Qt5Tools.hxx>
+#include <cassert>
#include <map>
-using namespace com::sun::star;
-
-namespace
-{
-QClipboard::Mode getClipboardTypeFromName(const OUString& aString)
+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)
{
- 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;
-
- 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;
+ 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);
}
-void lcl_peekFormats(const css::uno::Sequence<css::datatransfer::DataFlavor>& rFormats,
- bool& bHasHtml, bool& bHasImage)
+css::uno::Reference<css::uno::XInterface> Qt5Clipboard::create(const OUString& aModeString)
{
- for (int i = 0; i < rFormats.getLength(); ++i)
- {
- const css::datatransfer::DataFlavor& rFlavor = rFormats[i];
+ static const std::map<OUString, QClipboard::Mode> aNameToClipboardMap
+ = { { "CLIPBOARD", QClipboard::Clipboard }, { "PRIMARY", QClipboard::Selection } };
- if (rFlavor.MimeType == "text/html")
- bHasHtml = true;
- else if (rFlavor.MimeType.startsWith("image"))
- bHasImage = true;
- }
-}
-}
+ assert(QApplication::clipboard()->thread() == qApp->thread());
-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;
+
+ 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);
+ });
}
-Qt5Clipboard::~Qt5Clipboard() {}
-
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 d260536683e7..26e2e5782147 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 a78ff8bc712e..65e2ad5399fc 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))
-{
-}
-
-Qt5ClipboardTransferable::~Qt5ClipboardTransferable() {}
-
css::uno::Any SAL_CALL
-Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+Qt5Transferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
{
css::uno::Any aAny;
- assert(m_pMimeData);
- if (!m_pMimeData)
+ if (!isDataFlavorSupported(rFlavor))
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");
+ 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 if (rFlavor.MimeType == "text/html")
+ else
{
- 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());
+ 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;
}
- 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());
+ return aAny;
+}
+
+Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
+ const QMimeData* pMimeData)
+ : Qt5Transferable(pMimeData)
+ , m_aMode(aMode)
+{
+}
- uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(ba.data()), ba.size());
- aAny <<= aSeq;
- }
+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;
+ 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;
+}
+
+sal_Bool SAL_CALL
+Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ bool bIsSupported = false;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
+ });
+ return bIsSupported;
+}
+
+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())
+ {
+ QByteArray aData = data(format);
+ // Checking for custom MIME types
+ if (format.startsWith("application/x-qt"))
+ {
+ // Retrieving true format name
+ int indexBegin = format.indexOf('"') + 1;
+ int indexEnd = format.indexOf('"', indexBegin);
+ format = format.mid(indexBegin, indexEnd - indexBegin);
+ }
+ pMimeData->setData(format, aData);
+ }
+
+ *pMimeCopy = pMimeData;
+ return true;
}
-css::uno::Any Qt5DnDTransferable::getTransferData(const css::datatransfer::DataFlavor&)
+QStringList Qt5MimeData::formats() const
{
- uno::Any aAny;
- assert(m_pMimeData);
+ if (!m_aMimeTypeList.isEmpty())
+ return m_aMimeTypeList;
- // 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())
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
+ = m_aContents->getTransferDataFlavors();
+ QStringList aList;
+ bool bHaveUTF16;
+
+ for (const auto& rFlavor : aFormats)
{
- QList<QUrl> urlList = m_pMimeData->urls();
+ 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();
- if (urlList.size() > 0)
+ 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))
{
- 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;
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
}
+ else if (bWantUTF16)
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
}
- return aAny;
+
+ 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: */