changeset 428:751fb354149e am-vsol-upgrade

ability to change the scene of the RadiographyWidget
author am@osimis.io
date Wed, 28 Nov 2018 10:44:28 +0100
parents 660fe6f6bf4a
children c7fb700a7d12
files Applications/Samples/CMakeLists.txt Applications/Samples/SingleFrameEditorApplication.h Framework/Messages/IObservable.cpp Framework/Messages/IObservable.h Framework/Messages/MessageForwarder.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyWidget.cpp Framework/Radiography/RadiographyWidget.h UnitTestsSources/TestMessageBroker.cpp
diffstat 9 files changed, 443 insertions(+), 144 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/CMakeLists.txt	Thu Nov 22 23:15:24 2018 +0100
+++ b/Applications/Samples/CMakeLists.txt	Wed Nov 28 10:44:28 2018 +0100
@@ -193,7 +193,6 @@
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
-    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker2.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
     )
 
--- a/Applications/Samples/SingleFrameEditorApplication.h	Thu Nov 22 23:15:24 2018 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Wed Nov 28 10:44:28 2018 +0100
@@ -303,7 +303,7 @@
             if (context_ != NULL)
             {
               widget.GetScene().ExportDicom(context_->GetOrthancApiClient(),
-                                            tags, 0.1, 0.1, widget.IsInverted(),
+                                            tags, std::string(), 0.1, 0.1, widget.IsInverted(),
                                             widget.GetInterpolation(), EXPORT_USING_PAM);
             }
             
@@ -380,7 +380,7 @@
       public IObserver
     {
     private:
-      std::auto_ptr<RadiographyScene>  scene_;
+      boost::shared_ptr<RadiographyScene>  scene_;
       RadiographyEditorInteractor      interactor_;
 
     public:
@@ -465,7 +465,7 @@
         }
         
         
-        mainWidget_ = new RadiographyWidget(GetBroker(), *scene_, "main-widget");
+        mainWidget_ = new RadiographyWidget(GetBroker(), scene_, "main-widget");
         mainWidget_->SetTransmitMouseOver(true);
         mainWidget_->SetInteractor(interactor_);
 
--- a/Framework/Messages/IObservable.cpp	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Messages/IObservable.cpp	Wed Nov 28 10:44:28 2018 +0100
@@ -64,6 +64,25 @@
     callables_[messageType].insert(callable);
   }
 
+  void IObservable::Unregister(IObserver *observer)
+  {
+    // delete all callables from this observer
+    for (Callables::iterator itCallableSet = callables_.begin();
+         itCallableSet != callables_.end(); ++itCallableSet)
+    {
+      for (std::set<ICallable*>::const_iterator
+             itCallable = itCallableSet->second.begin(); itCallable != itCallableSet->second.end(); )
+      {
+        if ((*itCallable)->GetObserver() == observer)
+        {
+          delete *itCallable;
+          itCallableSet->second.erase(itCallable++);
+        }
+        else
+          ++itCallable;
+      }
+    }
+  }
   
   void IObservable::EmitMessage(const IMessage& message)
   {
--- a/Framework/Messages/IObservable.h	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Messages/IObservable.h	Wed Nov 28 10:44:28 2018 +0100
@@ -58,6 +58,8 @@
     // Takes ownsership
     void RegisterObserverCallback(ICallable* callable);
 
+    void Unregister(IObserver* observer);
+
     void EmitMessage(const IMessage& message);
 
     // Takes ownsership
--- a/Framework/Messages/MessageForwarder.h	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Messages/MessageForwarder.h	Wed Nov 28 10:44:28 2018 +0100
@@ -59,10 +59,10 @@
    * C is an observer of B and knows that B is re-emitting many messages from A
    *
    * instead of implementing a callback, B will create a MessageForwarder that will emit the messages in his name:
-   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where this is B
+   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where "this" is B
    *
    * in C:
-   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where this is C
+   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where "this" is C
    */
   template<typename TMessage>
   class MessageForwarder : public IMessageForwarder, public Callable<MessageForwarder<TMessage>, TMessage>
--- a/Framework/Radiography/RadiographyScene.cpp	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Wed Nov 28 10:44:28 2018 +0100
@@ -412,6 +412,8 @@
 
     std::auto_ptr<RadiographyLayer> raii(layer);
 
+    LOG(INFO) << "Registering layer: " << countLayers_;
+
     size_t index = countLayers_++;
     raii->SetIndex(index);
     layers_[index] = raii.release();
@@ -445,6 +447,8 @@
 
   void RadiographyScene::RemoveLayer(size_t layerIndex)
   {
+    LOG(INFO) << "Removing layer: " << layerIndex;
+
     if (layerIndex > countLayers_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -452,6 +456,7 @@
     delete layers_[layerIndex];
     layers_.erase(layerIndex);
     countLayers_--;
+    LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers";
   }
 
   RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex)
--- a/Framework/Radiography/RadiographyWidget.cpp	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Radiography/RadiographyWidget.cpp	Wed Nov 28 10:44:28 2018 +0100
@@ -31,7 +31,7 @@
                                          ImageInterpolation interpolation)
   {
     float windowCenter, windowWidth;
-    scene_.GetWindowingWithDefault(windowCenter, windowWidth);
+    scene_->GetWindowingWithDefault(windowCenter, windowWidth);
       
     float x0 = windowCenter - windowWidth / 2.0f;
     float x1 = windowCenter + windowWidth / 2.0f;
@@ -56,7 +56,7 @@
         cairoBuffer_.reset(new CairoSurface(width, height));
       }
 
