/* This file is part of Clementine. Copyright 2011, David Sansome Clementine 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. Clementine 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 Clementine. If not, see . */ #ifndef CLOSURE_H #define CLOSURE_H #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); public slots: void TearDown(); private slots: void Invoked(); private: QMetaObject::Connection connection_; 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); // Use a direct connection to insure that this is called immediately when // the sender is destroyed. This will run on the signal thread, so TearDown // must be thread safe. QObject::connect(receiver_, SIGNAL(destroyed()), helper_, SLOT(TearDown()), Qt::DirectConnection); } 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; QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); watcher->setFuture(future); return NewClosure(watcher, SIGNAL(finished()), receiver, slot, args...); } template _detail::ClosureBase* NewClosure(QFuture future, const F& callback, const Args&... args) { QFutureWatcher* watcher = new QFutureWatcher; QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); watcher->setFuture(future); 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