Port Closure to variadic templates.

This commit is contained in:
John Maguire 2012-11-22 15:15:07 +01:00
parent d6b84558f6
commit 8171192df5
11 changed files with 185 additions and 249 deletions

View File

@ -19,80 +19,47 @@
#include <QTimer> #include <QTimer>
#include "core/logging.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
namespace _detail { namespace _detail {
Closure::Closure(QObject* sender, ClosureBase::ClosureBase(ObjectHelper* helper)
const char* signal, : helper_(helper) {
QObject* receiver,
const char* slot,
const ClosureArgumentWrapper* val0,
const ClosureArgumentWrapper* val1,
const ClosureArgumentWrapper* val2,
const ClosureArgumentWrapper* val3)
: QObject(receiver),
callback_(NULL),
val0_(val0),
val1_(val1),
val2_(val2),
val3_(val3) {
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);
Connect(sender, signal);
} }
Closure::Closure(QObject* sender, ClosureBase::~ClosureBase() {
const char* signal,
std::tr1::function<void()> callback)
: callback_(callback) {
Connect(sender, signal);
} }
Closure::~Closure() { ObjectHelper* ClosureBase::helper() const {
return helper_;
} }
void Closure::Connect(QObject* sender, const char* signal) { ObjectHelper::ObjectHelper(
bool success = connect(sender, signal, SLOT(Invoked())); QObject* sender,
Q_ASSERT(success); const char* signal,
success = connect(sender, SIGNAL(destroyed()), SLOT(Cleanup())); ClosureBase* closure)
Q_ASSERT(success); : closure_(closure) {
Q_UNUSED(success); connect(sender, signal, SLOT(Invoked()));
connect(sender, SIGNAL(destroyed()), SLOT(deleteLater()));
} }
void Closure::Invoked() { void ObjectHelper::Invoked() {
if (callback_) { closure_->Invoke();
callback_();
} else {
slot_.invoke(
parent(),
val0_ ? val0_->arg() : QGenericArgument(),
val1_ ? val1_->arg() : QGenericArgument(),
val2_ ? val2_->arg() : QGenericArgument(),
val3_ ? val3_->arg() : QGenericArgument());
}
deleteLater(); deleteLater();
} }
void Closure::Cleanup() { template<>
disconnect(); void Arg<boost::tuples::tuple<>>(
deleteLater(); const boost::tuples::tuple<>&,
} QList<QGenericArgument>* list) {}
template<>
void Arg<boost::tuples::null_type>(
const boost::tuples::null_type&,
QList<QGenericArgument>* list) {}
} // namespace _detail } // namespace _detail
_detail::Closure* NewClosure(
QObject* sender, const char* signal,
QObject* receiver, const char* slot) {
return new _detail::Closure(sender, signal, receiver, slot);
}
void DoAfter(QObject* receiver, const char* slot, int msec) { void DoAfter(QObject* receiver, const char* slot, int msec) {
QTimer::singleShot(msec, receiver, slot); QTimer::singleShot(msec, receiver, slot);
} }

View File