-      scene_.Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
+      scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
         
       // Conversion from Float32 to BGRA32 (cairo). Very similar to
       // GrayscaleFrameRenderer => TODO MERGE?
@@ -128,7 +128,7 @@
 
     if (hasSelection_)
     {
-      scene_.DrawBorder(context, selectedLayer_, view.GetZoom());
+      scene_->DrawBorder(context, selectedLayer_, view.GetZoom());
     }
 
     return true;
@@ -136,23 +136,16 @@
 
 
   RadiographyWidget::RadiographyWidget(MessageBroker& broker,
-                                       RadiographyScene& scene,
+                                       boost::shared_ptr<RadiographyScene> scene,
                                        const std::string& name) :
     WorldSceneWidget(name),
     IObserver(broker),
-    scene_(scene),
     invert_(false),
     interpolation_(ImageInterpolation_Nearest),
     hasSelection_(false),
     selectedLayer_(0)    // Dummy initialization
   {
-    scene.RegisterObserverCallback(
-      new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
-      (*this, &RadiographyWidget::OnGeometryChanged));
-
-    scene.RegisterObserverCallback(
-      new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
-      (*this, &RadiographyWidget::OnContentChanged));
+    SetScene(scene);
   }
 
 
@@ -216,4 +209,25 @@
       NotifyContentChanged();
     }
   }
+
+  void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
+  {
+    if (scene_ != NULL)
+    {
+      scene_->Unregister(this);
+    }
+
+    scene_ = scene;
+
+    scene_->RegisterObserverCallback(
+      new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
+      (*this, &RadiographyWidget::OnGeometryChanged));
+
+    scene_->RegisterObserverCallback(
+      new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
+      (*this, &RadiographyWidget::OnContentChanged));
+
+    // force redraw
+    FitContent();
+  }
 }
