/****************************************************************************** * Copyright (C) 2019 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "util.h" #include namespace Quotient { namespace _impl { template using decorated_slot_tt = std::function; template inline QMetaObject::Connection connectDecorated(SenderT* sender, SignalT signal, ContextT* context, decorated_slot_tt decoratedSlot, Qt::ConnectionType connType) { // See https://bugreports.qt.io/browse/QTBUG-60339 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) auto pc = std::make_shared(); #else auto pc = std::make_unique(); #endif auto& c = *pc; // Resolve a reference before pc is moved to lambda // Perfect forwarding doesn't work through signal-slot connections - // arguments are always copied (at best - COWed) to the context of // the slot. Therefore the slot decorator receives const ArgTs&... // rather than ArgTs&&... // TODO: std::bind_front() instead of lambda. c = QObject::connect(sender, signal, context, [pc = std::move(pc), decoratedSlot = std::move(decoratedSlot)](const ArgTs&... args) { Q_ASSERT(*pc); // If it's been triggered, it should exist decoratedSlot(*pc, args...); }, connType); return c; } template inline QMetaObject::Connection connectUntil(SenderT* sender, SignalT signal, ContextT* context, std::function functor, Qt::ConnectionType connType) { return connectDecorated(sender, signal, context, decorated_slot_tt( [functor = std::move(functor)](QMetaObject::Connection& c, const ArgTs&... args) { if (functor(args...)) QObject::disconnect(c); }), connType); } template inline QMetaObject::Connection connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, std::function slot, Qt::ConnectionType connType) { return connectDecorated(sender, signal, context, decorated_slot_tt( [slot = std::move(slot)](QMetaObject::Connection& c, const ArgTs&... args) { QObject::disconnect(c); slot(args...); }), connType); } } // namespace _impl /// Create a connection that self-disconnects when its "slot" returns true /*! A slot accepted by connectUntil() is different from classic Qt slots * in that its return value must be bool, not void. The slot's return value * controls whether the connection should be kept; if the slot returns false, * the connection remains; upon returning true, the slot is disconnected from * the signal. Because of a different slot signature connectUntil() doesn't * accept member functions as QObject::connect or Quotient::connectSingleShot * do; you should pass a lambda or a pre-bound member function to it. */ template inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { return _impl::connectUntil(sender, signal, context, std::function(slot), connType); } /// Create a connection that self-disconnects after triggering on the signal template inline auto connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { return _impl::connectSingleShot( sender, signal, context, std::function(slot), connType); } // Specialisation for usual Qt slots passed as pointers-to-members. template inline auto connectSingleShot(SenderT* sender, SignalT signal, ReceiverT* receiver, void (SlotObjectT::*slot)(ArgTs...), Qt::ConnectionType connType = Qt::AutoConnection) { // TODO: when switching to C++20, use std::bind_front() instead return _impl::connectSingleShot(sender, signal, receiver, std::function( [receiver, slot](const ArgTs&... args) { (receiver->*slot)(args...); }), connType); } /// A guard pointer that disconnects an interested object upon destruction /*! It's almost QPointer<> except that you have to initialise it with one * more additional parameter - a pointer to a QObject that will be * disconnected from signals of the underlying pointer upon the guard's * destruction. Note that destructing the guide doesn't destruct either QObject. */ template class ConnectionsGuard : public QPointer { public: ConnectionsGuard(T* publisher, QObject* subscriber) : QPointer(publisher), subscriber(subscriber) {} ~ConnectionsGuard() { if (*this) (*this)->disconnect(subscriber); } ConnectionsGuard(ConnectionsGuard&&) = default; ConnectionsGuard& operator=(ConnectionsGuard&&) = default; Q_DISABLE_COPY(ConnectionsGuard) using QPointer::operator=; private: QObject* subscriber; }; } // namespace Quotient