galinette
galinette

Reputation: 9292

Discard QClipboard::dataChanged() signal if triggered by the same application

I'm using QClipboard in an application, where large 3D objects may be copied and pasted. The paste operations may block the GUI for some time as a lot of data has to be de-serialized.

I would like to optimize this for the frequent case where objects are copied and pasted on the same application window. In that case I do not need a system-wide clipboard, simple internal functions may store and copy the c++ object without need for deserialization.

So the idea is:

1)When "Copy" is called, a copy of the object is stored internally, and the object is serialized and placed on the system clipboard. A flag is set to remember that the next paste action should directly take the stored object, and not the system clipboard.

2)When the system clipboard has been modified by another app (possibly the same program, but another process), a flag is set to know that the next paste action should be done from the system clipboard, with deserialization.

3)The "Paste" action checks for the flag, and either take the internally stored object, or deserializes an object from the system clipboard.

The issue is 1). Whenever I change the system clipboard, the dataChanged() signal gets triggered. And this is done asynchronously, long time after QClipboard::setData has been called. So setting blockSignals() during the call to setData() does not help.

Any idea?

Thanks!

Upvotes: 3

Views: 631

Answers (2)

Kevin Krammer
Kevin Krammer

Reputation: 5207

From the documentation I would assume that QClipboard::ownsClipboard() returns true if the dataChanged() is caused by the current process itself.

So you could check that in the slot connected to dataChanged() and e.g. ignore that specific invocation.

Upvotes: 3

First issue is that you shouldn't be deserializing in the gui thread. You can run the deserialization concurrently.

Once that's fixed, you can then optimize for the case of the clipboard being internal. This can be done using a flag - without blocking signals.

Here's a sketch of how you could address both issues. First, we have a Data class that holds the data and is expensive to copy - so we don't let it be copied:

class Data {
   Q_DISABLE_COPY(Data) // presumably expensive to copy
public:
   //...
   QMimeData* serialize() { return new QMimeData; }
   static QSharedPointer<Data> deserialize(QMimeData*);
};
Q_DECLARE_METATYPE(QSharedPointer<Data>)

Then we need a way to clone mime data:

QMimeData* clone(const QMimeData *src) {
   auto dst = new QMimeData();
   for (auto format : src->formats())
      dst->setData(format, src->data(format));
   return dst;
}

Finally, a controller object that has the on_copy and on_paste methods that are triggered by the actions.

The tracking is done in two stages: first, a copy switches the internal state to Copied. Then, when the clipboard indicates that the data has changed, the state transitions from Copied to Ready.

Finally, on_paste will perform the paste using internal data if the state is Ready. Otherwise, it will deserialize concurrently, and perform the paste once deserialization is finished.

The paste method should implement the actual pasting of the data.

class Class : public QObject {
   Q_OBJECT
   QSharedPointer<Data> m_data;
   enum { None, Copied, Ready } m_internalCopy = None;

   Q_SIGNAL void reqPaste(const QSharedPointer<Data> &);
   void paste(const QSharedPointer<Data> &);
   void onDataChanged() {
      m_internalCopy = m_internalCopy == Copied ? Ready : None;
   }
public:
   Q_SLOT void on_copy() {
      m_internalCopy = Copied;
      QScopedPointer<QMimeData> mimeData(m_data->serialize());
      QApplication::clipboard()->setMimeData(mimeData.data());
   }
   Q_SLOT void on_paste() {
      if (m_internalCopy == Ready)
         return paste(m_data);

      m_internalCopy = None;
      auto mimeData = clone(QApplication::clipboard()->mimeData());
      QtConcurrent::run([=]{
         emit reqPaste(Data::deserialize(mimeData));
         delete mimeData;
      });
   }
   Class() {
      qRegisterMetaType<QSharedPointer<Data>>();
      connect(QApplication::clipboard(), &QClipboard::dataChanged, this,
              &Class::onDataChanged);
      connect(this, &Class::reqPaste, this, &Class::paste);
   }
};

Upvotes: 1

Related Questions