--- a/Framework/Radiography/RadiographyWidget.h	Thu Nov 22 23:15:24 2018 +0100
+++ b/Framework/Radiography/RadiographyWidget.h	Wed Nov 28 10:44:28 2018 +0100
@@ -32,7 +32,7 @@
     public IObserver
   {
   private:
-    RadiographyScene&                      scene_;
+    boost::shared_ptr<RadiographyScene>    scene_;
     std::auto_ptr<Orthanc::ImageAccessor>  floatBuffer_;
     std::auto_ptr<CairoSurface>            cairoBuffer_;
     bool                                   invert_;
@@ -47,7 +47,7 @@
   protected:
     virtual Extent2D GetSceneExtent()
     {
-      return scene_.GetSceneExtent();
+      return scene_->GetSceneExtent();
     }
 
     virtual bool RenderScene(CairoContext& context,
@@ -55,14 +55,16 @@
 
   public:
     RadiographyWidget(MessageBroker& broker,
-                      RadiographyScene& scene,
+                      boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
                       const std::string& name);
 
     RadiographyScene& GetScene() const
     {
-      return scene_;
+      return *scene_;
     }
 
+    void SetScene(boost::shared_ptr<RadiographyScene> scene);
+
     void Unselect()
     {
       hasSelection_ = false;
--- a/UnitTestsSources/TestMessageBroker.cpp	Thu Nov 22 23:15:24 2018 +0100
+++ b/UnitTestsSources/TestMessageBroker.cpp	Wed Nov 28 10:44:28 2018 +0100
@@ -1,158 +1,416 @@
-///**
-// * Stone of Orthanc
-// * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-// * Department, University Hospital of Liege, Belgium
-// * Copyright (C) 2017-2018 Osimis S.A., Belgium
-// *
-// * This program is free software: you can redistribute it and/or
-// * modify it under the terms of the GNU Affero General Public License
-// * as published by the Free Software Foundation, either version 3 of
-// * the License, or (at your option) any later version.
-// *
-// * This program 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
-// * Affero General Public License for more details.
-// *
-// * You should have received a copy of the GNU Affero General Public License
-// * along with this program. If not, see <http://www.gnu.org/licenses/>.
-// **/
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
 
 
-//#include "gtest/gtest.h"
+#include "gtest/gtest.h"
+
+#include "Framework/Messages/MessageBroker.h"
+#include "Framework/Messages/Promise.h"
+#include "Framework/Messages/IObservable.h"
+#include "Framework/Messages/IObserver.h"
+#include "Framework/Messages/MessageForwarder.h"
+
 
-//#include "../Framework/Messages/MessageBroker.h"
-//#include "../Framework/Messages/IMessage.h"
-//#include "../Framework/Messages/IObservable.h"
-//#include "../Framework/Messages/IObserver.h"
-//#include "../Framework/StoneEnumerations.h"
+int testCounter = 0;
+namespace {
+
+  using namespace OrthancStone;
+
+
+  enum CustomMessageType
+  {
+    CustomMessageType_First = MessageType_CustomMessage + 1,
+
+    CustomMessageType_Completed,
+    CustomMessageType_Increment
+  };
 
 
-//static int test1Counter = 0;
-//static int test2Counter = 0;
-//class MyFullObserver : public OrthancStone::IObserver
-//{
+  class MyObservable : public IObservable
+  {
+  public:
+    struct MyCustomMessage: public BaseMessage<CustomMessageType_Completed>
+    {
+      int payload_;
+
+      MyCustomMessage(int payload)
+        : BaseMessage(),
+          payload_(payload)
+      {}
+    };
+
+    MyObservable(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+
+  };
 
-//public:
-//  MyFullObserver(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObserver(broker)
-//  {
-////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-////    DeclareIgnoredMessage(OrthancStone::MessageType_Test2);
-//  }
+  class MyObserver : public IObserver
+  {
+  public:
+    MyObserver(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void HandleCompletedMessage(const MyObservable::MyCustomMessage& message)
+    {
+      testCounter += message.payload_;
+    }
+
+  };
+
+
+  class MyIntermediate : public IObserver, public IObservable
+  {
+    IObservable& observedObject_;
+  public:
+    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
+      : IObserver(broker),
+        IObservable(broker),
+        observedObject_(observedObject)
+    {
+      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
+    }
+  };
 
 
-//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-//    switch (message.GetType())
-//    {
-//    case OrthancStone::MessageType_Test1:
-//      test1Counter++;
-//      break;
-//    case OrthancStone::MessageType_Test2:
-//      test2Counter++;
-//      break;
-//    default:
-//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-//    }
-//  }
+  class MyPromiseSource : public IObservable
+  {
+    Promise* currentPromise_;
+  public:
+    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
+    {
+      int increment;
+
+      MyPromiseMessage(int increment)
+        : BaseMessage(),
+          increment(increment)
+      {}
+    };
+
+    MyPromiseSource(MessageBroker& broker)
+      : IObservable(broker),
+        currentPromise_(NULL)
+    {}
+
+    Promise& StartSomethingAsync()
+    {
+      currentPromise_ = new Promise(GetBroker());
+      return *currentPromise_;
+    }
 
-//};
+    void CompleteSomethingAsyncWithSuccess(int payload)
+    {
+      currentPromise_->Success(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
 
-//class MyPartialObserver : public OrthancStone::IObserver
-//{
+    void CompleteSomethingAsyncWithFailure(int payload)
+    {
+      currentPromise_->Failure(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
+  };
+
 
-//public:
-//  MyPartialObserver(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObserver(broker)
-//  {
-////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-//    // don't declare Test2 on purpose
-//  }
+  class MyPromiseTarget : public IObserver
+  {
+  public:
+    MyPromiseTarget(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void IncrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter += args.increment;
+    }
+
+    void DecrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter -= args.increment;
+    }
+  };
+}
 
 
-//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-//    switch (message.GetType())
-//    {
-//    case OrthancStone::MessageType_Test1:
-//      test1Counter++;
-//      break;
-//    case OrthancStone::MessageType_Test2:
-//      test2Counter++;
-//      break;
-//    default:
-//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-//    }
-//  }
+TEST(MessageBroker, TestPermanentConnectionSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver    observer(broker);
+
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+
+  // Unregister the observer; make sure it's not called anymore
+  observable.Unregister(&observer);
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+TEST(MessageBroker, TestMessageForwarderSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+TEST(MessageBroker, TestPermanentConnectionDeleteObserver)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
 
-//};
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
 
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//class MyObservable : public OrthancStone::IObservable
-//{
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+TEST(MessageBroker, TestMessageForwarderDeleteIntermediate)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate* intermediate = new MyIntermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
 
-//public:
-//  MyObservable(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObservable(broker)
-//  {
-//    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
-//    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
-//  }
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  delete intermediate;
+
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(12, testCounter);
+}
 
-//};
+TEST(MessageBroker, TestCustomMessage)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
 
 
-//TEST(MessageBroker, NormalUsage)
-//{
-//  OrthancStone::MessageBroker broker;
-//  MyObservable observable(broker);
+TEST(MessageBroker, TestPromiseSuccessFailure)
+{
+  MessageBroker broker;
+  MyPromiseSource  source(broker);
+  MyPromiseTarget target(broker);
+
+  // test a successful promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(10, testCounter);
 
-//  test1Counter = 0;
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  ASSERT_EQ(-15, testCounter);
+}
 
-//  // no observers have been registered -> nothing shall happen
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+TEST(MessageBroker, TestPromiseDeleteTarget)
+{
+  MessageBroker broker;
+  MyPromiseSource source(broker);
+  MyPromiseTarget* target = new MyPromiseTarget(broker);
 
-//  ASSERT_EQ(0, test1Counter);
+  // create the promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
 
-//  // register an observer, check it is called
-//  MyFullObserver fullObserver(broker);
-//  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
+  // delete the promise target
+  delete target;
+
+  // trigger the promise, make sure it does not throw and does not call the callback
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(0, testCounter);
 
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  ASSERT_EQ(0, testCounter);
+}
+
+#if __cplusplus >= 201103L
 
-//  ASSERT_EQ(1, test1Counter);
+#include <functional>
+
+namespace OrthancStone {
+
+  template <typename TMessage>
+  class LambdaCallable : public MessageHandler<TMessage>
+  {
+  private:
 
-//  // register an invalid observer, check it raises an exception
-//  MyPartialObserver partialObserver(broker);
-//  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
+    IObserver&      observer_;
+    std::function<void (const TMessage&)> lambda_;
 
-//  // check an exception is thrown when the observable emits an undeclared message
-//  ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_VolumeSlicer_GeometryReady)), OrthancStone::MessageNotDeclaredException);
+  public:
+    LambdaCallable(IObserver& observer,
+                    std::function<void (const TMessage&)> lambdaFunction) :
+             observer_(observer),
+             lambda_(lambdaFunction)
+    {
+    }
 
-//  // unregister the observer, make sure nothing happens afterwards
-//  observable.UnregisterObserver(fullObserver);
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
-//  ASSERT_EQ(1, test1Counter);
-//}
+    virtual void Apply(const IMessage& message)
+    {
+      lambda_(dynamic_cast<const TMessage&>(message));
+    }
+
+    virtual MessageType GetMessageType() const
+    {
+      return static_cast<MessageType>(TMessage::Type);
+    }
+
+    virtual IObserver* GetObserver() const
+    {
+      return &observer_;
+    }
+  };
+
 
-//TEST(MessageBroker, DeleteObserverWhileRegistered)
-//{
-//  OrthancStone::MessageBroker broker;
-//  MyObservable observable(broker);
+}
+
+TEST(MessageBroker, TestLambdaSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
 
-//  test1Counter = 0;
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;}));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(24, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//  {
-//    // register an observer, check it is called
-//    MyFullObserver observer(broker);
-//    observable.RegisterObserver(observer);
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
 
-//    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+namespace {
+  class MyObserverWithLambda : public IObserver {
+  private:
+    int multiplier_;  // this is a private variable we want to access in a lambda
+
+  public:
+    MyObserverWithLambda(MessageBroker& broker, int multiplier, MyObservable& observable)
+      : IObserver(broker),
+        multiplier_(multiplier)
+    {
+      // register a callable to a lambda that access private members
+      observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*this, [this](const MyObservable::MyCustomMessage& message) {
+        testCounter += multiplier_ * message.payload_;
+      }));
 
-//    ASSERT_EQ(1, test1Counter);
-//  }
+    }
+  };
+}
+
+TEST(MessageBroker, TestLambdaCaptureThisAndAccessPrivateMembers)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserverWithLambda*   observer = new MyObserverWithLambda(broker, 3, observable);
 
-//  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(36, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//  ASSERT_EQ(1, test1Counter);
-//}
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+#endif // C++ 11