Reputation: 9292
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
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
Reputation: 98445
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