/* This file is part of Strawberry. Copyright 2011, David Sansome Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Strawberry is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Strawberry. If not, see . */ #ifndef CLOSURE_H #define CLOSURE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace _detail { class ObjectHelper; // Interface for ObjectHelper to call on signal emission. class ClosureBase { public: virtual ~ClosureBase(); virtual void Invoke() = 0; // Tests only. ObjectHelper *helper() const; protected: explicit ClosureBase(ObjectHelper*); ObjectHelper *helper_; private: Q_DISABLE_COPY(ClosureBase); }; // QObject helper as templated QObjects do not work. // Connects to the given signal and invokes the closure when called. // Deletes itself and the Closure after being invoked. class ObjectHelper : public QObject { Q_OBJECT public: ObjectHelper(QObject *parent, const char *signal, ClosureBase *closure); private slots: void Invoked(); private: std::unique_ptr closure_; Q_DISABLE_COPY(ObjectHelper); }; // Helpers for unpacking a variadic template list. // Base case of no arguments. void Unpack(QList*); template void Unpack(QList *list, const Arg &arg) { list->append(Q_ARG(Arg, arg)); } template void Unpack(QList *list, const Head &head, const Tail&... tail) { Unpack(list, head); Unpack(list, tail...); } template class Closure : public ClosureBase { public: Closure( QObject *sender, const char *signal, QObject *receiver, const char *slot, const Args&... args) : ClosureBase(new ObjectHelper(sender, signal, this)), // std::bind is the easiest way to store an argument list. function_(std::bind(&Closure::Call, this, args...)), receiver_(receiver) { const QMetaObject *meta_receiver = receiver->metaObject(); QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1); const int index = meta_receiver->indexOfSlot(normalised_slot.constData()); Q_ASSERT(index != -1); slot_ = meta_receiver->method(index); QObject::connect(receiver_, SIGNAL(destroyed()), helper_, SLOT(deleteLater())); } virtual void Invoke() { function_(); } private: void Call(const Args&... args) { QList arg_list; Unpack(&arg_list, args...); slot_.invoke( receiver_, arg_list.size() > 0 ? arg_list[0] : QGenericArgument(), arg_list.size() > 1 ? arg_list[1] : QGenericArgument(), arg_list.size() > 2 ? arg_list[2] : QGenericArgument(), arg_list.size() > 3 ? arg_list[3] : QGenericArgument(), arg_list.size() > 4 ? arg_list[4] : QGenericArgument(), arg_list.size() > 5 ? arg_list[5] : QGenericArgument(), arg_list.size() > 6 ? arg_list[6] : QGenericArgument(), arg_list.size() > 7 ? arg_list[7] : QGenericArgument(), arg_list.size() > 8 ? arg_list[8] : QGenericArgument(), arg_list.size() > 9 ? arg_list[9] : QGenericArgument()); } std::function function_; QObject *receiver_; QMetaMethod slot_; }; template class SharedClosure : public Closure { public: SharedClosure( QSharedPointer sender, const char *signal, QObject *receiver, const char *slot, const Args&... args) : Closure( sender.data(), signal, receiver, slot, args...), data_(sender) { } private: QSharedPointer data_; }; class CallbackClosure : public ClosureBase { public: CallbackClosure(QObject *sender, const char *signal, std::function callback); virtual void Invoke(); private: std::function callback_; }; } // namespace _detail template _detail::ClosureBase *NewClosure( QObject *sender, const char *signal, QObject *receiver, const char *slot, const Args&... args) { return new _detail::Closure( sender, signal, receiver, slot, args...); } // QSharedPointer variant template _detail::ClosureBase *NewClosure( QSharedPointer sender, const char *signal, QObject *receiver, const char *slot, const Args&... args) { return new _detail::SharedClosure( sender, signal, receiver, slot, args...); } _detail::ClosureBase *NewClosure(QObject *sender, const char *signal, std::function callback); template _detail::ClosureBase *NewClosure(QObject *sender, const char *signal, std::function callback, const Args&... args) { return NewClosure(sender, signal, std::bind(callback, args...)); } template _detail::ClosureBase *NewClosure( QObject *sender, const char *signal, void (*callback)(Args...), const Args&... args) { return NewClosure(sender, signal, std::bind(callback, args...)); } template _detail::ClosureBase *NewClosure( QObject *sender, const char *signal, T *receiver, Unused (T::*callback)(Args...), const Args&... args) { return NewClosure(sender, signal, std::bind(callback, receiver, args...)); } template _detail::ClosureBase *NewClosure(QFuture future, QObject *receiver, const char *slot, const Args&... args) { QFutureWatcher *watcher = new QFutureWatcher; watcher->setFuture(future); QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return NewClosure(watcher, SIGNAL(finished()), receiver, slot, args...); } template _detail::ClosureBase *NewClosure(QFuture future, const F &callback, const Args&... args) { QFutureWatcher *watcher = new QFutureWatcher; watcher->setFuture(future); QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return NewClosure(watcher, SIGNAL(finished()), callback, args...); } void DoAfter(QObject *receiver, const char *slot, int msec); void DoAfter(std::function callback, std::chrono::milliseconds msec); void DoInAMinuteOrSo(QObject *receiver, const char *slot); template void DoAfter(std::function callback, std::chrono::duration duration) { QTimer *timer = new QTimer; timer->setSingleShot(true); NewClosure(timer, SIGNAL(timeout()), callback); QObject::connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater())); std::chrono::milliseconds msec = std::chrono::duration_cast(duration); timer->start(msec.count()); } #endif // CLOSURE_H