QObject: add a single shot connection flag
If one needed to listen to a signal just once, one had to store the QMetaObject::Connection object returned by connect() and use it to disconnect the slot after the first signal activation. This has led to a proliferation of using wrappers (and enough TMP); they usually look like this: 1) create a shared_ptr<QMO::Connection>, allocating its payload; 2) create a lambda, capturing the shared_ptr by value; 3) in the lambda, disconnect the connection (through the shared_ptr), and call the actual slot; 4) connect the signal to the lambda, storing the returned QMO::Connection into the shared_ptr. This is expensive, error prone for newcomers, and tricky to support as a general facility inside one's projects. We can do better, just support single shot connections right in QObject. [ChangeLog][QtCore][QObject] Added the Qt::SingleShotConnection flag. When a connection is established with this flag set, the slot is going to be activated at most once; when the signal is emitted, the connection gets automatically broken by Qt. Change-Id: I5f5feeae7f76c9c3d6323d841efba81c8f98ce7e Fixes: QTBUG-44219 Reviewed-by: Lars Knoll <lars.knoll@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
This commit is contained in:
parent
3e7c63955e
commit
11b8c46d2a
@ -1301,7 +1301,8 @@ namespace Qt {
|
||||
DirectConnection,
|
||||
QueuedConnection,
|
||||
BlockingQueuedConnection,
|
||||
UniqueConnection = 0x80
|
||||
UniqueConnection = 0x80,
|
||||
SingleShotConnection = 0x100,
|
||||
};
|
||||
|
||||
enum ShortcutContext {
|
||||
|
@ -638,6 +638,13 @@
|
||||
(i.e. if the same signal is already connected to the same slot
|
||||
for the same pair of objects). This flag was introduced in Qt 4.6.
|
||||
|
||||
\value SingleShotConnection
|
||||
This is a flag that can be combined with any one of the above
|
||||
connection types, using a bitwise OR. When Qt::SingleShotConnection
|
||||
is set, the slot is going to be called only once; the connection
|
||||
will be automatically broken when the signal is emitted.
|
||||
This flag was introduced in Qt 6.0.
|
||||
|
||||
With queued connections, the parameters must be of types that are
|
||||
known to Qt's meta-object system, because Qt needs to copy the
|
||||
arguments to store them in an event behind the scenes. If you try
|
||||
|
@ -3325,8 +3325,14 @@ QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
|
||||
c2 = c2->nextConnectionList.loadRelaxed();
|
||||
}
|
||||
}
|
||||
type &= Qt::UniqueConnection - 1;
|
||||
}
|
||||
type &= ~Qt::UniqueConnection;
|
||||
|
||||
const bool isSingleShot = type & Qt::SingleShotConnection;
|
||||
type &= ~Qt::SingleShotConnection;
|
||||
|
||||
Q_ASSERT(type >= 0);
|
||||
Q_ASSERT(type <= 3);
|
||||
|
||||
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
|
||||
c->sender = s;
|
||||
@ -3341,6 +3347,7 @@ QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
|
||||
c->isSlotObject = false;
|
||||
c->argumentTypes.storeRelaxed(types);
|
||||
c->callFunction = callFunction;
|
||||
c->isSingleShot = isSingleShot;
|
||||
|
||||
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
|
||||
|
||||
@ -3632,7 +3639,8 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect
|
||||
++nargs;
|
||||
|
||||
QBasicMutexLocker locker(signalSlotLock(c->receiver.loadRelaxed()));
|
||||
if (!c->receiver.loadRelaxed()) {
|
||||
QObject *receiver = c->receiver.loadRelaxed();
|
||||
if (!receiver) {
|
||||
// the connection has been disconnected before we got the lock
|
||||
return;
|
||||
}
|
||||
@ -3658,17 +3666,22 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect
|
||||
args[n] = types[n].create(argv[n]);
|
||||
}
|
||||
|
||||
if (c->isSingleShot && !QObjectPrivate::disconnect(c)) {
|
||||
delete ev;
|
||||
return;
|
||||
}
|
||||
|
||||
locker.relock();
|
||||
if (c->isSlotObject)
|
||||
c->slotObj->destroyIfLastRef();
|
||||
if (!c->receiver.loadRelaxed()) {
|
||||
if (!c->isSingleShot && !c->receiver.loadRelaxed()) {
|
||||
// the connection has been disconnected while we were unlocked
|
||||
locker.unlock();
|
||||
delete ev;
|
||||
return;
|
||||
}
|
||||
|
||||
QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev);
|
||||
QCoreApplication::postEvent(receiver, ev);
|
||||
}
|
||||
|
||||
template <bool callbacks_enabled>
|
||||
@ -3762,10 +3775,14 @@ void doActivate(QObject *sender, int signal_index, void **argv)
|
||||
sender->metaObject()->className(), sender,
|
||||
receiver->metaObject()->className(), receiver);
|
||||
}
|
||||
|
||||
if (c->isSingleShot && !QObjectPrivate::disconnect(c))
|
||||
continue;
|
||||
|
||||
QSemaphore semaphore;
|
||||
{
|
||||
QBasicMutexLocker locker(signalSlotLock(receiver));
|
||||
if (!c->receiver.loadAcquire())
|
||||
if (!c->isSingleShot && !c->receiver.loadAcquire())
|
||||
continue;
|
||||
QMetaCallEvent *ev = c->isSlotObject ?
|
||||
new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
|
||||
@ -3778,6 +3795,9 @@ void doActivate(QObject *sender, int signal_index, void **argv)
|
||||
#endif
|
||||
}
|
||||
|
||||
if (c->isSingleShot && !QObjectPrivate::disconnect(c))
|
||||
continue;
|
||||
|
||||
QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);
|
||||
|
||||
if (c->isSlotObject) {
|
||||
@ -4855,7 +4875,7 @@ QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signa
|
||||
*/
|
||||
QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
|
||||
const QObject *receiver, void **slot,
|
||||
QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
|
||||
QtPrivate::QSlotObjectBase *slotObj, int type,
|
||||
const int *types, const QMetaObject *senderMetaObject)
|
||||
{
|
||||
if (!sender || !receiver || !slotObj || !senderMetaObject) {
|
||||
@ -4889,8 +4909,14 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s
|
||||
c2 = c2->nextConnectionList.loadRelaxed();
|
||||
}
|
||||
}
|
||||
type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
|
||||
}
|
||||
type &= ~Qt::UniqueConnection;
|
||||
|
||||
const bool isSingleShot = type & Qt::SingleShotConnection;
|
||||
type &= ~Qt::SingleShotConnection;
|
||||
|
||||
Q_ASSERT(type >= 0);
|
||||
Q_ASSERT(type <= 3);
|
||||
|
||||
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
|
||||
c->sender = s;
|
||||
@ -4906,6 +4932,7 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s
|
||||
c->argumentTypes.storeRelaxed(types);
|
||||
c->ownArgumentTypes = false;
|
||||
}
|
||||
c->isSingleShot = isSingleShot;
|
||||
|
||||
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
|
||||
QMetaObject::Connection ret(c.release());
|
||||
@ -4929,39 +4956,12 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s
|
||||
bool QObject::disconnect(const QMetaObject::Connection &connection)
|
||||
{
|
||||
QObjectPrivate::Connection *c = static_cast<QObjectPrivate::Connection *>(connection.d_ptr);
|
||||
|
||||
if (!c)
|
||||
return false;
|
||||
QObject *receiver = c->receiver.loadRelaxed();
|
||||
if (!receiver)
|
||||
return false;
|
||||
|
||||
QBasicMutex *senderMutex = signalSlotLock(c->sender);
|
||||
QBasicMutex *receiverMutex = signalSlotLock(receiver);
|
||||
|
||||
QObjectPrivate::ConnectionData *connections;
|
||||
{
|
||||
QOrderedMutexLocker locker(senderMutex, receiverMutex);
|
||||
|
||||
// load receiver once again and recheck to ensure nobody else has removed the connection in the meantime
|
||||
receiver = c->receiver.loadRelaxed();
|
||||
if (!receiver)
|
||||
return false;
|
||||
|
||||
connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed();
|
||||
Q_ASSERT(connections);
|
||||
connections->removeConnection(c);
|
||||
const bool disconnected = QObjectPrivate::disconnect(c);
|
||||
if (disconnected) {
|
||||
const_cast<QMetaObject::Connection &>(connection).d_ptr = nullptr;
|
||||
c->deref(); // has been removed from the QMetaObject::Connection object
|
||||
}
|
||||
|
||||
connections->cleanOrphanedConnections(c->sender);
|
||||
|
||||
c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
|
||||
c->signal_index));
|
||||
|
||||
const_cast<QMetaObject::Connection &>(connection).d_ptr = nullptr;
|
||||
c->deref(); // has been removed from the QMetaObject::Connection object
|
||||
|
||||
return true;
|
||||
return disconnected;
|
||||
}
|
||||
|
||||
/*! \fn template<typename PointerToMemberFunction> bool QObject::disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method)
|
||||
@ -5084,6 +5084,43 @@ bool QObjectPrivate::disconnect(const QObject *sender, int signal_index, void **
|
||||
return QMetaObjectPrivate::disconnect(sender, signal_index, senderMetaObject, sender, -1, slot);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\threadsafe
|
||||
*/
|
||||
bool QObjectPrivate::disconnect(QObjectPrivate::Connection *c)
|
||||
{
|
||||
if (!c)
|
||||
return false;
|
||||
QObject *receiver = c->receiver.loadRelaxed();
|
||||
if (!receiver)
|
||||
return false;
|
||||
|
||||
QBasicMutex *senderMutex = signalSlotLock(c->sender);
|
||||
QBasicMutex *receiverMutex = signalSlotLock(receiver);
|
||||
|
||||
QObjectPrivate::ConnectionData *connections;
|
||||
{
|
||||
QOrderedMutexLocker locker(senderMutex, receiverMutex);
|
||||
|
||||
// load receiver once again and recheck to ensure nobody else has removed the connection in the meantime
|
||||
receiver = c->receiver.loadRelaxed();
|
||||
if (!receiver)
|
||||
return false;
|
||||
|
||||
connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed();
|
||||
Q_ASSERT(connections);
|
||||
connections->removeConnection(c);
|
||||
}
|
||||
|
||||
connections->cleanOrphanedConnections(c->sender);
|
||||
|
||||
c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(),
|
||||
c->signal_index));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! \class QMetaObject::Connection
|
||||
\inmodule QtCore
|
||||
Represents a handle to a signal-slot (or signal-functor) connection.
|
||||
|
@ -153,9 +153,10 @@ public:
|
||||
ushort method_offset;
|
||||
ushort method_relative;
|
||||
signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
|
||||
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
|
||||
ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking
|
||||
ushort isSlotObject : 1;
|
||||
ushort ownArgumentTypes : 1;
|
||||
ushort isSingleShot : 1;
|
||||
Connection() : ref_(2), ownArgumentTypes(true) {
|
||||
//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
|
||||
}
|
||||
@ -348,10 +349,11 @@ public:
|
||||
|
||||
static QMetaObject::Connection connectImpl(const QObject *sender, int signal_index,
|
||||
const QObject *receiver, void **slot,
|
||||
QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
|
||||
QtPrivate::QSlotObjectBase *slotObj, int type,
|
||||
const int *types, const QMetaObject *senderMetaObject);
|
||||
static QMetaObject::Connection connect(const QObject *sender, int signal_index, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type);
|
||||
static bool disconnect(const QObject *sender, int signal_index, void **slot);
|
||||
static bool disconnect(Connection *c);
|
||||
|
||||
void ensureConnectionData()
|
||||
{
|
||||
|
@ -49,6 +49,8 @@
|
||||
#include <private/qobject_p.h>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
class tst_QObject : public QObject
|
||||
@ -154,6 +156,7 @@ private slots:
|
||||
void nullReceiver();
|
||||
void functorReferencesConnection();
|
||||
void disconnectDisconnects();
|
||||
void singleShotConnection();
|
||||
};
|
||||
|
||||
struct QObjectCreatedOnShutdown
|
||||
@ -7495,6 +7498,594 @@ void tst_QObject::disconnectDisconnects()
|
||||
QCOMPARE(count, 3); // + δ
|
||||
}
|
||||
|
||||
class ReceiverDisconnecting : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SenderObject *sender;
|
||||
int slotCalledCount = 0;
|
||||
|
||||
public slots:
|
||||
void aSlotByName()
|
||||
{
|
||||
++slotCalledCount;
|
||||
QVERIFY(!disconnect(sender, SIGNAL(signal1()), this, SLOT(aSlotByName())));
|
||||
}
|
||||
|
||||
void aSlotByPtr()
|
||||
{
|
||||
++slotCalledCount;
|
||||
QVERIFY(!disconnect(sender, &SenderObject::signal1, this, &ReceiverDisconnecting::aSlotByPtr));
|
||||
}
|
||||
};
|
||||
|
||||
class DeleteThisReceiver : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static int counter;
|
||||
|
||||
public slots:
|
||||
void deleteThis()
|
||||
{
|
||||
++counter;
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
int DeleteThisReceiver::counter = 0;
|
||||
|
||||
void tst_QObject::singleShotConnection()
|
||||
{
|
||||
{
|
||||
// Non single shot behavior: slot called every time the signal is emitted
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot);
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 3);
|
||||
}
|
||||
|
||||
{
|
||||
// Non single shot behavior: multiple connections cause multiple invocations
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot);
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
QMetaObject::Connection c2 = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot);
|
||||
QVERIFY(c);
|
||||
QVERIFY(c2);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(c);
|
||||
QVERIFY(c2);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 4);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(c);
|
||||
QVERIFY(c2);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 6);
|
||||
}
|
||||
|
||||
{
|
||||
// Single shot behavior: slot called only once
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Same, without holding a Connection object
|
||||
SenderObject sender;
|
||||
bool ok = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Single shot, disconnect before emitting
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QVERIFY(QObject::disconnect(c));
|
||||
QVERIFY(!c);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Single shot together with another connection
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot));
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 4);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 5);
|
||||
}
|
||||
|
||||
{
|
||||
// Two single shot, from the same signal, to the same slot
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// Two single shot, from different signals, to the same slot
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
QVERIFY(connect(&sender, &SenderObject::signal2,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal2();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal2();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// Same signal, different connections
|
||||
SenderObject sender;
|
||||
ReceiverObject receiver1, receiver2;
|
||||
receiver1.reset();
|
||||
receiver2.reset();
|
||||
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&receiver1, &ReceiverObject::slot1));
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&receiver2, &ReceiverObject::slot1,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
QCOMPARE(receiver1.count_slot1, 0);
|
||||
QCOMPARE(receiver2.count_slot1, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver1.count_slot1, 1);
|
||||
QCOMPARE(receiver2.count_slot1, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver1.count_slot1, 2);
|
||||
QCOMPARE(receiver2.count_slot1, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver1.count_slot1, 3);
|
||||
QCOMPARE(receiver2.count_slot1, 1);
|
||||
|
||||
// Reestablish a single shot
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&receiver2, &ReceiverObject::slot1,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
QCOMPARE(receiver1.count_slot1, 3);
|
||||
QCOMPARE(receiver2.count_slot1, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver1.count_slot1, 4);
|
||||
QCOMPARE(receiver2.count_slot1, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver1.count_slot1, 5);
|
||||
QCOMPARE(receiver2.count_slot1, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// Check that the slot is invoked with the connection already disconnected
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c;
|
||||
auto breakSlot = [&]() {
|
||||
QVERIFY(!c);
|
||||
++sender.aPublicSlotCalled;
|
||||
};
|
||||
|
||||
c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, breakSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
QVERIFY(!c);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
QVERIFY(!c);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
QVERIFY(!c);
|
||||
}
|
||||
|
||||
{
|
||||
// Same
|
||||
SenderObject sender;
|
||||
ReceiverDisconnecting receiver;
|
||||
receiver.sender = &sender;
|
||||
bool ok = connect(&sender, SIGNAL(signal1()),
|
||||
&receiver, SLOT(aSlotByName()),
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(receiver.slotCalledCount, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
// reconnect
|
||||
ok = connect(&sender, SIGNAL(signal1()),
|
||||
&receiver, SLOT(aSlotByName()),
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// Same
|
||||
SenderObject sender;
|
||||
ReceiverDisconnecting receiver;
|
||||
receiver.sender = &sender;
|
||||
bool ok = connect(&sender, &SenderObject::signal1,
|
||||
&receiver, &ReceiverDisconnecting::aSlotByPtr,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(receiver.slotCalledCount, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
// reconnect
|
||||
ok = connect(&sender, &SenderObject::signal1,
|
||||
&receiver, &ReceiverDisconnecting::aSlotByPtr,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(receiver.slotCalledCount, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(receiver.slotCalledCount, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// Reconnect from inside the slot
|
||||
SenderObject sender;
|
||||
std::function<void()> reconnectingSlot;
|
||||
bool reconnect = false;
|
||||
reconnectingSlot = [&]() {
|
||||
++sender.aPublicSlotCalled;
|
||||
if (reconnect) {
|
||||
QObject::connect(&sender, &SenderObject::signal1,
|
||||
&sender, reconnectingSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
}
|
||||
};
|
||||
|
||||
bool ok = connect(&sender, &SenderObject::signal1,
|
||||
&sender, reconnectingSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
reconnect = true;
|
||||
ok = connect(&sender, &SenderObject::signal1,
|
||||
&sender, reconnectingSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 2);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 3);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 4);
|
||||
|
||||
reconnect = false;
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 5);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 5);
|
||||
}
|
||||
|
||||
{
|
||||
// Delete the receiver from inside the slot
|
||||
SenderObject sender;
|
||||
QPointer<DeleteThisReceiver> p = new DeleteThisReceiver;
|
||||
DeleteThisReceiver::counter = 0;
|
||||
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
p.get(), &DeleteThisReceiver::deleteThis,
|
||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
|
||||
|
||||
QVERIFY(p);
|
||||
QCOMPARE(DeleteThisReceiver::counter, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(DeleteThisReceiver::counter, 1);
|
||||
QVERIFY(!p);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(DeleteThisReceiver::counter, 1);
|
||||
QVERIFY(!p);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, non single shot
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection)));
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QTRY_COMPARE(sender.aPublicSlotCalled, 3);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 3);
|
||||
|
||||
QTRY_COMPARE(sender.aPublicSlotCalled, 4);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, single shot
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QTRY_COMPARE(sender.aPublicSlotCalled, 1);
|
||||
QTest::qWait(0);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, single shot, checking the connection handle
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QTRY_COMPARE(sender.aPublicSlotCalled, 1);
|
||||
QVERIFY(!c);
|
||||
QTest::qWait(0);
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, single shot, disconnect before emitting
|
||||
SenderObject sender;
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QVERIFY(QObject::disconnect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot));
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QTest::qWait(0);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, single shot, disconnect before emitting by using the connection handle
|
||||
SenderObject sender;
|
||||
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
|
||||
&sender, &SenderObject::aPublicSlot,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||
QVERIFY(c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QVERIFY(QObject::disconnect(c));
|
||||
QVERIFY(!c);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
|
||||
QTest::qWait(0);
|
||||
QVERIFY(!c);
|
||||
QCOMPARE(sender.aPublicSlotCalled, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Queued, single shot, delete the receiver from inside the slot
|
||||
SenderObject sender;
|
||||
QPointer<DeleteThisReceiver> p = new DeleteThisReceiver;
|
||||
DeleteThisReceiver::counter = 0;
|
||||
|
||||
QVERIFY(connect(&sender, &SenderObject::signal1,
|
||||
p.get(), &DeleteThisReceiver::deleteThis,
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
|
||||
QCOMPARE(DeleteThisReceiver::counter, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(p);
|
||||
QCOMPARE(DeleteThisReceiver::counter, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(p);
|
||||
QCOMPARE(DeleteThisReceiver::counter, 0);
|
||||
|
||||
sender.emitSignal1();
|
||||
QVERIFY(p);
|
||||
QCOMPARE(DeleteThisReceiver::counter, 0);
|
||||
|
||||
QTRY_COMPARE(DeleteThisReceiver::counter, 1);
|
||||
QVERIFY(!p);
|
||||
QTest::qWait(0);
|
||||
QCOMPARE(DeleteThisReceiver::counter, 1);
|
||||
QVERIFY(!p);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for QtPrivate::HasQ_OBJECT_Macro
|
||||
static_assert(QtPrivate::HasQ_OBJECT_Macro<tst_QObject>::Value);
|
||||
static_assert(!QtPrivate::HasQ_OBJECT_Macro<SiblingDeleter>::Value);
|
||||
|
Loading…
x
Reference in New Issue
Block a user