QSignalBlocker: (new) RAII class for QObject::blockSignals()

I don't think I ever worked on a project of non-trivial
size that didn't at some point add a QSignalBlocker.

This commit adds code, tests and documentation. Later
commits will convert naked blockSignals() calls to use
QSignalBlocker.

The implementation is purely inline to avoid the heavy
overhead of cross-dll function calls for this miniscule
task. This should not be a problem because QSignalBlocker
only uses public API and a pattern that we anyway need
to keep working until Qt 6, at least, so even changing
the implementation later will be no problem as the old
implementation lurking in non-recompiled code will be
acceptable, too.

This implementation is an evolution from KDTools'
KDSignalBlocker, with the following changes:
- Implements unblock() and reblock()
- Uses the return value of blockSignals() instead of a
  separate signalsBlocked() call.

Change-Id: I1933dfd72a0f5190324be377cfca3c54cf3d6828
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Marc Mutz 2012-03-01 18:52:32 +01:00 committed by The Qt Project
parent bf6a345baa
commit ed827acc27
4 changed files with 170 additions and 0 deletions

2
dist/changes-5.3.0 vendored
View File

@ -25,5 +25,7 @@ QtWidgets
QtCore QtCore
------ ------
- QSignalBlocker: (new) RAII wrapper around QObject::blockSignals()
QtGui QtGui
----- -----

View File

@ -482,6 +482,79 @@ void QMetaCallEvent::placeMetaCall(QObject *object)
} }
} }
/*!
\class QSignalBlocker
\brief Exception-safe wrapper around QObject::blockSignals()
\since 5.3
\ingroup objectmodel
\reentrant
QSignalBlocker can be used whereever you would otherwise use a
pair of calls to blockSignals(). It blocks signals in its
constructor and in the destructor it resets the state to what
it was before the constructor ran.
\code
{
const QSignalBlocker blocker(someQObject);
// no signals here
}
\endcode
is thus equivalent to
\code
const bool wasBlocked = someQObject->blockSignals(true);
// no signals here
someQObject->blockSignals(false);
\endcode
except the code using QSignalBlocker is safe in the face of
exceptions.
\sa QMutexLocker, QEventLoopLocker
*/
/*!
\fn QSignalBlocker::QSignalBlocker(QObject *object)
Constructor. Calls \a{object}->blockSignals(true).
*/
/*!
\fn QSignalBlocker::QSignalBlocker(QObject &object)
\overload
Calls \a{object}.blockSignals(true).
*/
/*!
\fn QSignalBlocker::~QSignalBlocker()
Destructor. Restores the QObject::signalsBlocked() state to what it
was before the constructor ran, unless unblock() has been called
without a following reblock(), in which case it does nothing.
*/
/*!
\fn void QSignalBlocker::reblock()
Re-blocks signals after a previous unblock().
The numbers of reblock() and unblock() calls are not counted, so
every reblock() undoes any number of unblock() calls.
*/
/*!
\fn void QSignalBlocker::unblock()
Temporarily restores the QObject::signalsBlocked() state to what
it was before this QSignaBlocker's constructor ran. To undo, use
reblock().
The numbers of reblock() and unblock() calls are not counted, so
every unblock() undoes any number of reblock() calls.
*/
/*! /*!
\class QObject \class QObject
\inmodule QtCore \inmodule QtCore

View File

@ -549,6 +549,52 @@ template <class T> inline const char * qobject_interface_iid()
Q_CORE_EXPORT QDebug operator<<(QDebug, const QObject *); Q_CORE_EXPORT QDebug operator<<(QDebug, const QObject *);
#endif #endif
class Q_CORE_EXPORT QSignalBlocker
{
public:
inline explicit QSignalBlocker(QObject *o);
inline explicit QSignalBlocker(QObject &o);
inline ~QSignalBlocker();
inline void reblock();
inline void unblock();
private:
Q_DISABLE_COPY(QSignalBlocker)
QObject * const m_o;
bool m_blocked;
bool m_inhibited;
};
QSignalBlocker::QSignalBlocker(QObject *o)
: m_o(o),
m_blocked(o && o->blockSignals(true)),
m_inhibited(false)
{}
QSignalBlocker::QSignalBlocker(QObject &o)
: m_o(&o),
m_blocked(o.blockSignals(true)),
m_inhibited(false)
{}
QSignalBlocker::~QSignalBlocker()
{
if (m_o && !m_inhibited)
m_o->blockSignals(m_blocked);
}
void QSignalBlocker::reblock()
{
if (m_o) m_o->blockSignals(true);
m_inhibited = false;
}
void QSignalBlocker::unblock()
{
if (m_o) m_o->blockSignals(m_blocked);
m_inhibited = true;
}
namespace QtPrivate { namespace QtPrivate {
inline QObject & deref_for_methodcall(QObject &o) { return o; } inline QObject & deref_for_methodcall(QObject &o) { return o; }
inline QObject & deref_for_methodcall(QObject *o) { return *o; } inline QObject & deref_for_methodcall(QObject *o) { return *o; }

View File

@ -102,6 +102,7 @@ private slots:
#ifndef QT_NO_PROCESS #ifndef QT_NO_PROCESS
void recursiveSignalEmission(); void recursiveSignalEmission();
#endif #endif
void signalBlocking();
void blockingQueuedConnection(); void blockingQueuedConnection();
void childEvents(); void childEvents();
void installEventFilter(); void installEventFilter();
@ -2979,6 +2980,54 @@ void tst_QObject::recursiveSignalEmission()
} }
#endif #endif
void tst_QObject::signalBlocking()
{
SenderObject sender;
ReceiverObject receiver;
receiver.connect(&sender, SIGNAL(signal1()), SLOT(slot1()));
sender.emitSignal1();
QVERIFY(receiver.called(1));
receiver.reset();
{
QSignalBlocker blocker(&sender);
sender.emitSignal1();
QVERIFY(!receiver.called(1));
receiver.reset();
sender.blockSignals(false);
sender.emitSignal1();
QVERIFY(receiver.called(1));
receiver.reset();
sender.blockSignals(true);
sender.emitSignal1();
QVERIFY(!receiver.called(1));
receiver.reset();
blocker.unblock();
sender.emitSignal1();
QVERIFY(receiver.called(1));
receiver.reset();
blocker.reblock();
sender.emitSignal1();
QVERIFY(!receiver.called(1));
receiver.reset();
}
sender.emitSignal1();
QVERIFY(receiver.called(1));
receiver.reset();
}
void tst_QObject::blockingQueuedConnection() void tst_QObject::blockingQueuedConnection()
{ {
{ {