diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index db6ff568bdf..97aae6f7e0a 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -260,26 +260,50 @@ class QSingleShotTimer : public QObject { Q_OBJECT int timerId; + bool hasValidReceiver; + QPointer receiver; + QtPrivate::QSlotObjectBase *slotObj; public: ~QSingleShotTimer(); QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char * m); + QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj); + Q_SIGNALS: void timeout(); protected: void timerEvent(QTimerEvent *); }; -QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member) - : QObject(QAbstractEventDispatcher::instance()) +QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char *member) + : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(true), slotObj(0) { - connect(this, SIGNAL(timeout()), receiver, member); timerId = startTimer(msec, timerType); + connect(this, SIGNAL(timeout()), r, member); +} + +QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj) + : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj) +{ + timerId = startTimer(msec, timerType); + if (r && thread() != r->thread()) { + // We need the invocation to happen in the receiver object's thread. + // So, move QSingleShotTimer to the correct thread. Before that occurs, we + // shall remove the parent from the object. + setParent(0); + moveToThread(r->thread()); + + // Given we're also parentless now, we should take defence against leaks + // in case the application quits before we expire. + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); + } } QSingleShotTimer::~QSingleShotTimer() { if (timerId > 0) killTimer(timerId); + if (slotObj) + slotObj->destroyIfLastRef(); } void QSingleShotTimer::timerEvent(QTimerEvent *) @@ -289,7 +313,18 @@ void QSingleShotTimer::timerEvent(QTimerEvent *) if (timerId > 0) killTimer(timerId); timerId = -1; - emit timeout(); + + if (slotObj) { + // If the receiver was destroyed, skip this part + if (Q_LIKELY(!receiver.isNull() || !hasValidReceiver)) { + // We allocate only the return type - we previously checked the function had + // no arguments. + void *args[1] = { 0 }; + slotObj->call(const_cast(receiver.data()), args); + } + } else { + emit timeout(); + } // we would like to use delete later here, but it feels like a // waste to post a new event to handle this event, so we just unset the flag @@ -297,6 +332,25 @@ void QSingleShotTimer::timerEvent(QTimerEvent *) qDeleteInEventHandler(this); } +/*! + \internal + + Implementation of the template version of singleShot + + \a msec is the timer interval + \a timerType is the timer type + \a receiver is the receiver object, can be null. In such a case, it will be the same + as the final sender class. + \a slot a pointer only used when using Qt::UniqueConnection + \a slotObj the slot object + */ +void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, + const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj) +{ + new QSingleShotTimer(msec, timerType, receiver, slotObj); +} + /*! \reentrant This static function calls a slot after a given time interval. @@ -357,6 +411,129 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv } } +/*!\fn void QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method) + + \since 5.4 + + \overload + \reentrant + This static function calls a member function of a QObject after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The \a receiver is the receiving object and the \a method is the member function. The + time interval is \a msec milliseconds. + + If \a receiver is destroyed before the interval occurs, the method will not be called. + The function will be run in the thread of \a receiver. The receiver's thread must have + a running Qt event loop. + + \sa start() +*/ + +/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method) + + \since 5.4 + + \overload + \reentrant + This static function calls a member function of a QObject after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The \a receiver is the receiving object and the \a method is the member function. The + time interval is \a msec milliseconds. The \a timerType affects the + accuracy of the timer. + + If \a receiver is destroyed before the interval occurs, the method will not be called. + The function will be run in the thread of \a receiver. The receiver's thread must have + a running Qt event loop. + + \sa start() +*/ + +/*!\fn void QTimer::singleShot(int msec, Functor functor) + + \since 5.4 + + \overload + \reentrant + This static function calls \a functor after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The time interval is \a msec milliseconds. + + \sa start() +*/ + +/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor) + + \since 5.4 + + \overload + \reentrant + This static function calls \a functor after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The time interval is \a msec milliseconds. The \a timerType affects the + accuracy of the timer. + + \sa start() +*/ + +/*!\fn void QTimer::singleShot(int msec, const QObject *context, Functor functor) + + \since 5.4 + + \overload + \reentrant + This static function calls \a functor after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The time interval is \a msec milliseconds. + + If \a context is destroyed before the interval occurs, the method will not be called. + The function will be run in the thread of \a context. The context's thread must have + a running Qt event loop. + + \sa start() +*/ + +/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor) + + \since 5.4 + + \overload + \reentrant + This static function calls \a functor after a given time interval. + + It is very convenient to use this function because you do not need + to bother with a \l{QObject::timerEvent()}{timerEvent} or + create a local QTimer object. + + The time interval is \a msec milliseconds. The \a timerType affects the + accuracy of the timer. + + If \a context is destroyed before the interval occurs, the method will not be called. + The function will be run in the thread of \a context. The context's thread must have + a running Qt event loop. + + \sa start() +*/ + /*! \property QTimer::singleShot \brief whether the timer is a single-shot timer diff --git a/src/corelib/kernel/qtimer.h b/src/corelib/kernel/qtimer.h index 3484f4dba8c..6439070805d 100644 --- a/src/corelib/kernel/qtimer.h +++ b/src/corelib/kernel/qtimer.h @@ -81,6 +81,67 @@ public: static void singleShot(int msec, const QObject *receiver, const char *member); static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member); +#ifdef Q_QDOC + static void singleShot(int msec, const QObject *receiver, PointerToMemberFunction method); + static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method); + static void singleShot(int msec, Functor functor); + static void singleShot(int msec, Qt::TimerType timerType, Functor functor); + static void singleShot(int msec, const QObject *context, Functor functor); + static void singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor); +#else + // singleShot to a QObject slot + template + static inline void singleShot(int msec, const typename QtPrivate::FunctionPointer::Object *receiver, Func1 slot) + { + singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, slot); + } + template + static inline void singleShot(int msec, Qt::TimerType timerType, const typename QtPrivate::FunctionPointer::Object *receiver, + Func1 slot) + { + typedef QtPrivate::FunctionPointer SlotType; + + //compilation error if the slot has arguments. + Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) == 0, + "The slot must not have any arguments."); + + singleShotImpl(msec, timerType, receiver, + new QtPrivate::QSlotObject(slot)); + } + // singleShot to a functor or function pointer (without context) + template + static inline void singleShot(int msec, Func1 slot) + { + singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, Q_NULLPTR, slot); + } + template + static inline void singleShot(int msec, Qt::TimerType timerType, Func1 slot) + { + singleShot(msec, timerType, Q_NULLPTR, slot); + } + // singleShot to a functor or function pointer (with context) + template + static inline typename QtPrivate::QEnableIf::IsPointerToMemberFunction && + !QtPrivate::is_same::value, void>::Type + singleShot(int msec, QObject *context, Func1 slot) + { + singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, context, slot); + } + template + static inline typename QtPrivate::QEnableIf::IsPointerToMemberFunction && + !QtPrivate::is_same::value, void>::Type + singleShot(int msec, Qt::TimerType timerType, QObject *context, Func1 slot) + { + //compilation error if the slot has arguments. + typedef QtPrivate::FunctionPointer SlotType; + Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) <= 0, "The slot must not have any arguments."); + + singleShotImpl(msec, timerType, context, + new QtPrivate::QFunctorSlotObject::Value, void>(slot)); + } +#endif + public Q_SLOTS: void start(int msec); @@ -103,6 +164,9 @@ private: inline int startTimer(int){ return -1;} inline void killTimer(int){} + static void singleShotImpl(int msec, Qt::TimerType timerType, + const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj); + int id, inter, del; uint single : 1; uint nulltimer : 1; diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp index 27ea3faf815..31c627afcb5 100644 --- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp +++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp @@ -77,6 +77,7 @@ private slots: void cancelLongTimer(); void singleShotStaticFunctionZeroTimeout(); void recurseOnTimeoutAndStopTimer(); + void singleShotToFunctors(); void dontBlockEvents(); void postedEventsShouldNotStarveTimers(); @@ -586,6 +587,14 @@ void tst_QTimer::singleShotStaticFunctionZeroTimeout() QCOMPARE(helper.count, 1); QTest::qWait(500); QCOMPARE(helper.count, 1); + + TimerHelper nhelper; + + QTimer::singleShot(0, &nhelper, &TimerHelper::timeout); + QCoreApplication::processEvents(); + QCOMPARE(nhelper.count, 1); + QCoreApplication::processEvents(); + QCOMPARE(nhelper.count, 1); } class RecursOnTimeoutAndStopTimerTimer : public QObject @@ -631,6 +640,96 @@ void tst_QTimer::recurseOnTimeoutAndStopTimer() QVERIFY(!t.two->isActive()); } +struct CountedStruct +{ + CountedStruct(int *count, QThread *t = Q_NULLPTR) : count(count), thread(t) { } + ~CountedStruct() { } + void operator()() const { ++(*count); if (thread) QCOMPARE(QThread::currentThread(), thread); } + + int *count; + QThread *thread; +}; + +static QEventLoop _e; +static QThread *_t = Q_NULLPTR; + +class StaticEventLoop +{ +public: + static void quitEventLoop() { _e.quit(); if (_t) QCOMPARE(QThread::currentThread(), _t); } +}; + +void tst_QTimer::singleShotToFunctors() +{ + int count = 0; + QEventLoop e; + + QTimer::singleShot(0, CountedStruct(&count)); + QCoreApplication::processEvents(); + QCOMPARE(count, 1); + + QTimer::singleShot(0, &StaticEventLoop::quitEventLoop); + QCOMPARE(_e.exec(), 0); + + QThread t1; + QObject c1; + c1.moveToThread(&t1); + + QObject::connect(&t1, SIGNAL(started()), &e, SLOT(quit())); + t1.start(); + QCOMPARE(e.exec(), 0); + + QTimer::singleShot(0, &c1, CountedStruct(&count, &t1)); + QTest::qWait(500); + QCOMPARE(count, 2); + + t1.quit(); + t1.wait(); + + _t = new QThread; + QObject c2; + c2.moveToThread(_t); + + QObject::connect(_t, SIGNAL(started()), &e, SLOT(quit())); + _t->start(); + QCOMPARE(e.exec(), 0); + + QTimer::singleShot(0, &c2, &StaticEventLoop::quitEventLoop); + QCOMPARE(_e.exec(), 0); + + _t->quit(); + _t->wait(); + _t->deleteLater(); + _t = Q_NULLPTR; + + { + QObject c3; + QTimer::singleShot(500, &c3, CountedStruct(&count)); + } + QTest::qWait(800); + QCOMPARE(count, 2); + +#if defined(Q_COMPILER_LAMBDA) + QTimer::singleShot(0, [&count] { ++count; }); + QCoreApplication::processEvents(); + QCOMPARE(count, 3); + + QObject context; + QThread thread; + + context.moveToThread(&thread); + QObject::connect(&thread, SIGNAL(started()), &e, SLOT(quit())); + thread.start(); + QCOMPARE(e.exec(), 0); + + QTimer::singleShot(0, &context, [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); }); + QTest::qWait(500); + QCOMPARE(count, 4); + + thread.quit(); + thread.wait(); +#endif +} class DontBlockEvents : public QObject