@ -18,211 +18,169 @@
#ifndef CLOSURE_H #ifndef CLOSURE_H
#define CLOSURE_H #define CLOSURE_H
#include <tr1/functional>
#include <QMetaMethod> #include <QMetaMethod>
#include <QObject> #include <QObject>
#include <QSharedPointer> #include <QSharedPointer>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include "core/logging.h"
namespace _detail { namespace _detail {
class ClosureArgumentWrapper { class ObjectHelper;
public:
virtual ~ClosureArgumentWrapper() {}
virtual QGenericArgument arg() const = 0; // Interface for ObjectHelper to call on signal emission.
class ClosureBase : boost::noncopyable {
public:
virtual ~ClosureBase();
virtual void Invoke() = 0;
// Tests only.
ObjectHelper* helper() const;
protected:
explicit ClosureBase(ObjectHelper*);
ObjectHelper* helper_;
}; };
template<typename T> // QObject helper as templated QObjects do not work.
class ClosureArgument : public ClosureArgumentWrapper { // Connects to the given signal and invokes the closure when called.
public: // Deletes itself and the Closure after being invoked.
explicit ClosureArgument(const T& data) : data_(data) {} class ObjectHelper : public QObject {
virtual QGenericArgument arg() const {
return Q_ARG(T, data_);
}
private:
T data_;
};
class Closure : public QObject, boost::noncopyable {
Q_OBJECT Q_OBJECT
public: public:
Closure(QObject* sender, const char* signal, ObjectHelper(
QObject* receiver, const char* slot, QObject* parent,
const ClosureArgumentWrapper* val0 = 0, const char* signal,
const ClosureArgumentWrapper* val1 = 0, ClosureBase* closure);
const ClosureArgumentWrapper* val2 = 0,
const ClosureArgumentWrapper* val3 = 0);
Closure(QObject* sender, const char* signal,
std::tr1::function<void()> callback);
virtual ~Closure();
private slots: private slots:
void Invoked(); void Invoked();
void Cleanup();
private: private:
void Connect(QObject* sender, const char* signal); boost::scoped_ptr<ClosureBase> closure_;
Q_DISABLE_COPY(ObjectHelper);
QMetaMethod slot_;
std::tr1::function<void()> callback_;
boost::scoped_ptr<const ClosureArgumentWrapper> val0_;
boost::scoped_ptr<const ClosureArgumentWrapper> val1_;
boost::scoped_ptr<const ClosureArgumentWrapper> val2_;
boost::scoped_ptr<const ClosureArgumentWrapper> val3_;
};
class SharedPointerWrapper {
public:
virtual ~SharedPointerWrapper() {}
virtual QObject* data() const = 0;
}; };
// Helper to unpack a tuple into a QList.
template<typename T> template<typename T>
class SharedPointer : public SharedPointerWrapper { void Arg(const T& tail, QList<QGenericArgument>* list) {
typedef decltype(tail.get_head()) HeadType;
list->append(Q_ARG(HeadType, tail.get_head()));
Arg(tail.get_tail(), list);
}
// Specialisation for starting with an empty tuple, ie. no arguments.
template<>
void Arg<boost::tuples::tuple<>>(
const boost::tuples::tuple<>&,
QList<QGenericArgument>* list);
// Specialisation for the end of a tuple, where get_tail() returns null_type.
template<>
void Arg<boost::tuples::null_type>(
const boost::tuples::null_type&,
QList<QGenericArgument>* list);
template <typename... Args>
class Closure : public ClosureBase {
public: public:
explicit SharedPointer(QSharedPointer<T> ptr) Closure(
: ptr_(ptr) { QObject* sender,
const char* signal,
QObject* receiver,
const char* slot,
const Args&... args)
: ClosureBase(new ObjectHelper(sender, signal, this)),
// boost::bind is the easiest way to store an argument list.
function_(boost::bind(&Closure<Args...>::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* data() const { virtual void Invoke() {
return ptr_.data(); function_();
} }
private: private:
QSharedPointer<T> ptr_; void Call(const Args&... args) {
auto t = boost::make_tuple(args...);
QList<QGenericArgument> arg_list;
Arg(t, &arg_list);
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());
}
boost::function<void()> function_;
QObject* receiver_;
QMetaMethod slot_;
}; };
// For use with a QSharedPointer as a sender. template <typename T, typename... Args>
class SharedClosure : public Closure { class SharedClosure : public Closure<Args...> {
Q_OBJECT
public: public:
SharedClosure(SharedPointerWrapper* sender, const char* signal, SharedClosure(
QObject* receiver, const char* slot, QSharedPointer<T> sender,
const ClosureArgumentWrapper* val0 = 0, const char* signal,
const ClosureArgumentWrapper* val1 = 0, QObject* receiver,
const ClosureArgumentWrapper* val2 = 0, const char* slot,
const ClosureArgumentWrapper* val3 = 0) const Args&... args)
: Closure(sender->data(), signal, : Closure<Args...>(
receiver, slot, sender.data(),
val0, val1, val2, val3), signal,
shared_sender_(sender) { receiver,
slot,
args...),
data_(sender) {
} }
private: private:
boost::scoped_ptr<SharedPointerWrapper> shared_sender_; QSharedPointer<T> data_;
}; };
} // namespace _detail } // namespace _detail
#define C_ARG(type, data) new _detail::ClosureArgument<type>(data) template <typename... Args>
_detail::ClosureBase* NewClosure(
_detail::Closure* NewClosure(
QObject* sender,
const char* signal,
QObject* receiver,
const char* slot);
template <typename T>
_detail::Closure* NewClosure(
QObject* sender, QObject* sender,
const char* signal, const char* signal,
QObject* receiver, QObject* receiver,
const char* slot, const char* slot,
const T& val0) { const Args&... args) {
return new _detail::Closure( return new _detail::Closure<Args...>(
sender, signal, receiver, slot, sender, signal, receiver, slot, args...);
C_ARG(T, val0));
} }
template <typename T0, typename T1> // QSharedPointer variant
_detail::Closure* NewClosure( template <typename T, typename... Args>
QObject* sender, _detail::ClosureBase* NewClosure(
QSharedPointer<T> sender,
const char* signal, const char* signal,
QObject* receiver, QObject* receiver,
const char* slot, const char* slot,
const T0& val0, const Args&... args) {
const T1& val1) { return new _detail::SharedClosure<T, Args...>(
return new _detail::Closure( sender, signal, receiver, slot, args...);
sender, signal, receiver, slot,
C_ARG(T0, val0), C_ARG(T1, val1));
} }
template <typename T0, typename T1, typename T2>
_detail::Closure* NewClosure(
QObject* sender,
const char* signal,
QObject* receiver,
const char* slot,
const T0& val0,
const T1& val1,
const T2& val2) {
return new _detail::Closure(
sender, signal, receiver, slot,
C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2));
}
template <typename T0, typename T1, typename T2, typename T3>
_detail::Closure* NewClosure(
QObject* sender,
const char* signal,
QObject* receiver,
const char* slot,
const T0& val0,
const T1& val1,
const T2& val2,
const T3& val3) {
return new _detail::Closure(
sender, signal, receiver, slot,
C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2), C_ARG(T3, val3));
}
template <typename TP>
_detail::Closure* NewClosure(
QSharedPointer<TP> sender,
const char* signal,
QObject* receiver,
const char* slot) {
return new _detail::SharedClosure(
new _detail::SharedPointer<TP>(sender), signal, receiver, slot);
}
template <typename TP, typename T0>
_detail::Closure* NewClosure(
QSharedPointer<TP> sender,
const char* signal,
QObject* receiver,
const char* slot,
const T0& val0) {
return new _detail::SharedClosure(
new _detail::SharedPointer<TP>(sender), signal, receiver, slot,
C_ARG(T0, val0));
}
template <typename TP, typename T0, typename T1>
_detail::Closure* NewClosure(
QSharedPointer<TP> sender,
const char* signal,
QObject* receiver,
const char* slot,
const T0& val0,
const T1& val1) {
return new _detail::SharedClosure(
new _detail::SharedPointer<TP>(sender), signal, receiver, slot,
C_ARG(T0, val0), C_ARG(T1, val1));
}
void DoAfter(QObject* receiver, const char* slot, int msec); void DoAfter(QObject* receiver, const char* slot, int msec);
void DoInAMinuteOrSo(QObject* receiver, const char* slot); void DoInAMinuteOrSo(QObject* receiver, const char* slot);

View File

@ -1,27 +1,29 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com> Copyright 2012, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Clementine is distributed in the hope that it will be useful, Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "googledriveclient.h" #include "googledriveclient.h"
#include "oauthenticator.h"
#include "core/closure.h"
#include "core/network.h"
#include <qjson/parser.h> #include <qjson/parser.h>
#include "oauthenticator.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
using namespace google_drive; using namespace google_drive;
const char* File::kFolderMimeType = "application/vnd.google-apps.folder"; const char* File::kFolderMimeType = "application/vnd.google-apps.folder";

View File

@ -12,6 +12,7 @@
#include <qjson/parser.h> #include <qjson/parser.h>
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h"
namespace { namespace {

View File

@ -16,11 +16,8 @@
*/ */
#include "moodbarloader.h" #include "moodbarloader.h"
#include "moodbarpipeline.h"
#include "core/application.h" #include <boost/scoped_ptr.hpp>
#include "core/closure.h"
#include "core/qhash_qurl.h"
#include "core/utilities.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
@ -30,7 +27,12 @@
#include <QThread> #include <QThread>
#include <QUrl> #include <QUrl>
#include <boost/scoped_ptr.hpp> #include "moodbarpipeline.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/qhash_qurl.h"
#include "core/utilities.h"
MoodbarLoader::MoodbarLoader(Application* app, QObject* parent) MoodbarLoader::MoodbarLoader(Application* app, QObject* parent)
: QObject(parent), : QObject(parent),

View File

@ -189,7 +189,7 @@ void GPodderSync::DeviceUpdatesFinished(mygpo::DeviceUpdatesPtr reply) {
// have a list of the episodes. // have a list of the episodes.
PodcastUrlLoaderReply* loader_reply = loader_->Load(url); PodcastUrlLoaderReply* loader_reply = loader_->Load(url);
NewClosure(loader_reply, SIGNAL(Finished(bool)), NewClosure(loader_reply, SIGNAL(Finished(bool)),
this, SLOT(NewPodcastLoaded(PodcastUrlLoaderReply*,QUrl,QList<QSharedPointer<mygpo::Episode> >)), this, SLOT(NewPodcastLoaded(PodcastUrlLoaderReply*,QUrl,QList<mygpo::EpisodePtr>)),
loader_reply, url, episodes_by_podcast[url]); loader_reply, url, episodes_by_podcast[url]);
} }

View File

@ -1,28 +1,30 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com> Copyright 2012, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Clementine is distributed in the hope that it will be useful, Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "podcastparser.h"
#include "podcasturlloader.h" #include "podcasturlloader.h"
#include "core/closure.h"
#include "core/network.h"
#include "core/utilities.h"
#include <QNetworkReply> #include <QNetworkReply>
#include "podcastparser.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/utilities.h"
const int PodcastUrlLoader::kMaxRedirects = 5; const int PodcastUrlLoader::kMaxRedirects = 5;

View File

@ -26,6 +26,7 @@
#include <qjson/parser.h> #include <qjson/parser.h>
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h"
#include "songkickconcertwidget.h" #include "songkickconcertwidget.h"
const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick"; const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick";

View File

@ -15,18 +15,20 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "songinfotextview.h"
#include "songkickconcertwidget.h" #include "songkickconcertwidget.h"
#include "ui_songkickconcertwidget.h"
#include "core/closure.h"
#include "core/network.h"
#include "core/utilities.h"
#include <QDate> #include <QDate>
#include <QDesktopServices> #include <QDesktopServices>
#include <QMouseEvent> #include <QMouseEvent>
#include <QTextDocument> #include <QTextDocument>
#include "songinfotextview.h"
#include "ui_songkickconcertwidget.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/utilities.h"
const int SongKickConcertWidget::kStaticMapWidth = 100; const int SongKickConcertWidget::kStaticMapWidth = 100;
const int SongKickConcertWidget::kStaticMapHeight = 100; const int SongKickConcertWidget::kStaticMapHeight = 100;

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -U__STRICT_ANSI__ -fpermissive") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -fpermissive -U__STRICT_ANSI__")
if(USE_SYSTEM_GMOCK) if(USE_SYSTEM_GMOCK)
include_directories(${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) include_directories(${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})

View File

@ -11,7 +11,7 @@
TEST(ClosureTest, ClosureInvokesReceiver) { TEST(ClosureTest, ClosureInvokesReceiver) {
TestQObject sender; TestQObject sender;
TestQObject receiver; TestQObject receiver;
_detail::Closure* closure = NewClosure( _detail::ClosureBase* closure = NewClosure(
&sender, SIGNAL(Emitted()), &sender, SIGNAL(Emitted()),
&receiver, SLOT(Invoke())); &receiver, SLOT(Invoke()));
EXPECT_EQ(0, receiver.invoked()); EXPECT_EQ(0, receiver.invoked());
@ -22,17 +22,18 @@ TEST(ClosureTest, ClosureInvokesReceiver) {
TEST(ClosureTest, ClosureDeletesSelf) { TEST(ClosureTest, ClosureDeletesSelf) {
TestQObject sender; TestQObject sender;
TestQObject receiver; TestQObject receiver;
_detail::Closure* closure = NewClosure( _detail::ClosureBase* closure = NewClosure(
&sender, SIGNAL(Emitted()), &sender, SIGNAL(Emitted()),
&receiver, SLOT(Invoke())); &receiver, SLOT(Invoke()));
QSignalSpy spy(closure, SIGNAL(destroyed())); _detail::ObjectHelper* helper = closure->helper();
QSignalSpy spy(helper, SIGNAL(destroyed()));
EXPECT_EQ(0, receiver.invoked()); EXPECT_EQ(0, receiver.invoked());
sender.Emit(); sender.Emit();
EXPECT_EQ(1, receiver.invoked()); EXPECT_EQ(1, receiver.invoked());
EXPECT_EQ(0, spy.count()); EXPECT_EQ(0, spy.count());
QEventLoop loop; QEventLoop loop;
QObject::connect(closure, SIGNAL(destroyed()), &loop, SLOT(quit())); QObject::connect(helper, SIGNAL(destroyed()), &loop, SLOT(quit()));
loop.exec(); loop.exec();
EXPECT_EQ(1, spy.count()); EXPECT_EQ(1, spy.count());
} }
@ -41,23 +42,23 @@ TEST(ClosureTest, ClosureDoesNotCrashWithSharedPointerSender) {
TestQObject receiver; TestQObject receiver;
TestQObject* sender; TestQObject* sender;
boost::scoped_ptr<QSignalSpy> spy; boost::scoped_ptr<QSignalSpy> spy;
QPointer<_detail::Closure> closure; QPointer<_detail::ObjectHelper> closure;
{ {
QSharedPointer<TestQObject> sender_shared(new TestQObject); QSharedPointer<TestQObject> sender_shared(new TestQObject);
sender = sender_shared.data(); sender = sender_shared.data();
closure = QPointer<_detail::Closure>(NewClosure( closure = QPointer<_detail::ObjectHelper>(NewClosure(
sender_shared, SIGNAL(Emitted()), sender_shared, SIGNAL(Emitted()),
&receiver, SLOT(Invoke()))); &receiver, SLOT(Invoke()))->helper());
spy.reset(new QSignalSpy(sender, SIGNAL(destroyed()))); spy.reset(new QSignalSpy(sender, SIGNAL(destroyed())));
} }
EXPECT_EQ(0, receiver.invoked()); ASSERT_EQ(0, receiver.invoked());
sender->Emit(); sender->Emit();
EXPECT_EQ(1, receiver.invoked()); ASSERT_EQ(1, receiver.invoked());
EXPECT_EQ(0, spy->count()); ASSERT_EQ(0, spy->count());
QEventLoop loop; QEventLoop loop;
QObject::connect(sender, SIGNAL(destroyed()), &loop, SLOT(quit())); QObject::connect(sender, SIGNAL(destroyed()), &loop, SLOT(quit()));
loop.exec(); loop.exec();
EXPECT_EQ(1, spy->count()); ASSERT_EQ(1, spy->count());
EXPECT_TRUE(closure.isNull()); EXPECT_TRUE(closure.isNull());
} }