KDDockWidgets API Documentation 2.0
Loading...
Searching...
No Matches
signal.h
Go to the documentation of this file.
1/*
2 This file is part of KDBindings.
3
4 SPDX-FileCopyrightText: 2021-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Sean Harmer <sean.harmer@kdab.com>
6
7 SPDX-License-Identifier: MIT
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
12#pragma once
13
14#include <assert.h>
15#include <functional>
16#include <memory>
17#include <optional>
18#include <stdexcept>
19#include <type_traits>
20#include <utility>
21
23#include <kdbindings/utils.h>
24
30namespace KDBindings {
31
32template<typename... Args>
33class Signal;
34
35namespace Private {
36//
37// This class defines a virtual interface, that the Signal this ConnectionHandle refers
38// to must implement.
39// It allows ConnectionHandle to refer to this non-template class, which then dispatches
40// to the template implementation using virtual function calls.
41// It allows ConnectionHandle to be a non-template class.
43{
44public:
45 SignalImplBase() = default;
46
47 virtual ~SignalImplBase() = default;
48
49 virtual void disconnect(const GenerationalIndex &id) = 0;
50 virtual bool blockConnection(const GenerationalIndex &id, bool blocked) = 0;
51 virtual bool isConnectionActive(const GenerationalIndex &id) const = 0;
52 virtual bool isConnectionBlocked(const GenerationalIndex &id) const = 0;
53};
54
55} // namespace Private
56
68{
69public:
75 ConnectionHandle() = default;
76
82
88
102 {
103 if (auto shared_impl = checkedLock()) {
104 shared_impl->disconnect(m_id);
105 }
106 // ConnectionHandle is no longer active;
107 m_signalImpl.reset();
108 }
109
116 bool isActive() const
117 {
118 return static_cast<bool>(checkedLock());
119 }
120
137 bool block(bool blocked)
138 {
139 if (auto shared_impl = checkedLock()) {
140 return shared_impl->blockConnection(m_id, blocked);
141 }
142 throw std::out_of_range("Cannot block a non-active connection!");
143 }
144
152 bool isBlocked() const
153 {
154 if (auto shared_impl = checkedLock()) {
155 return shared_impl->isConnectionBlocked(m_id);
156 }
157 throw std::out_of_range("Cannot check whether a non-active connection is blocked!");
158 }
159
165 template<typename... Args>
166 bool belongsTo(const Signal<Args...> &signal) const
167 {
168 auto shared_impl = m_signalImpl.lock();
169 return shared_impl && shared_impl == std::static_pointer_cast<Private::SignalImplBase>(signal.m_impl);
170 }
171
172private:
173 template<typename...>
174 friend class Signal;
175
176 std::weak_ptr<Private::SignalImplBase> m_signalImpl;
178
179 // private, so it is only available from Signal
180 ConnectionHandle(std::weak_ptr<Private::SignalImplBase> signalImpl, Private::GenerationalIndex id)
181 : m_signalImpl{ std::move(signalImpl) }, m_id{ std::move(id) }
182 {
183 }
184
185 // Checks that the weak_ptr can be locked and that the connection is
186 // still active
187 std::shared_ptr<Private::SignalImplBase> checkedLock() const
188 {
189 auto shared_impl = m_signalImpl.lock();
190 if (shared_impl && shared_impl->isConnectionActive(m_id)) {
191 return shared_impl;
192 }
193 return nullptr;
194 }
195};
196
216template<typename... Args>
218{
219 static_assert(
220 std::conjunction<std::negation<std::is_rvalue_reference<Args>>...>::value,
221 "R-value references are not allowed as Signal parameters!");
222
223 // The Signal::Impl class exists, so Signals can be implemented in a PIMPL-like way.
224 // This allows us to easily move Signals without losing their ConnectionHandles, as well as
225 // making an unconnected Signal only sizeof(shared_ptr).
226 class Impl : public Private::SignalImplBase
227 {
228 public:
229 Impl() noexcept { }
230
231 ~Impl() noexcept { }
232
233 // Signal::Impls are not copyable
234 Impl(Impl const &other) = delete;
235 Impl &operator=(Impl const &other) = delete;
236
237 // Signal::Impls are not moveable, this would break the ConnectionHandles
238 Impl(Impl &&other) = delete;
239 Impl &operator=(Impl &&other) = delete;
240
241 // Connects a std::function to the signal. The returned
242 // value can be used to disconnect the function again.
243 Private::GenerationalIndex connect(std::function<void(Args...)> const &slot)
244 {
245 return m_connections.insert({ slot });
246 }
247
248 // Disconnects a previously connected function
249 void disconnect(const Private::GenerationalIndex &id) override
250 {
251 m_connections.erase(id);
252 }
253
254 // Disconnects all previously connected functions
255 void disconnectAll()
256 {
257 m_connections.clear();
258 }
259
260 bool blockConnection(const Private::GenerationalIndex &id, bool blocked) override
261 {
262 Connection *connection = m_connections.get(id);
263 if (connection) {
264 const bool wasBlocked = connection->blocked;
265 connection->blocked = blocked;
266 return wasBlocked;
267 } else {
268 throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
269 }
270 }
271
272 bool isConnectionActive(const Private::GenerationalIndex &id) const override
273 {
274 return m_connections.get(id);
275 }
276
277 bool isConnectionBlocked(const Private::GenerationalIndex &id) const override
278 {
279 auto connection = m_connections.get(id);
280 if (connection) {
281 return connection->blocked;
282 } else {
283 throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
284 }
285 }
286
287 // Calls all connected functions
288 void emit(Args... p) const
289 {
290 const auto numEntries = m_connections.entriesSize();
291
292 // This loop can tolerate signal handles being disconnected inside a slot,
293 // but adding new connections to a signal inside a slot will still be undefined behaviour
294 for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) {
295 const auto index = m_connections.indexAtEntry(i);
296
297 if (index) {
298 const auto con = m_connections.get(*index);
299
300 if (!con->blocked)
301 con->slot(p...);
302 }
303 }
304 }
305
306 private:
307 struct Connection {
308 std::function<void(Args...)> slot;
309 bool blocked{ false };
310 };
312 };
313
314public:
316 Signal() = default;
317
321 Signal(const Signal &) = delete;
322 Signal &operator=(Signal const &other) = delete;
323
325 Signal(Signal &&other) noexcept = default;
326 Signal &operator=(Signal &&other) noexcept = default;
327
335 {
337 }
338
348 ConnectionHandle connect(std::function<void(Args...)> const &slot)
349 {
350 ensureImpl();
351
352 return ConnectionHandle{ m_impl, m_impl->connect(slot) };
353 }
354
385 // The enable_if_t makes sure that this connect function specialization is only
386 // available if we provide a function that cannot be otherwise converted to a
387 // std::function<void(Args...)>, as it otherwise tries to take precedence
388 // over the normal connect function.
389 template<typename Func, typename... FuncArgs, typename = std::enable_if_t<std::disjunction_v<std::negation<std::is_convertible<Func, std::function<void(Args...)>>>, std::integral_constant<bool, sizeof...(FuncArgs) /*Also enable this function if we want to bind at least one argument*/>>>>
390 ConnectionHandle connect(Func &&slot, FuncArgs &&...args)
391 {
392 std::function<void(Args...)> bound = Private::bind_first(std::forward<Func>(slot), std::forward<FuncArgs>(args)...);
393 return connect(bound);
394 }
395
405 void disconnect(const ConnectionHandle &handle)
406 {
407 if (m_impl && handle.belongsTo(*this)) {
408 m_impl->disconnect(handle.m_id);
409 // TODO check if Impl is now empty and reset
410 } else {
411 throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
412 }
413 }
414
422 {
423 if (m_impl) {
424 m_impl->disconnectAll();
425 // Once all connections are disconnected, we can release ownership of the Impl.
426 // This does not destroy the Signal itself, just the Impl object.
427 // If another slot is connected, another Impl object will be constructed.
428 m_impl.reset();
429 }
430 // If m_impl is nullptr, we don't have any connections to disconnect
431 }
432
450 bool blockConnection(const ConnectionHandle &handle, bool blocked)
451 {
452 if (m_impl && handle.belongsTo(*this)) {
453 return m_impl->blockConnection(handle.m_id, blocked);
454 } else {
455 throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
456 }
457 }
458
468 bool isConnectionBlocked(const ConnectionHandle &handle) const
469 {
470 assert(handle.belongsTo(*this));
471 if (!m_impl) {
472 throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
473 }
474
475 return m_impl->isConnectionBlocked(handle.m_id);
476 }
477
491 void emit(Args... p) const
492 {
493 if (m_impl)
494 m_impl->emit(p...);
495
496 // if m_impl is nullptr, we don't have any slots connected, don't bother emitting
497 }
498
499private:
500 friend class ConnectionHandle;
501
502 void ensureImpl()
503 {
504 if (!m_impl) {
505 m_impl = std::make_shared<Impl>();
506 }
507 }
508
509 // shared_ptr is used here instead of unique_ptr, so ConnectionHandle instances can
510 // use a weak_ptr to check if the Signal::Impl they reference is still alive.
511 //
512 // This makes Signals easily copyable in theory, but the semantics of this are unclear.
513 // Copying could either simply copy the shared_ptr, which means the copy would share
514 // the connections of the original, which is possibly unintuitive, or the Impl would
515 // have to be copied as well.
516 // This would however leave connections without handles to disconnect them.
517 // So copying is forbidden for now.
518 //
519 // Think of this shared_ptr more like a unique_ptr with additional weak_ptr's
520 // in ConnectionHandle that can check whether the Impl object is still alive.
521 mutable std::shared_ptr<Impl> m_impl;
522};
523
533{
534public:
541 ScopedConnection() = default;
542
545
550
553 {
554 m_connection.disconnect();
555 m_connection = std::move(other.m_connection);
556 return *this;
557 }
558
563 : m_connection(std::move(h))
564 {
565 }
566
571 {
572 return *this = ScopedConnection(std::move(h));
573 }
574
579 {
580 return m_connection;
581 }
582
587 {
588 return m_connection;
589 }
590
595 {
596 return &m_connection;
597 }
598
603 {
604 return &m_connection;
605 }
606
611 {
612 m_connection.disconnect();
613 }
614
615private:
616 ConnectionHandle m_connection;
617};
618
631{
632public:
639 explicit ConnectionBlocker(const ConnectionHandle &handle)
640 : m_handle{ handle }
641 {
642 m_wasBlocked = m_handle.block(true);
643 }
644
650 {
651 m_handle.block(m_wasBlocked);
652 }
653
654private:
655 ConnectionHandle m_handle;
656 bool m_wasBlocked{ false };
657};
658
720} // namespace KDBindings
A ConnectionBlocker is a convenient RAII-style mechanism for temporarily blocking a connection.
Definition signal.h:631
ConnectionBlocker(const ConnectionHandle &handle)
Definition signal.h:639
A ConnectionHandle represents the connection of a Signal to a slot (i.e. a function that is called wh...
Definition signal.h:68
ConnectionHandle(ConnectionHandle &&)=default
ConnectionHandle & operator=(const ConnectionHandle &)=default
bool belongsTo(const Signal< Args... > &signal) const
Definition signal.h:166
ConnectionHandle(const ConnectionHandle &)=default
bool block(bool blocked)
Definition signal.h:137
ConnectionHandle & operator=(ConnectionHandle &&)=default
virtual bool isConnectionBlocked(const GenerationalIndex &id) const =0
virtual bool blockConnection(const GenerationalIndex &id, bool blocked)=0
virtual bool isConnectionActive(const GenerationalIndex &id) const =0
virtual void disconnect(const GenerationalIndex &id)=0
A ScopedConnection is a RAII-style way to make sure a Connection is disconnected.
Definition signal.h:533
ConnectionHandle * operator->()
Definition signal.h:594
ScopedConnection()=default
A ScopedConnection can be default constructed.
ScopedConnection(const ScopedConnection &)=delete
ScopedConnection & operator=(ScopedConnection &&other)
Definition signal.h:552
ConnectionHandle & handle()
Definition signal.h:578
const ConnectionHandle * operator->() const
Definition signal.h:602
ScopedConnection(ScopedConnection &&)=default
ScopedConnection & operator=(ConnectionHandle &&h)
Definition signal.h:570
ScopedConnection & operator=(const ScopedConnection &)=delete
ScopedConnection(ConnectionHandle &&h)
Definition signal.h:562
const ConnectionHandle & handle() const
Definition signal.h:586
A Signal provides a mechanism for communication between objects.
Definition signal.h:218
ConnectionHandle connect(Func &&slot, FuncArgs &&...args)
Definition signal.h:390
void disconnectAll()
Definition signal.h:421
Signal(Signal &&other) noexcept=default
bool isConnectionBlocked(const ConnectionHandle &handle) const
Definition signal.h:468
void disconnect(const ConnectionHandle &handle)
Definition signal.h:405
bool blockConnection(const ConnectionHandle &handle, bool blocked)
Definition signal.h:450
Signal & operator=(Signal const &other)=delete
Signal & operator=(Signal &&other) noexcept=default
void emit(Args... p) const
Definition signal.h:491
Signal(const Signal &)=delete
friend class ConnectionHandle
Definition signal.h:500
ConnectionHandle connect(std::function< void(Args...)> const &slot)
Definition signal.h:348
auto bind_first(Func &&fun, Args &&...args)
Definition utils.h:152
typename operator_node_result< Operator, Ts... >::type operator_node_result_t
Definition make_node.h:57
The main namespace of the KDBindings library.
Definition binding.h:21
Definition utils.h:161

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated by doxygen 1.9.8