util: Signal::connectSingleShot

refs #2331

Change-Id: I96e6c362c07c8d962e59ebba2cf338761a4f110a
diff --git a/src/util/signal-signal.hpp b/src/util/signal-signal.hpp
index e1c01df..7f91e15 100644
--- a/src/util/signal-signal.hpp
+++ b/src/util/signal-signal.hpp
@@ -63,6 +63,13 @@
   Connection
   connect(const Handler& handler);
 
+  /** \brief connects a single-shot handler to the signal
+   *
+   *  After the handler is executed once, it is automatically disconnected.
+   */
+  Connection
+  connectSingleShot(const Handler& handler);
+
 private: // API for owner
   /** \retval true if there is no connection
    */
@@ -164,6 +171,22 @@
 }
 
 template<typename Owner, typename ...TArgs>
+inline Connection
+Signal<Owner, TArgs...>::connectSingleShot(const Handler& handler)
+{
+  typename SlotList::iterator it = m_slots.insert(m_slots.end(), {nullptr, nullptr});
+  it->disconnect = make_shared<function<void()>>(bind(&Self::disconnect, this, it));
+  signal::Connection conn(weak_ptr<function<void()>>(it->disconnect));
+
+  it->handler = [conn, handler] (const TArgs&... args) mutable {
+    handler(args...);
+    conn.disconnect();
+  };
+
+  return conn;
+}
+
+template<typename Owner, typename ...TArgs>
 inline void
 Signal<Owner, TArgs...>::disconnect(typename SlotList::iterator it)
 {
diff --git a/tests/unit-tests/util/signal.cpp b/tests/unit-tests/util/signal.cpp
index e0d70aa..d168cf5 100644
--- a/tests/unit-tests/util/signal.cpp
+++ b/tests/unit-tests/util/signal.cpp
@@ -290,6 +290,46 @@
   BOOST_CHECK_EQUAL(hit, 2); // handler called
 }
 
+BOOST_AUTO_TEST_CASE(ConnectSingleShot)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  so.sig.connectSingleShot([&hit] { ++hit; });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShotDisconnected)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection conn = so.sig.connectSingleShot([&hit] { ++hit; });
+  conn.disconnect();
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 0); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShot1)
+{
+  SignalEmitter1 se;
+
+  int hit = 0;
+  se.sig.connectSingleShot([&hit] (int) { ++hit; });
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
 BOOST_AUTO_TEST_CASE(ConnectInHandler)
 {
   SignalOwner0 so;