changeset 260:c887eddd48f1 am-2

merge
author am@osimis.io
date Tue, 17 Jul 2018 14:43:42 +0200
parents e5a9b3d03478 (diff) 106a0f9781d9 (current diff)
children 58ad26032bd6
files Applications/IBasicApplication.cpp Applications/Sdl/BasicSdlApplication.cpp Framework/SmartLoader.cpp Framework/SmartLoader.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 85 files changed, 4335 insertions(+), 923 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,6 @@
+Platforms/Generic/CMakeLists.txt.user
+Platforms/Generic/ThirdPartyDownloads/
+Platforms/Wasm/CMakeLists.txt.user
+Platforms/Wasm/build/
+Platforms/Wasm/build-web/
+Platforms/Wasm/ThirdPartyDownloads/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/BasicApplication.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,291 @@
+/**
+ * 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 "IBasicApplication.h"
+
+#include "../Framework/Toolbox/MessagingToolbox.h"
+#include "Sdl/SdlEngine.h"
+
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>
+
+namespace OrthancStone
+{
+  // Anonymous namespace to avoid clashes against other compilation modules
+  namespace
+  {
+    class LogStatusBar : public IStatusBar
+    {
+    public:
+      virtual void ClearMessage()
+      {
+      }
+
+      virtual void SetMessage(const std::string& message)
+      {
+        LOG(WARNING) << message;
+      }
+    };
+  }
+
+
+#if ORTHANC_ENABLE_SDL == 1
+  static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options)
+  {
+    // Declare the supported parameters
+    boost::program_options::options_description generic("Generic options");
+    generic.add_options()
+      ("help", "Display this help and exit")
+      ("verbose", "Be verbose in logs")
+      ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+       "URL to the Orthanc server")
+      ("username", "Username for the Orthanc server")
+      ("password", "Password for the Orthanc server")
+      ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates")
+      ;
+
+    options.add(generic);
+
+    boost::program_options::options_description sdl("SDL options");
+    sdl.add_options()
+      ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window")
+      ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window")
+      ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL")
+      ;
+
+    options.add(sdl);
+  }
+
+
+  int IBasicApplication::ExecuteWithSdl(IBasicApplication& application,
+                                        int argc, 
+                                        char* argv[])
+  {
+    /******************************************************************
+     * Initialize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Orthanc::Logging::Initialize();
+    Orthanc::HttpClient::InitializeOpenSsl();
+    Orthanc::HttpClient::GlobalInitialize();
+    SdlWindow::GlobalInitialize();
+
+
+    /******************************************************************
+     * Declare and parse the command-line options of the application
+     ******************************************************************/
+
+    boost::program_options::options_description options;
+    DeclareSdlCommandLineOptions(options);   
+    application.DeclareCommandLineOptions(options);
+
+    boost::program_options::variables_map parameters;
+    bool error = false;
+
+    try
+    {
+      boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                    options(options).run(), parameters);
+      boost::program_options::notify(parameters);    
+    }
+    catch (boost::program_options::error& e)
+    {
+      LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+      error = true;
+    }
+
+
+    /******************************************************************
+     * Configure the application with the command-line parameters
+     ******************************************************************/
+
+    if (error || parameters.count("help")) 
+    {
+      std::cout << std::endl
+                << "Usage: " << argv[0] << " [OPTION]..."
+                << std::endl
+                << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+                << std::endl << std::endl
+                << "Demonstration application of Orthanc Stone using SDL."
+                << std::endl;
+
+      std::cout << options << "\n";
+      return error ? -1 : 0;
+    }
+
+    if (parameters.count("https-verify") &&
+        !parameters["https-verify"].as<bool>())
+    {
+      LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)";
+      Orthanc::HttpClient::ConfigureSsl(false, "");
+    }
+
+    if (parameters.count("verbose"))
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+    }
+
+    if (!parameters.count("width") ||
+        !parameters.count("height") ||
+        !parameters.count("opengl"))
+    {
+      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
+      return -1;
+    }
+
+    int w = parameters["width"].as<int>();
+    int h = parameters["height"].as<int>();
+    if (w <= 0 || h <= 0)
+    {
+      LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive";
+      return -1;
+    }
+
+    unsigned int width = static_cast<unsigned int>(w);
+    unsigned int height = static_cast<unsigned int>(h);
+    LOG(WARNING) << "Initial display size: " << width << "x" << height;
+
+    bool opengl = parameters["opengl"].as<bool>();
+    if (opengl)
+    {
+      LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes";
+    }
+    else
+    {
+      LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance";
+    }
+
+    bool success = true;
+    try
+    {
+      /****************************************************************
+       * Initialize the connection to the Orthanc server
+       ****************************************************************/
+
+      Orthanc::WebServiceParameters webService;
+
+      if (parameters.count("orthanc"))
+      {
+        webService.SetUrl(parameters["orthanc"].as<std::string>());
+      }
+
+      if (parameters.count("username"))
+      {
+        webService.SetUsername(parameters["username"].as<std::string>());
+      }
+
+      if (parameters.count("password"))
+      {
+        webService.SetPassword(parameters["password"].as<std::string>());
+      }
+
+      LOG(WARNING) << "URL to the Orthanc REST API: " << webService.GetUrl();
+
+      {
+        OrthancPlugins::OrthancHttpConnection orthanc(webService);
+        if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+        {
+          LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+      }
+
+
+      /****************************************************************
+       * Initialize the application
+       ****************************************************************/
+
+      LOG(WARNING) << "Creating the widgets of the application";
+
+      LogStatusBar statusBar;
+      BasicApplicationContext context(webService);
+
+      application.Initialize(context, statusBar, parameters);
+
+      {
+        BasicApplicationContext::ViewportLocker locker(context);
+        locker.GetViewport().SetStatusBar(statusBar);
+      }
+
+      std::string title = application.GetTitle();
+      if (title.empty())
+      {
+        title = "Stone of Orthanc";
+      }
+
+      {
+        /**************************************************************
+         * Run the application inside a SDL window
+         **************************************************************/
+
+        LOG(WARNING) << "Starting the application";
+
+        SdlWindow window(title.c_str(), width, height, opengl);
+        SdlEngine sdl(window, context);
+
+        {
+          BasicApplicationContext::ViewportLocker locker(context);
+          locker.GetViewport().Register(sdl);  // (*)
+        }
+
+        context.Start();
+        sdl.Run();
+
+        LOG(WARNING) << "Stopping the application";
+
+        // Don't move the "Stop()" command below out of the block,
+        // otherwise the application might crash, because the
+        // "SdlEngine" is an observer of the viewport (*) and the
+        // update thread started by "context.Start()" would call a
+        // destructed object (the "SdlEngine" is deleted with the
+        // lexical scope).
+        context.Stop();
+      }
+
+
+      /****************************************************************
+       * Finalize the application
+       ****************************************************************/
+
+      LOG(WARNING) << "The application has stopped";
+      application.Finalize();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+      success = false;
+    }
+
+
+    /******************************************************************
+     * Finalize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    SdlWindow::GlobalFinalize();
+    Orthanc::HttpClient::GlobalFinalize();
+    Orthanc::HttpClient::FinalizeOpenSsl();
+
+    return (success ? 0 : -1);
+  }
+#endif
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/BasicApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,68 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "BasicApplicationContext.h"
+
+#include <boost/program_options.hpp>
+
+#if ORTHANC_ENABLE_SDL == 1
+#  include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
+#endif
+
+namespace OrthancStone
+{
+  class IBasicApplication : public boost::noncopyable
+  {
+  public:
+    virtual ~IBasicApplication()
+    {
+    }
+
+    virtual void DeclareStartupOption(const std::string& name, const std::string& defaultValue, const std::string& helpText) = 0;
+    virtual void Initialize(IStatusBar& statusBar, const std::map<std::string, std::string> startupOptions);
+
+
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
+
+    virtual std::string GetTitle() const = 0;
+
+    virtual void Initialize(BasicApplicationContext& context,
+                            IStatusBar& statusBar,
+                            const boost::program_options::variables_map& parameters) = 0;
+
+    virtual void Finalize() = 0;
+
+#if ORTHANC_ENABLE_SDL == 1
+    static int ExecuteWithSdl(IBasicApplication& application,
+                              int argc, 
+                              char* argv[]);
+#endif
+  };
+
+  class IBasicSdlApplication : public IBasicApplication
+  {
+    public:
+
+    
+  }
+}
--- a/Applications/BasicApplicationContext.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/BasicApplicationContext.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -23,122 +23,4 @@
 
 namespace OrthancStone
 {
-  void BasicApplicationContext::UpdateThread(BasicApplicationContext* that)
-  {
-    while (!that->stopped_)
-    {
-      {
-        ViewportLocker locker(*that);
-        locker.GetViewport().UpdateContent();
-      }
-      
-      boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_));
-    }
-  }
-  
-
-  BasicApplicationContext::BasicApplicationContext(Orthanc::WebServiceParameters& orthanc) :
-    oracle_(viewportMutex_, 4),  // Use 4 threads to download
-    //oracle_(viewportMutex_, 1),  // Disable threading to be reproducible
-    webService_(oracle_, orthanc),
-    stopped_(true),
-    updateDelay_(100)   // By default, 100ms between each refresh of the content
-  {
-    srand(time(NULL)); 
-  }
-
-
-  BasicApplicationContext::~BasicApplicationContext()
-  {
-    for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-
-    for (SlicedVolumes::iterator it = slicedVolumes_.begin(); it != slicedVolumes_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-
-    for (VolumeLoaders::iterator it = volumeLoaders_.begin(); it != volumeLoaders_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-  }
-
-
-  IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
-  {
-    viewport_.SetCentralWidget(widget);
-    return *widget;
-  }
-
-
-  ISlicedVolume& BasicApplicationContext::AddSlicedVolume(ISlicedVolume* volume)
-  {
-    if (volume == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      slicedVolumes_.push_back(volume);
-      return *volume;
-    }
-  }
-
-
-  IVolumeLoader& BasicApplicationContext::AddVolumeLoader(IVolumeLoader* loader)
-  {
-    if (loader == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      volumeLoaders_.push_back(loader);
-      return *loader;
-    }
-  }
-
-
-  IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor)
-  {
-    if (interactor == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    interactors_.push_back(interactor);
-
-    return *interactor;
-  }
-
-
-  void BasicApplicationContext::Start()
-  {
-    oracle_.Start();
-
-    if (viewport_.HasUpdateContent())
-    {
-      stopped_ = false;
-      updateThread_ = boost::thread(UpdateThread, this);
-    }
-  }
-
-
-  void BasicApplicationContext::Stop()
-  {
-    stopped_ = true;
-    
-    if (updateThread_.joinable())
-    {
-      updateThread_.join();
-    }
-    
-    oracle_.Stop();
-  }
 }
--- a/Applications/BasicApplicationContext.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/BasicApplicationContext.h	Tue Jul 17 14:43:42 2018 +0200
@@ -21,82 +21,27 @@
 
 #pragma once
 
+#include "../Platforms/Generic/OracleWebService.h"
 #include "../Framework/Viewport/WidgetViewport.h"
-#include "../Framework/Volumes/ISlicedVolume.h"
-#include "../Framework/Volumes/IVolumeLoader.h"
-#include "../Framework/Widgets/IWorldSceneInteractor.h"
-#include "../Platforms/Generic/OracleWebService.h"
 
 #include <list>
-#include <boost/thread.hpp>
 
 namespace OrthancStone
 {
   class BasicApplicationContext : public boost::noncopyable
   {
-  private:
-    typedef std::list<ISlicedVolume*>          SlicedVolumes;
-    typedef std::list<IVolumeLoader*>          VolumeLoaders;
-    typedef std::list<IWorldSceneInteractor*>  Interactors;
-
-    static void UpdateThread(BasicApplicationContext* that);
-
-    Oracle              oracle_;
-    OracleWebService    webService_;
-    boost::mutex        viewportMutex_;
-    WidgetViewport      viewport_;
-    SlicedVolumes       slicedVolumes_;
-    VolumeLoaders       volumeLoaders_;
-    Interactors         interactors_;
-    boost::thread       updateThread_;
-    bool                stopped_;
-    unsigned int        updateDelay_;
-
-  public:
-    class ViewportLocker : public boost::noncopyable
-    {
-    private:
-      boost::mutex::scoped_lock  lock_;
-      IViewport&                 viewport_;
-
-    public:
-      ViewportLocker(BasicApplicationContext& that) :
-        lock_(that.viewportMutex_),
-        viewport_(that.viewport_)
-      {
-      }
 
-      IViewport& GetViewport() const
-      {
-        return viewport_;
-      }
-    };
-
-    
-    BasicApplicationContext(Orthanc::WebServiceParameters& orthanc);
-
-    ~BasicApplicationContext();
-
-    IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
-
-    IWebService& GetWebService()
+  protected:
+    IWebService& webService_;
+  public:
+    BasicApplicationContext(IWebService& webService)
+      : webService_(webService)
     {
-      return webService_;
     }
-    
-    ISlicedVolume& AddSlicedVolume(ISlicedVolume* volume);
-
-    IVolumeLoader& AddVolumeLoader(IVolumeLoader* loader);
-
-    IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
 
-    void Start();
-
-    void Stop();
+    virtual IWebService& GetWebService() {return webService_;}
+//    virtual IWidget& SetCentralWidget(IWidget* widget) = 0;   // Takes ownership
 
-    void SetUpdateDelay(unsigned int delay)  // In milliseconds
-    {
-      updateDelay_ = delay;
-    }
+    virtual ~BasicApplicationContext() {}
   };
 }
--- a/Applications/IBasicApplication.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/IBasicApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -22,36 +22,34 @@
 #pragma once
 
 #include "BasicApplicationContext.h"
-
 #include <boost/program_options.hpp>
-
-#if ORTHANC_ENABLE_SDL == 1
-#  include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
-#endif
+#include "../Framework/Viewport/WidgetViewport.h"
 
 namespace OrthancStone
 {
   class IBasicApplication : public boost::noncopyable
   {
+  protected:
+    BasicApplicationContext* context_;
+
   public:
     virtual ~IBasicApplication()
     {
     }
 
-    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
+    virtual void DeclareStartupOptions(boost::program_options::options_description& options) = 0;
+    virtual void Initialize(BasicApplicationContext* context,
+                            IStatusBar& statusBar,
+                            const boost::program_options::variables_map& parameters) = 0;
+#if ORTHANC_ENABLE_SDL==0
+    virtual void InitializeWasm() {}  // specific initialization when the app is running in WebAssembly.  This is called after the other Initialize()
+#endif
 
     virtual std::string GetTitle() const = 0;
-
-    virtual void Initialize(BasicApplicationContext& context,
-                            IStatusBar& statusBar,
-                            const boost::program_options::variables_map& parameters) = 0;
+    virtual IWidget* GetCentralWidget() = 0;
 
     virtual void Finalize() = 0;
 
-#if ORTHANC_ENABLE_SDL == 1
-    static int ExecuteWithSdl(IBasicApplication& application,
-                              int argc, 
-                              char* argv[]);
-#endif
   };
+
 }
--- a/Applications/Samples/EmptyApplication.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/EmptyApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -32,7 +32,7 @@
     class EmptyApplication : public SampleApplicationBase
     {
     public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
         generic.add_options()
@@ -44,15 +44,14 @@
         options.add(generic);    
       }
 
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
+      virtual void Initialize(IStatusBar& statusBar,
                               const boost::program_options::variables_map& parameters)
       {
         int red = parameters["red"].as<int>();
         int green = parameters["green"].as<int>();
         int blue = parameters["blue"].as<int>();
 
-        context.SetCentralWidget(new EmptyWidget(red, green, blue));
+        context_->SetCentralWidget(new EmptyWidget(red, green, blue));
       }
     };
   }
--- a/Applications/Samples/SampleApplicationBase.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/SampleApplicationBase.h	Tue Jul 17 14:43:42 2018 +0200
@@ -21,12 +21,48 @@
 
 #pragma once
 
-#include "../IBasicApplication.h"
+//#if ORTHANC_ENABLE_SDL==1
+//#include "../../Applications/Sdl/BasicSdlApplication.h"
+//#else
+//#include "../../Applications/Wasm/BasicWasmApplication.h"
+//#endif
+#include "../../Applications/IBasicApplication.h"
+#include "../../Framework/Viewport/WidgetViewport.h"
+//#include "SampleApplicationContext.h"
 
 namespace OrthancStone
 {
   namespace Samples
   {
+
+//#if ORTHANC_ENABLE_SDL==1
+//    class SampleSdlApplicationBase : public BasicSdlApplication {
+//    protected:
+//    public:
+//      virtual BasicApplicationContext& CreateApplicationContext(Orthanc::WebServiceParameters& orthanc, WidgetViewport* centralViewport) {
+//        context_.reset(new SampleApplicationContext(orthanc, centralViewport));
+
+//        return *context_;
+//      }
+//    };
+
+//    typedef SampleSdlApplicationBase SampleApplicationBase_;
+//#else
+//    class SampleWasmApplicationBase : public BasicWasmApplication {
+//    protected:
+//      std::unique_ptr<SampleApplicationContext> context_;
+//    public:
+//      virtual BasicApplicationContext& CreateApplicationContext(IWebService& orthancWebService, std::shared_ptr<WidgetViewport> centralViewport) {
+//        context_.reset(new SampleApplicationContext(orthancWebService));
+//        return *context_;
+//      }
+
+//    };
+
+//    typedef SampleWasmApplicationBase SampleApplicationBase_;
+
+//#endif
+
     class SampleApplicationBase : public IBasicApplication
     {
     public:
@@ -35,13 +71,10 @@
         return "Stone of Orthanc - Sample";
       }
 
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-      }
+      virtual void CustomInitialize() {}
 
-      virtual void Finalize()
-      {
-      }
     };
+
+
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleList.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,37 @@
+// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script
+
+#if ORTHANC_STONE_SAMPLE == 1
+#include "EmptyApplication.h"
+typedef OrthancStone::Samples::EmptyApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 2
+#include "TestPatternApplication.h"
+typedef OrthancStone::Samples::TestPatternApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 3
+#include "SingleFrameApplication.h"
+typedef OrthancStone::Samples::SingleFrameApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 4
+#include "SingleVolumeApplication.h"
+typedef OrthancStone::Samples::SingleVolumeApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 5
+#include "BasicPetCtFusionApplication.h"
+typedef OrthancStone::Samples::BasicPetCtFusionApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 6
+#include "SynchronizedSeriesApplication.h"
+typedef OrthancStone::Samples::SynchronizedSeriesApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 7
+#include "LayoutPetCtFusionApplication.h"
+typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 8
+#include "SimpleViewerApplication.h"
+typedef OrthancStone::Samples::SimpleViewerApplication Application;
+
+#else
+#error Please set the ORTHANC_STONE_SAMPLE macro
+#endif
--- a/Applications/Samples/SampleMainSdl.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/SampleMainSdl.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -19,44 +19,14 @@
  **/
 
 
-// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script
-
-#if ORTHANC_STONE_SAMPLE == 1
-#include "EmptyApplication.h"
-typedef OrthancStone::Samples::EmptyApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 2
-#include "TestPatternApplication.h"
-typedef OrthancStone::Samples::TestPatternApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 3
-#include "SingleFrameApplication.h"
-typedef OrthancStone::Samples::SingleFrameApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 4
-#include "SingleVolumeApplication.h"
-typedef OrthancStone::Samples::SingleVolumeApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 5
-#include "BasicPetCtFusionApplication.h"
-typedef OrthancStone::Samples::BasicPetCtFusionApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 6
-#include "SynchronizedSeriesApplication.h"
-typedef OrthancStone::Samples::SynchronizedSeriesApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 7
-#include "LayoutPetCtFusionApplication.h"
-typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application;
-
-#else
-#error Please set the ORTHANC_STONE_SAMPLE macro
-#endif
-
+#include "SampleList.h"
+#include "../Sdl/BasicSdlApplication.h"
+#include "../../Framework/Messages/MessageBroker.h"
 
 int main(int argc, char* argv[]) 
 {
-  Application application;
+  OrthancStone::MessageBroker broker;
+  Application application(broker);
 
-  return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv);
+  return OrthancStone::BasicSdlApplication::ExecuteWithSdl(broker, application, argc, argv);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleMainWasm.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,32 @@
+/**
+ * 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 "Platforms/Wasm/WasmWebService.h"
+#include "Platforms/Wasm/WasmViewport.h"
+
+#include <emscripten/emscripten.h>
+
+#include "SampleList.h"
+
+
+OrthancStone::IBasicApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) {
+  
+  return new Application(broker);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SimpleViewerApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,312 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "SampleApplicationBase.h"
+
+#include "../../Framework/Layers/OrthancFrameLayerSource.h"
+#include "../../Framework/Widgets/LayerWidget.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/SmartLoader.h"
+
+#include <Core/Logging.h>
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SimpleViewerApplication :
+        public SampleApplicationBase,
+        public IObserver
+    {
+    private:
+      class Interactor : public IWorldSceneInteractor
+      {
+      private:
+        SimpleViewerApplication&  application_;
+        
+      public:
+        Interactor(SimpleViewerApplication&  application) :
+          application_(application)
+        {
+        }
+        
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          return NULL;
+        }
+
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {
+          if (statusBar != NULL)
+          {
+            Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+            
+            char buf[64];
+            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
+                    p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+            statusBar->SetMessage(buf);
+          }
+        }
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                MouseWheelDirection direction,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          //          int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
+          
+          //          switch (direction)
+          //          {
+          //            case MouseWheelDirection_Up:
+          //              application_.OffsetSlice(-scale);
+          //              break;
+
+          //            case MouseWheelDirection_Down:
+          //              application_.OffsetSlice(scale);
+          //              break;
+
+          //            default:
+          //              break;
+          //          }
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          switch (key)
+          {
+          case 's':
+            widget.SetDefaultView();
+            break;
+          case 'n':
+            application_.NextImage(widget);
+            break;
+
+          default:
+            break;
+          }
+        }
+      };
+
+
+      //      void OffsetSlice(int offset)
+      //      {
+      //        if (source_ != NULL)
+      //        {
+      //          int slice = static_cast<int>(slice_) + offset;
+
+      //          if (slice < 0)
+      //          {
+      //            slice = 0;
+      //          }
+
+      //          if (slice >= static_cast<int>(source_->GetSliceCount()))
+      //          {
+      //            slice = source_->GetSliceCount() - 1;
+      //          }
+
+      //          if (slice != static_cast<int>(slice_))
+      //          {
+      //            SetSlice(slice);
+      //          }
+      //        }
+      //      }
+      
+
+      //      void SetSlice(size_t index)
+      //      {
+      //        if (source_ != NULL &&
+      //            index < source_->GetSliceCount())
+      //        {
+      //          slice_ = index;
+
+      //#if 1
+      //          widget_->SetSlice(source_->GetSlice(slice_).GetGeometry());
+      //#else
+      //          // TEST for scene extents - Rotate the axes
+      //          double a = 15.0 / 180.0 * M_PI;
+
+      //#if 1
+      //          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+      //          Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
+      //#else
+      //          // Flip the normal
+      //          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+      //          Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0);
+      //#endif
+
+      //          SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y);
+      //          widget_->SetSlice(s);
+      //#endif
+      //        }
+      //      }
+
+
+      virtual void HandleMessage(IObservable& from, const IMessage& message) {
+        switch (message.GetType()) {
+        case MessageType_GeometryReady:
+          mainLayout_->SetDefaultView();
+          break;
+        default:
+          VLOG("unhandled message type" << message.GetType());
+        }
+      }
+
+      std::unique_ptr<Interactor>     interactor_;
+      LayoutWidget*                   mainLayout_;
+      LayoutWidget*                   thumbnailsLayout_;
+      LayerWidget*                    mainViewport_;
+      std::vector<LayerWidget*>       thumbnails_;
+      std::vector<std::string>        instances_;
+      unsigned int                    currentInstanceIndex_;
+      OrthancStone::WidgetViewport*                wasmViewport1_;
+      OrthancStone::WidgetViewport*                wasmViewport2_;
+
+      IStatusBar*                     statusBar_;
+      unsigned int                    slice_;
+      std::unique_ptr<SmartLoader>    smartLoader_;
+
+    public:
+      SimpleViewerApplication(MessageBroker& broker) :
+        IObserver(broker),
+        mainLayout_(NULL),
+        currentInstanceIndex_(0),
+        wasmViewport1_(NULL),
+        wasmViewport2_(NULL),
+        slice_(0)
+      {
+      }
+
+      virtual void Finalize() {}
+      virtual IWidget* GetCentralWidget() {return mainLayout_;}
+
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+            //          ("study", boost::program_options::value<std::string>(),
+            //           "Orthanc ID of the study")
+            ("instance1", boost::program_options::value<std::string>(),
+             "Orthanc ID of the instances")
+            ("instance2", boost::program_options::value<std::string>(),
+             "Orthanc ID of the instances")
+            ;
+
+        options.add(generic);
+      }
+
+      virtual void Initialize(BasicApplicationContext* context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        context_ = context;
+        statusBar_ = &statusBar;
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+        statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport");
+
+        if (parameters.count("instance1") < 1)
+        {
+          LOG(ERROR) << "The instance ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+        if (parameters.count("instance2") < 1)
+        {
+          LOG(ERROR) << "The instance ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+        instances_.push_back(parameters["instance1"].as<std::string>());
+        instances_.push_back(parameters["instance2"].as<std::string>());
+
+        mainLayout_ = new LayoutWidget();
+        mainLayout_->SetPadding(10);
+        mainLayout_->SetBackgroundCleared(true);
+        mainLayout_->SetBackgroundColor(0, 0, 0);
+        mainLayout_->SetHorizontal();
+
+        thumbnailsLayout_ = new LayoutWidget();
+        thumbnailsLayout_->SetPadding(10);
+        thumbnailsLayout_->SetBackgroundCleared(true);
+        thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
+        thumbnailsLayout_->SetVertical();
+
+        mainViewport_ = new LayerWidget(broker_);
+        thumbnails_.push_back(new LayerWidget(broker_));
+        thumbnails_.push_back(new LayerWidget(broker_));
+
+        // hierarchy
+        mainLayout_->AddWidget(thumbnailsLayout_);
+        mainLayout_->AddWidget(mainViewport_);
+        thumbnailsLayout_->AddWidget(thumbnails_[0]);
+        thumbnailsLayout_->AddWidget(thumbnails_[1]);
+
+        // sources
+        smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
+        smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+        smartLoader_->RegisterObserver(*this);
+
+        mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
+        thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0));
+        thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0));
+
+        mainLayout_->SetTransmitMouseOver(true);
+        interactor_.reset(new Interactor(*this));
+        mainViewport_->SetInteractor(*interactor_);
+      }
+
+#if ORTHANC_ENABLE_SDL==0
+      virtual void InitializeWasm() {
+
+        AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
+        AttachWidgetToWasmViewport("canvas2", mainViewport_);
+      }
+#endif
+
+      void NextImage(WorldSceneWidget& widget) {
+        assert(context_);
+        statusBar_->SetMessage("displaying next image");
+
+        currentInstanceIndex_ = (currentInstanceIndex_ + 1) % instances_.size();
+
+        mainViewport_->ReplaceLayer(0, smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
+
+      }
+    };
+
+
+  }
+}
--- a/Applications/Samples/SingleFrameApplication.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -214,7 +214,7 @@
       {
       }
       
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
         generic.add_options()
@@ -229,8 +229,7 @@
         options.add(generic);    
       }
 
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
+      virtual void Initialize(IStatusBar& statusBar,
                               const boost::program_options::variables_map& parameters)
       {
         using namespace OrthancStone;
@@ -250,7 +249,7 @@
 
 #if 1
         std::auto_ptr<OrthancFrameLayerSource> layer
-          (new OrthancFrameLayerSource(context.GetWebService()));
+          (new OrthancFrameLayerSource(context_->GetWebService()));
         //layer->SetImageQuality(SliceImageQuality_Jpeg50);
         layer->LoadFrame(instance, frame);
         //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6");
@@ -271,7 +270,7 @@
         // 0178023P**
         // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632)
         std::auto_ptr<OrthancFrameLayerSource> ct;
-        ct.reset(new OrthancFrameLayerSource(context.GetWebService()));
+        ct.reset(new OrthancFrameLayerSource(context_->GetWebService()));
         //ct->LoadInstance("c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0);
         //ct->LoadInstance("4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0);  // BAD SLICE
         //ct->SetImageQuality(SliceImageQuality_Jpeg50);
@@ -281,7 +280,7 @@
         widget->AddLayer(ct.release());
 
         std::auto_ptr<OrthancFrameLayerSource> pet;
-        pet.reset(new OrthancFrameLayerSource(context.GetWebService()));
+        pet.reset(new OrthancFrameLayerSource(context_->GetWebService()));
         //pet->LoadInstance("a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0);
         pet->LoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e");
         pet->Register(*this);
@@ -309,8 +308,8 @@
 
         widget_ = widget.get();
         widget_->SetTransmitMouseOver(true);
-        widget_->SetInteractor(context.AddInteractor(new Interactor(*this)));
-        context.SetCentralWidget(widget.release());
+        widget_->SetInteractor(context_->AddInteractor(new Interactor(*this)));
+        context_->SetCentralWidget(widget.release());
       }
     };
   }
--- a/Applications/Samples/SingleVolumeApplication.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/SingleVolumeApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -89,7 +89,7 @@
 
 
     public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
         generic.add_options()
@@ -108,8 +108,7 @@
         options.add(generic);    
       }
 
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
+      virtual void Initialize(IStatusBar& statusBar,
                               const boost::program_options::variables_map& parameters)
       {
         using namespace OrthancStone;
@@ -147,8 +146,8 @@
           throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
         }
 
-        unsigned int threads = parameters["threads"].as<unsigned int>();
-        bool reverse = parameters["reverse"].as<bool>();
+        //unsigned int threads = parameters["threads"].as<unsigned int>();
+        //bool reverse = parameters["reverse"].as<bool>();
 
         std::string tmp = parameters["projection"].as<std::string>();
         Orthanc::Toolbox::ToLowerCase(tmp);
@@ -175,7 +174,7 @@
         std::auto_ptr<LayerWidget> widget(new LayerWidget);
 
 #if 0
-        std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context.GetWebService(), true));
+        std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context_->GetWebService(), true));
         if (series.empty())
         {
           volume->ScheduleLoadInstance(instance);
@@ -187,8 +186,8 @@
 
         widget->AddLayer(new VolumeImageSource(*volume));
 
-        context.AddInteractor(new Interactor(*volume, *widget, projection, 0));
-        context.AddSlicedVolume(volume.release());
+        context_->AddInteractor(new Interactor(*volume, *widget, projection, 0));
+        context_->AddSlicedVolume(volume.release());
 
         {
           RenderStyle s;
@@ -199,14 +198,14 @@
           widget->SetLayerStyle(0, s);
         }
 #else
-        std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context.GetWebService(), false));
+        std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context_->GetWebService(), false));
         //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8");  // 0178023P
         //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d");
         //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // IBA
         //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0");  // 0522c0001 TCIA
         ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953");  // Captain
         
-        std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context.GetWebService(), true));
+        std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context_->GetWebService(), true));
         //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53");  // 0178023P
         //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e");
         //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1
@@ -216,7 +215,7 @@
         pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6");  // Captain 1
         //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1");  // Captain 2
 
-        std::auto_ptr<StructureSetLoader> rtStruct(new StructureSetLoader(context.GetWebService()));
+        std::auto_ptr<StructureSetLoader> rtStruct(new StructureSetLoader(context_->GetWebService()));
         //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3");  // 0178023P
         //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // IBA
         //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20");  // 0522c0001 TCIA
@@ -226,12 +225,12 @@
         widget->AddLayer(new VolumeImageSource(*pet));
         widget->AddLayer(new DicomStructureSetRendererFactory(*rtStruct));
         
-        context.AddInteractor(new Interactor(*pet, *widget, projection, 1));
-        //context.AddInteractor(new VolumeImageInteractor(*ct, *widget, projection));
+        context_->AddInteractor(new Interactor(*pet, *widget, projection, 1));
+        //context_->AddInteractor(new VolumeImageInteractor(*ct, *widget, projection));
 
-        context.AddSlicedVolume(ct.release());
-        context.AddSlicedVolume(pet.release());
-        context.AddVolumeLoader(rtStruct.release());
+        context_->AddSlicedVolume(ct.release());
+        context_->AddSlicedVolume(pet.release());
+        context_->AddVolumeLoader(rtStruct.release());
 
         {
           RenderStyle s;
@@ -263,7 +262,7 @@
         statusBar.SetMessage("Use the keys \"c\" to draw circles");
 
         widget->SetTransmitMouseOver(true);
-        context.SetCentralWidget(widget.release());
+        context_->SetCentralWidget(widget.release());
       }
     };
   }
--- a/Applications/Samples/TestPatternApplication.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Samples/TestPatternApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -34,7 +34,7 @@
     class TestPatternApplication : public SampleApplicationBase
     {
     public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
         generic.add_options()
@@ -44,8 +44,7 @@
         options.add(generic);    
       }
 
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
+      virtual void Initialize(IStatusBar& statusBar,
                               const boost::program_options::variables_map& parameters)
       {
         using namespace OrthancStone;
@@ -56,8 +55,8 @@
         layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>()));
         layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as<bool>()));
 
-        context.SetCentralWidget(layout.release());
-        context.SetUpdateDelay(25);  // If animation, update the content each 25ms
+        context_->SetCentralWidget(layout.release());
+        context_->SetUpdateDelay(25);  // If animation, update the content each 25ms
       }
     };
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/index.html	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,21 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+
+    <title>Wasm Samples</title>
+    <link href="samples-styles.css" rel="stylesheet" />
+
+<body>
+    <ul>
+        <li><a href="simple-viewer.html">Simple Viewer</a></li>
+    </ul>
+</body>
+
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/simple-viewer.html	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,23 @@
+<!doctype html>
+
+<html lang="us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+
+    <title>Simple Viewer</title>
+    <link href="samples-styles.css" rel="stylesheet" />
+
+<body>
+  <div>
+    <canvas id="canvas" data-width-ratio="20" data-height-ratio="50"></canvas>
+    <canvas id="canvas2" data-width-ratio="70" data-height-ratio="50"></canvas>
+  </div>
+  <script type="text/javascript" src="app-simple-viewer.js"></script>
+</body>
+
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/simple-viewer.ts	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,3 @@
+///<reference path='../../../Platforms/Wasm/wasm-application.ts'/>
+
+InitializeWasmApplication("OrthancStoneSimpleViewer", "../../../stone-orthanc");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/tsconfig-samples.json	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,10 @@
+{
+    "extends" : "../../../Platforms/Wasm/tsconfig-stone",
+    "compilerOptions": {
+        "sourceMap": false,
+        "lib" : [
+            "es2017",
+            "dom"
+        ]
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/tsconfig-simple-viewer.json	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,9 @@
+{
+    "extends" : "./tsconfig-samples",
+    "compilerOptions": {
+        "outFile": "../../../Platforms/Wasm/build-web/app-simple-viewer.js"
+    },
+    "include" : [
+        "simple-viewer.ts"
+    ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/samples-library.js	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,8 @@
+// this file contains the JS method you want to expose to C++ code
+
+// mergeInto(LibraryManager.library, {
+//    ScheduleRedraw: function() {
+//      ScheduleRedraw();
+//    }
+//  });
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/BasicSdlApplication.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,301 @@
+/**
+ * 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/>.
+ **/
+
+
+#if ORTHANC_ENABLE_SDL != 1
+#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1
+#endif
+
+#include "BasicSdlApplication.h"
+#include <boost/program_options.hpp>
+
+#include "../../Framework/Toolbox/MessagingToolbox.h"
+#include "SdlEngine.h"
+
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>
+
+namespace OrthancStone
+{
+  // Anonymous namespace to avoid clashes against other compilation modules
+  namespace
+  {
+    class LogStatusBar : public IStatusBar
+    {
+    public:
+      virtual void ClearMessage()
+      {
+      }
+
+      virtual void SetMessage(const std::string& message)
+      {
+        LOG(WARNING) << message;
+      }
+    };
+  }
+
+
+  static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options)
+  {
+    // Declare the supported parameters
+    boost::program_options::options_description generic("Generic options");
+    generic.add_options()
+      ("help", "Display this help and exit")
+      ("verbose", "Be verbose in logs")
+      ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+       "URL to the Orthanc server")
+      ("username", "Username for the Orthanc server")
+      ("password", "Password for the Orthanc server")
+      ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates")
+      ;
+
+    options.add(generic);
+
+    boost::program_options::options_description sdl("SDL options");
+    sdl.add_options()
+      ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window")
+      ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window")
+      ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL")
+      ;
+
+    options.add(sdl);
+  }
+
+
+  int BasicSdlApplication::ExecuteWithSdl(MessageBroker& broker,
+                                          IBasicApplication& application,
+                                        int argc,
+                                        char* argv[])
+  {
+    /******************************************************************
+     * Initialize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Orthanc::Logging::Initialize();
+    Orthanc::Toolbox::InitializeOpenSsl();
+    Orthanc::HttpClient::GlobalInitialize();
+    SdlWindow::GlobalInitialize();
+
+
+    /******************************************************************
+     * Declare and parse the command-line options of the application
+     ******************************************************************/
+
+    boost::program_options::options_description options;
+    DeclareSdlCommandLineOptions(options);
+    application.DeclareStartupOptions(options);
+
+    boost::program_options::variables_map parameters;
+    bool error = false;
+
+    try
+    {
+      boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                    options(options).run(), parameters);
+      boost::program_options::notify(parameters);
+    }
+    catch (boost::program_options::error& e)
+    {
+      LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+      error = true;
+    }
+
+
+    /******************************************************************
+     * Configure the application with the command-line parameters
+     ******************************************************************/
+
+    if (error || parameters.count("help"))
+    {
+      std::cout << std::endl
+                << "Usage: " << argv[0] << " [OPTION]..."
+                << std::endl
+                << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+                << std::endl << std::endl
+                << "Demonstration application of Orthanc Stone using SDL."
+                << std::endl;
+
+      std::cout << options << "\n";
+      return error ? -1 : 0;
+    }
+
+    if (parameters.count("https-verify") &&
+        !parameters["https-verify"].as<bool>())
+    {
+      LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)";
+      Orthanc::HttpClient::ConfigureSsl(false, "");
+    }
+
+    if (parameters.count("verbose"))
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+    }
+
+    if (!parameters.count("width") ||
+        !parameters.count("height") ||
+        !parameters.count("opengl"))
+    {
+      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
+      return -1;
+    }
+
+    int w = parameters["width"].as<int>();
+    int h = parameters["height"].as<int>();
+    if (w <= 0 || h <= 0)
+    {
+      LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive";
+      return -1;
+    }
+
+    unsigned int width = static_cast<unsigned int>(w);
+    unsigned int height = static_cast<unsigned int>(h);
+    LOG(WARNING) << "Initial display size: " << width << "x" << height;
+
+    bool opengl = parameters["opengl"].as<bool>();
+    if (opengl)
+    {
+      LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes";
+    }
+    else
+    {
+      LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance";
+    }
+
+    bool success = true;
+    try
+    {
+      /****************************************************************
+       * Initialize the connection to the Orthanc server
+       ****************************************************************/
+
+      Orthanc::WebServiceParameters webServiceParameters;
+
+      if (parameters.count("orthanc"))
+      {
+        webServiceParameters.SetUrl(parameters["orthanc"].as<std::string>());
+      }
+
+      if (parameters.count("username"))
+      {
+        webServiceParameters.SetUsername(parameters["username"].as<std::string>());
+      }
+
+      if (parameters.count("password"))
+      {
+        webServiceParameters.SetPassword(parameters["password"].as<std::string>());
+      }
+
+      LOG(WARNING) << "URL to the Orthanc REST API: " << webServiceParameters.GetUrl();
+
+      {
+        OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters);
+        if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+        {
+          LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+      }
+
+
+      /****************************************************************
+       * Initialize the application
+       ****************************************************************/
+
+      LOG(WARNING) << "Creating the widgets of the application";
+
+      LogStatusBar statusBar;
+
+      boost::mutex stoneGlobalMutex;
+      Oracle oracle(stoneGlobalMutex, 4); // use 4 threads to download content
+      OracleWebService webService(broker, oracle, webServiceParameters);
+      BasicSdlApplicationContext context(webService);
+
+      application.Initialize(&context, statusBar, parameters);
+
+      {
+        BasicSdlApplicationContext::ViewportLocker locker(context);
+        context.SetCentralWidget(application.GetCentralWidget());
+        locker.GetViewport().SetStatusBar(statusBar);
+      }
+
+      std::string title = application.GetTitle();
+      if (title.empty())
+      {
+        title = "Stone of Orthanc";
+      }
+
+      {
+        /**************************************************************
+         * Run the application inside a SDL window
+         **************************************************************/
+
+        LOG(WARNING) << "Starting the application";
+
+        SdlWindow window(title.c_str(), width, height, opengl);
+        SdlEngine sdl(window, context);
+
+        {
+          BasicSdlApplicationContext::ViewportLocker locker(context);
+          locker.GetViewport().Register(sdl);  // (*)
+        }
+
+        context.Start();
+        sdl.Run();
+
+        LOG(WARNING) << "Stopping the application";
+
+        // Don't move the "Stop()" command below out of the block,
+        // otherwise the application might crash, because the
+        // "SdlEngine" is an observer of the viewport (*) and the
+        // update thread started by "context.Start()" would call a
+        // destructed object (the "SdlEngine" is deleted with the
+        // lexical scope).
+        context.Stop();
+      }
+
+
+      /****************************************************************
+       * Finalize the application
+       ****************************************************************/
+
+      LOG(WARNING) << "The application has stopped";
+      application.Finalize();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+      success = false;
+    }
+
+
+    /******************************************************************
+     * Finalize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    SdlWindow::GlobalFinalize();
+    Orthanc::HttpClient::GlobalFinalize();
+    Orthanc::Toolbox::FinalizeOpenSsl();
+
+    return (success ? 0 : -1);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/BasicSdlApplication.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,44 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../IBasicApplication.h"
+
+#if ORTHANC_ENABLE_SDL != 1
+#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1
+#endif
+
+#include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
+
+namespace OrthancStone
+{
+  class BasicSdlApplication
+  {
+  public:
+
+    static int ExecuteWithSdl(MessageBroker& broker,
+                              IBasicApplication& application,
+                              int argc,
+                              char* argv[]);
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/BasicSdlApplicationContext.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,84 @@
+/**
+ * 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 "BasicSdlApplicationContext.h"
+
+namespace OrthancStone
+{
+  IWidget& BasicSdlApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
+  {
+    centralViewport_->SetCentralWidget(widget);
+    return *widget;
+  }
+
+
+  void BasicSdlApplicationContext::UpdateThread(BasicSdlApplicationContext* that)
+  {
+    while (!that->stopped_)
+    {
+      {
+        ViewportLocker locker(*that);
+        locker.GetViewport().UpdateContent();
+      }
+      
+      boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_));
+    }
+  }
+  
+
+  BasicSdlApplicationContext::BasicSdlApplicationContext(OracleWebService& webService) : // Orthanc::WebServiceParameters& orthanc, WidgetViewport* centralViewport) :
+    BasicApplicationContext(webService),
+    oracleWebService_(&webService),
+//    oracle_(viewportMutex_, 4),  // Use 4 threads to download
+//    webService_(oracle_, orthanc),
+//    centralViewport_(centralViewport),
+    centralViewport_(new OrthancStone::WidgetViewport()),
+    stopped_(true),
+    updateDelay_(100)   // By default, 100ms between each refresh of the content
+  {
+    srand(time(NULL)); 
+  }
+
+
+  void BasicSdlApplicationContext::Start()
+  {
+    oracleWebService_->Start();
+
+    if (centralViewport_->HasUpdateContent())
+    {
+      stopped_ = false;
+      updateThread_ = boost::thread(UpdateThread, this);
+    }
+  }
+
+
+  void BasicSdlApplicationContext::Stop()
+  {
+    stopped_ = true;
+    
+    if (updateThread_.joinable())
+    {
+      updateThread_.join();
+    }
+    
+    oracleWebService_->Stop();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/BasicSdlApplicationContext.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,90 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Viewport/WidgetViewport.h"
+#include "../../Framework/Volumes/ISlicedVolume.h"
+#include "../../Framework/Volumes/IVolumeLoader.h"
+#include "../../Framework/Widgets/IWorldSceneInteractor.h"
+#include "../../Platforms/Generic/OracleWebService.h"
+
+#include <list>
+#include <boost/thread.hpp>
+#include "../BasicApplicationContext.h"
+
+namespace OrthancStone
+{
+  class BasicSdlApplicationContext : public BasicApplicationContext
+  {
+  private:
+
+    static void UpdateThread(BasicSdlApplicationContext* that);
+
+    OracleWebService*   oracleWebService_;
+    boost::mutex        viewportMutex_;
+    std::unique_ptr<WidgetViewport>      centralViewport_;
+    boost::thread       updateThread_;
+    bool                stopped_;
+    unsigned int        updateDelay_;
+
+  public:
+    class ViewportLocker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      IViewport&                 viewport_;
+
+    public:
+      ViewportLocker(BasicSdlApplicationContext& that) :
+        lock_(that.viewportMutex_),
+        viewport_(*(that.centralViewport_.get()))
+      {
+      }
+
+      IViewport& GetViewport() const
+      {
+        return viewport_;
+      }
+    };
+
+    
+    BasicSdlApplicationContext(OracleWebService& webService);
+
+    virtual ~BasicSdlApplicationContext() {}
+
+    virtual IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
+
+    virtual IWebService& GetWebService()
+    {
+      return webService_;
+    }
+    
+    void Start();
+
+    void Stop();
+
+    void SetUpdateDelay(unsigned int delay)  // In milliseconds
+    {
+      updateDelay_ = delay;
+    }
+  };
+}
--- a/Applications/Sdl/SdlEngine.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Sdl/SdlEngine.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -29,7 +29,7 @@
 
 namespace OrthancStone
 {
-  void SdlEngine::SetSize(BasicApplicationContext::ViewportLocker& locker,
+  void SdlEngine::SetSize(BasicSdlApplicationContext::ViewportLocker& locker,
                           unsigned int width,
                           unsigned int height)
   {
@@ -42,7 +42,7 @@
   {
     if (viewportChanged_)
     {
-      BasicApplicationContext::ViewportLocker locker(context_);
+      BasicSdlApplicationContext::ViewportLocker locker(context_);
       surface_.Render(locker.GetViewport());
 
       viewportChanged_ = false;
@@ -99,7 +99,7 @@
 
 
   SdlEngine::SdlEngine(SdlWindow& window,
-                       BasicApplicationContext& context) :
+                       BasicSdlApplicationContext& context) :
     window_(window),
     context_(context),
     surface_(window),
@@ -119,7 +119,7 @@
     const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
 
     {
-      BasicApplicationContext::ViewportLocker locker(context_);
+      BasicSdlApplicationContext::ViewportLocker locker(context_);
       SetSize(locker, window_.GetWidth(), window_.GetHeight());
       locker.GetViewport().SetDefaultView();
     }
@@ -134,7 +134,7 @@
       while (!stop &&
              SDL_PollEvent(&event))
       {
-        BasicApplicationContext::ViewportLocker locker(context_);
+        BasicSdlApplicationContext::ViewportLocker locker(context_);
 
         if (event.type == SDL_QUIT) 
         {
--- a/Applications/Sdl/SdlEngine.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Applications/Sdl/SdlEngine.h	Tue Jul 17 14:43:42 2018 +0200
@@ -24,7 +24,7 @@
 #if ORTHANC_ENABLE_SDL == 1
 
 #include "SdlCairoSurface.h"
-#include "../BasicApplicationContext.h"
+#include "BasicSdlApplicationContext.h"
 
 namespace OrthancStone
 {
@@ -32,11 +32,11 @@
   {
   private:
     SdlWindow&                window_;
-    BasicApplicationContext&  context_;
+    BasicSdlApplicationContext&  context_;
     SdlCairoSurface           surface_;
     bool                      viewportChanged_;
 
-    void SetSize(BasicApplicationContext::ViewportLocker& locker,
+    void SetSize(BasicSdlApplicationContext::ViewportLocker& locker,
                  unsigned int width,
                  unsigned int height);
     
@@ -47,7 +47,7 @@
 
   public:
     SdlEngine(SdlWindow& window,
-              BasicApplicationContext& context);
+              BasicSdlApplicationContext& context);
   
     virtual ~SdlEngine();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Wasm/StartupParametersBuilder.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,43 @@
+#include "StartupParametersBuilder.h"
+
+namespace OrthancStone
+{
+    void StartupParametersBuilder::Clear() {
+        startupParameters_.clear();
+    }
+
+    void StartupParametersBuilder::SetStartupParameter(const char* name, const char* value) {
+        startupParameters_.push_back(std::make_tuple(name, value));
+    }
+
+    void StartupParametersBuilder::GetStartupParameters(boost::program_options::variables_map& parameters, const boost::program_options::options_description& options) {
+        
+        const char* argv[startupParameters_.size() + 1];
+        int argCounter = 0;
+        argv[0] = "Toto.exe";
+        argCounter++;
+
+        std::string cmdLine = "";
+        for (StartupParameters::const_iterator it = startupParameters_.begin(); it != startupParameters_.end(); it++) {
+            char* arg = new char[128];
+            snprintf(arg, 128, "--%s=%s", std::get<0>(*it).c_str(), std::get<1>(*it).c_str());
+            argv[argCounter] = arg;
+            cmdLine = cmdLine + " --" + std::get<0>(*it) + "=" + std::get<1>(*it);
+            argCounter++;
+        }
+
+        printf("simulated cmdLine = %s\n", cmdLine.c_str());
+
+        try
+        {
+            boost::program_options::store(boost::program_options::command_line_parser(argCounter, argv).
+                                            options(options).run(), parameters);
+            boost::program_options::notify(parameters);
+        }
+        catch (boost::program_options::error& e)
+        {
+            printf("Error while parsing the command-line arguments: %s\n", e.what());
+        }
+
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Wasm/StartupParametersBuilder.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,50 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include <boost/program_options.hpp>
+#include <tuple>
+
+#if ORTHANC_ENABLE_SDL == 1
+#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 0
+#endif
+
+namespace OrthancStone
+{
+  // This class is used to generate boost program options from a dico.
+  // In a Wasm context, startup options are passed as URI arguments that
+  // are then passed to this class as a dico.
+  // This class regenerates a fake command-line and parses it to produce
+  // the same output as if the app was started at command-line.
+  class StartupParametersBuilder
+  {
+    typedef std::list<std::tuple<std::string, std::string>> StartupParameters;
+    StartupParameters startupParameters_;
+
+  public:
+
+    void Clear();
+    void SetStartupParameter(const char* name, const char* value);
+    void GetStartupParameters(boost::program_options::variables_map& parameters_, const boost::program_options::options_description& options);
+  };
+
+}
--- a/Framework/Layers/DicomStructureSetRendererFactory.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/DicomStructureSetRendererFactory.h	Tue Jul 17 14:43:42 2018 +0200
@@ -37,21 +37,22 @@
     {
       LayerSourceBase::NotifyGeometryReady();
     }
-      
+
     virtual void NotifyGeometryError(const IVolumeLoader& loader)
     {
       LayerSourceBase::NotifyGeometryError();
     }
-      
+
     virtual void NotifyContentChange(const IVolumeLoader& loader)
     {
       LayerSourceBase::NotifyContentChange();
     }
-    
+
     StructureSetLoader& loader_;
 
   public:
-    DicomStructureSetRendererFactory(StructureSetLoader& loader) :
+    DicomStructureSetRendererFactory(MessageBroker& broker, StructureSetLoader& loader) :
+      LayerSourceBase(broker),
       loader_(loader)
     {
       loader_.Register(*this);
--- a/Framework/Layers/ILayerSource.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -23,47 +23,80 @@
 
 #include "ILayerRenderer.h"
 #include "../Toolbox/Slice.h"
+#include "../../Framework/Messages/IObservable.h"
+#include "../../Framework/Messages/IMessage.h"
 
 namespace OrthancStone
 {
-  class ILayerSource : public boost::noncopyable
+  class ILayerSource : public IObservable
   {
   public:
-    class IObserver : public boost::noncopyable
+    struct SliceChangedMessage : public IMessage
+    {
+      const Slice& slice_;
+      SliceChangedMessage(const Slice& slice)
+        : IMessage(MessageType_SliceChanged),
+          slice_(slice)
+      {
+      }
+    };
+
+    struct LayerReadyMessage : public IMessage
     {
-    public:
-      virtual ~IObserver()
+      std::auto_ptr<ILayerRenderer>& layer_;
+      const CoordinateSystem3D& slice_;
+      bool isError_;
+
+      LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
+                        const CoordinateSystem3D& slice,
+                        bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
+        : IMessage(MessageType_LayerReady),
+          layer_(layer),
+          slice_(slice),
+          isError_(isError)
       {
       }
+    };
 
-      // Triggered as soon as the source has enough information to
-      // answer to "GetExtent()"
-      virtual void NotifyGeometryReady(const ILayerSource& source) = 0;
-      
-      virtual void NotifyGeometryError(const ILayerSource& source) = 0;
-      
-      // Triggered if the content of several slices in the source
-      // volume has changed
-      virtual void NotifyContentChange(const ILayerSource& source) = 0;
+    //    class IObserver : public boost::noncopyable
+    //    {
+    //    public:
+    //      virtual ~IObserver()
+    //      {
+    //      }
+
+    //      // Triggered as soon as the source has enough information to
+    //      // answer to "GetExtent()"
+    //      virtual void NotifyGeometryReady(const ILayerSource& source) = 0;
+
+    //      virtual void NotifyGeometryError(const ILayerSource& source) = 0;
 
-      // Triggered if the content of some individual slice in the
-      // source volume has changed
-      virtual void NotifySliceChange(const ILayerSource& source,
-                                     const Slice& slice) = 0;
- 
-      // The layer must be deleted by the observer that releases the
-      // std::auto_ptr
-      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
-                                    const ILayerSource& source,
-                                    const CoordinateSystem3D& slice,
-                                    bool isError) = 0;  // TODO Shouldn't this be separate as NotifyLayerError?
-    };
+    //      // Triggered if the content of several slices in the source
+    //      // volume has changed
+    //      virtual void NotifyContentChange(const ILayerSource& source) = 0;
+
+    //      // Triggered if the content of some individual slice in the
+    //      // source volume has changed
+    //      virtual void NotifySliceChange(const ILayerSource& source,
+    //                                     const Slice& slice) = 0;
+
+    //      // The layer must be deleted by the observer that releases the
+    //      // std::auto_ptr
+    //      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
+    //                                    const ILayerSource& source,
+    //                                    const CoordinateSystem3D& slice,
+    //                                    bool isError) = 0;  // TODO Shouldn't this be separate as NotifyLayerError?
+    //    };
     
+    ILayerSource(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+
     virtual ~ILayerSource()
     {
     }
 
-    virtual void Register(IObserver& observer) = 0;
+    //    virtual void Register(IObserver& observer) = 0;
 
     virtual bool GetExtent(std::vector<Vector>& points,
                            const CoordinateSystem3D& viewportSlice) = 0;
--- a/Framework/Layers/LayerSourceBase.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -25,63 +25,59 @@
 
 namespace OrthancStone
 {
-  namespace
-  {
-    class LayerReadyFunctor : public boost::noncopyable
-    {
-    private:
-      std::auto_ptr<ILayerRenderer>  layer_;
-      const CoordinateSystem3D&      slice_;
-      bool                           isError_;
+//  namespace
+//  {
+//    class LayerReadyFunctor : public boost::noncopyable
+//    {
+//    private:
+//      std::auto_ptr<ILayerRenderer>  layer_;
+//      const CoordinateSystem3D&      slice_;
+//      bool                           isError_;
       
-    public:
-      LayerReadyFunctor(ILayerRenderer* layer,
-                        const CoordinateSystem3D& slice,
-                        bool isError) :
-        layer_(layer),
-        slice_(slice),
-        isError_(isError)
-      {
-      }
+//    public:
+//      LayerReadyFunctor(ILayerRenderer* layer,
+//                        const CoordinateSystem3D& slice,
+//                        bool isError) :
+//        layer_(layer),
+//        slice_(slice),
+//        isError_(isError)
+//      {
+//      }
 
-      void operator() (ILayerSource::IObserver& observer,
-                       const ILayerSource& source)
-      {
-        observer.NotifyLayerReady(layer_, source, slice_, isError_);
-      }
-    };
-  }
+//      void operator() (ILayerSource::IObserver& observer,
+//                       const ILayerSource& source)
+//      {
+//        observer.NotifyLayerReady(layer_, source, slice_, isError_);
+//      }
+//    };
+//  }
 
   void LayerSourceBase::NotifyGeometryReady()
   {
-    observers_.Apply(*this, &IObserver::NotifyGeometryReady);
+    EmitMessage(IMessage(MessageType_GeometryReady));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    observers_.Apply(*this, &IObserver::NotifyGeometryError);
-  }  
+    EmitMessage(IMessage(MessageType_GeometryError));
+  }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    observers_.Apply(*this, &IObserver::NotifyContentChange);
+    EmitMessage(IMessage(MessageType_ContentChanged));
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
   {
-    observers_.Apply(*this, &IObserver::NotifySliceChange, slice);
+    EmitMessage(ILayerSource::SliceChangedMessage(slice));
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
                                          const CoordinateSystem3D& slice,
                                          bool isError)
   {
-    LayerReadyFunctor functor(layer, slice, isError);
-    observers_.Notify(*this, functor);
+    std::auto_ptr<ILayerRenderer> renderer(layer);
+    EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError));
   }
 
-  void LayerSourceBase::Register(IObserver& observer)
-  {
-    observers_.Register(observer);
-  }
 }
--- a/Framework/Layers/LayerSourceBase.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Tue Jul 17 14:43:42 2018 +0200
@@ -28,11 +28,6 @@
 {
   class LayerSourceBase : public ILayerSource
   {
-  private:
-    typedef ObserversRegistry<ILayerSource, IObserver>  Observers;
-
-    Observers  observers_;
-
   protected:
     void NotifyGeometryReady();
     
@@ -46,7 +41,9 @@
                           const CoordinateSystem3D& slice,
                           bool isError);
 
-  public:
-    virtual void Register(IObserver& observer);
+    LayerSourceBase(MessageBroker& broker)
+      : ILayerSource(broker)
+    {}
+
   };
 }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -31,7 +31,7 @@
 
 namespace OrthancStone
 {
-  void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader& loader)
   {
     if (loader.GetSliceCount() > 0)
     {
@@ -43,23 +43,23 @@
     }
   }
 
-  void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader& loader)
   {
     LayerSourceBase::NotifyGeometryError();
   }
 
-  void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
+  void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
                                                       const Slice& slice,
                                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
                                                       SliceImageQuality quality)
   {
-    bool isFull = (quality == SliceImageQuality_Full);
+    bool isFull = (quality == SliceImageQuality_FullPng || quality == SliceImageQuality_FullPam);
     LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull),
                                       slice.GetGeometry(), false);
   }
 
-  void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
+  void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
                                                       const Slice& slice,
                                                       SliceImageQuality quality)
@@ -68,9 +68,11 @@
   }
 
 
-  OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) :
-    loader_(*this, orthanc),
-    quality_(SliceImageQuality_Full)
+  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
+    LayerSourceBase(broker),
+    OrthancSlicesLoader::ISliceLoaderObserver(broker),
+    loader_(broker, *this, orthanc),
+    quality_(SliceImageQuality_FullPng)
   {
   }
 
--- a/Framework/Layers/OrthancFrameLayerSource.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue Jul 17 14:43:42 2018 +0200
@@ -29,29 +29,29 @@
 {  
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    private OrthancSlicesLoader::ICallback
+    private OrthancSlicesLoader::ISliceLoaderObserver
   {
   private:
     OrthancSlicesLoader  loader_;
     SliceImageQuality    quality_;
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader);
+    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader);
 
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader);
+    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader);
 
-    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        std::auto_ptr<Orthanc::ImageAccessor>& image,
                                        SliceImageQuality quality);
 
-    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        SliceImageQuality quality);
 
   public:
-    OrthancFrameLayerSource(IWebService& orthanc);
+    OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc);
 
     void LoadSeries(const std::string& seriesId);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IMessage.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,42 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "MessageType.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone {
+
+  struct IMessage  : public boost::noncopyable
+  {
+    MessageType messageType_;
+  public:
+    IMessage(const MessageType& messageType)
+      : messageType_(messageType)
+    {}
+    virtual ~IMessage() {}
+
+    MessageType GetType() const {return messageType_;}
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IObservable.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,62 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "MessageBroker.h"
+#include <set>
+
+namespace OrthancStone {
+
+  class IObservable : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                     broker_;
+
+    std::set<IObserver*>              observers_;
+
+  public:
+
+    IObservable(MessageBroker& broker)
+      : broker_(broker)
+    {
+    }
+    virtual ~IObservable()
+    {
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      broker_.EmitMessage(*this, observers_, message);
+    }
+
+    void RegisterObserver(IObserver& observer)
+    {
+      observers_.insert(&observer);
+    }
+
+    void UnregisterObserver(IObserver& observer)
+    {
+      observers_.erase(&observer);
+    }
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IObserver.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,52 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "MessageBroker.h"
+#include "IMessage.h"
+#include "IObservable.h"
+
+namespace OrthancStone {
+
+  class IObservable;
+
+  class IObserver : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+  public:
+    IObserver(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IObserver()
+    {
+      broker_.Unregister(*this);
+    }
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageBroker.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,49 @@
+/**
+ * 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 "MessageBroker.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <vector>
+
+#include "IObserver.h"
+#include "MessageType.h"
+
+namespace OrthancStone {
+
+  void MessageBroker::EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message)
+  {
+    std::vector<IObserver*> activeObservers;
+    std::set_intersection(observers.begin(),
+                          observers.end(),
+                          activeObservers_.begin(),
+                          activeObservers_.end(),
+                          std::back_inserter(activeObservers)
+                          );
+
+    for (std::vector<IObserver*>::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++)
+    {
+      (*observer)->HandleMessage(from, message);
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageBroker.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,62 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../StoneEnumerations.h"
+
+#include "boost/noncopyable.hpp"
+#include <map>
+#include <list>
+#include <set>
+
+namespace OrthancStone
+{
+  class IObserver;
+  class IObservable;
+  class IMessage;
+
+  /*
+   * This is a central message broker.  It keeps track of all observers and knows
+   * when an observer is deleted.
+   * This way, it can prevent an observable to send a message to a dead observer.
+   */
+  class MessageBroker : public boost::noncopyable
+  {
+
+    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
+
+  public:
+
+    void Register(IObserver& observer)
+    {
+      activeObservers_.insert(&observer);
+    }
+
+    void Unregister(IObserver& observer)
+    {
+      activeObservers_.erase(&observer);
+    }
+
+    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageType.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,44 @@
+/**
+ * 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/>.
+ **/
+
+#pragma once
+
+namespace OrthancStone {
+
+  enum MessageType
+  {
+    MessageType_Generic,
+
+    MessageType_GeometryReady,
+    MessageType_GeometryError,
+    MessageType_ContentChanged,
+    MessageType_SliceChanged,
+    MessageType_LayerReady,
+
+    MessageType_SliceGeometryReady,
+    MessageType_SliceGeometryError,
+    MessageType_SliceImageReady,
+    MessageType_SliceImageError,
+
+    MessageType_HttpRequestSuccess,
+    MessageType_HttpRequestError
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SmartLoader.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,51 @@
+/**
+ * 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 "SmartLoader.h"
+#include "Layers/OrthancFrameLayerSource.h"
+
+namespace OrthancStone
+{
+  SmartLoader::SmartLoader(MessageBroker& broker, IWebService& webService) :
+    IObservable(broker),
+    IObserver(broker),
+    imageQuality_(SliceImageQuality_FullPam),
+    webService_(webService)
+  {}
+
+  void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    // forward messages to its own observers
+    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
+  }
+
+  ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame)
+  {
+    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_));
+    layerSource->SetImageQuality(imageQuality_);
+    layerSource->RegisterObserver(*this);
+    layerSource->LoadFrame(instanceId, frame);
+
+    return layerSource.release();
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SmartLoader.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,49 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "Layers/ILayerSource.h"
+#include "Messages/IObservable.h"
+#include "../Platforms/Generic/OracleWebService.h"
+
+namespace OrthancStone
+{
+  class SmartLoader : public IObservable, IObserver
+  {
+    SliceImageQuality imageQuality_;
+    IWebService& webService_;
+
+  public:
+    SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
+    void PreloadStudy(const std::string studyId) {/* TODO */}
+    void PreloadSeries(const std::string seriesId) {/* TODO */}
+
+    void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
+
+    ILayerSource* GetFrame(const std::string& instanceId, unsigned int frame);
+
+  };
+
+}
--- a/Framework/StoneEnumerations.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/StoneEnumerations.h	Tue Jul 17 14:43:42 2018 +0200
@@ -77,10 +77,13 @@
 
   enum SliceImageQuality
   {
-    SliceImageQuality_Full,
+    SliceImageQuality_FullPng,  // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth)
+    SliceImageQuality_FullPam,  // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN)
     SliceImageQuality_Jpeg50,
     SliceImageQuality_Jpeg90,
-    SliceImageQuality_Jpeg95
+    SliceImageQuality_Jpeg95,
+
+    SliceImageQuality_InternalRaw   // downloads the raw pixels data as they are stored in the DICOM file (internal use only)
   };
 
   enum SopClassUid
--- a/Framework/Toolbox/IWebService.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -22,41 +22,108 @@
 #pragma once
 
 #include <Core/IDynamicObject.h>
-
+#include "../../Framework/Messages/IObserver.h"
 #include <string>
+#include <Core/Logging.h>
 
 namespace OrthancStone
 {
-  class IWebService : public boost::noncopyable
-  {
-  public:
-    class ICallback : public boost::noncopyable
+    class IWebService
     {
+    protected:
+        MessageBroker& broker_;
     public:
-      virtual ~ICallback()
-      {
-      }
+        typedef std::map<std::string, std::string> Headers;
 
-      virtual void NotifyError(const std::string& uri,
-                               Orthanc::IDynamicObject* payload) = 0;
+        class ICallback : public IObserver
+        {
+        public:
+            struct HttpRequestSuccessMessage: public IMessage
+            {
+                const std::string& Uri;
+                const void* Answer;
+                size_t AnswerSize;
+                Orthanc::IDynamicObject* Payload;
+                HttpRequestSuccessMessage(const std::string& uri,
+                                          const void* answer,
+                                          size_t answerSize,
+                                          Orthanc::IDynamicObject* payload)
+                    : IMessage(MessageType_HttpRequestSuccess),
+                      Uri(uri),
+                      Answer(answer),
+                      AnswerSize(answerSize),
+                      Payload(payload)
+                {}
+            };
+
+            struct HttpRequestErrorMessage: public IMessage
+            {
+                const std::string& Uri;
+                Orthanc::IDynamicObject* Payload;
+                HttpRequestErrorMessage(const std::string& uri,
+                                        Orthanc::IDynamicObject* payload)
+                    : IMessage(MessageType_HttpRequestError),
+                      Uri(uri),
+                      Payload(payload)
+                {}
+            };
+
+            ICallback(MessageBroker& broker)
+                : IObserver(broker)
+            {}
+            virtual ~ICallback()
+            {
+            }
 
-      virtual void NotifySuccess(const std::string& uri,
-                                 const void* answer,
-                                 size_t answerSize,
-                                 Orthanc::IDynamicObject* payload) = 0;
-    };
-    
-    virtual ~IWebService()
-    {
-    }
+            virtual void HandleMessage(IObservable& from, const IMessage& message)
+            {
+                switch(message.GetType())
+                {
+                case MessageType_HttpRequestError:
+                {    const HttpRequestErrorMessage& msg = dynamic_cast<const HttpRequestErrorMessage&>(message);
+                    OnHttpRequestError(msg.Uri,
+                                       msg.Payload);
+                }; break;
+
+                case MessageType_HttpRequestSuccess:
+                {
+                    const HttpRequestSuccessMessage& msg = dynamic_cast<const HttpRequestSuccessMessage&>(message);
+                    OnHttpRequestSuccess(msg.Uri,
+                                         msg.Answer,
+                                         msg.AnswerSize,
+                                         msg.Payload);
+                }; break;
+                default:
+                  VLOG("unhandled message type" << message.GetType());
+                }
+            }
+
+            virtual void OnHttpRequestError(const std::string& uri,
+                                            Orthanc::IDynamicObject* payload) = 0;
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    Orthanc::IDynamicObject* payload) = 0;
+            virtual void OnHttpRequestSuccess(const std::string& uri,
+                                              const void* answer,
+                                              size_t answerSize,
+                                              Orthanc::IDynamicObject* payload) = 0;
+        };
+
+        IWebService(MessageBroker& broker)
+            : broker_(broker)
+        {}
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload) = 0;
-  };
+        virtual ~IWebService()
+        {
+        }
+
+        virtual void ScheduleGetRequest(ICallback& callback,
+                                        const std::string& uri,
+                                        const Headers& headers,
+                                        Orthanc::IDynamicObject* payload) = 0;
+
+        virtual void SchedulePostRequest(ICallback& callback,
+                                         const std::string& uri,
+                                         const Headers& headers,
+                                         const std::string& body,
+                                         Orthanc::IDynamicObject* payload) = 0;
+    };
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -29,6 +29,7 @@
 #include <Core/Images/ImageProcessing.h>
 #include <Core/Images/JpegReader.h>
 #include <Core/Images/PngReader.h>
+#include <Core/Images/PamReader.h>
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
@@ -47,10 +48,10 @@
 static std::string base64_decode(const std::string &in)
 {
   std::string out;
-
+  
   std::vector<int> T(256,-1);
-  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; 
-
+  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
+  
   int val=0, valb=-8;
   for (size_t i = 0; i < in.size(); i++) {
     unsigned char c = in[i];
@@ -78,32 +79,32 @@
     const Slice*       slice_;
     std::string        instanceId_;
     SliceImageQuality  quality_;
-
+    
     Operation(Mode mode) :
       mode_(mode)
     {
     }
-
+    
   public:
     Mode GetMode() const
     {
       return mode_;
     }
-
+    
     SliceImageQuality GetQuality() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return quality_;
     }
-
+    
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return sliceIndex_;
     }
-
+    
     const Slice& GetSlice() const
     {
       assert(mode_ == Mode_LoadImage ||
@@ -111,32 +112,32 @@
       assert(slice_ != NULL);
       return *slice_;
     }
-
+    
     unsigned int GetFrame() const
     {
       assert(mode_ == Mode_FrameGeometry);
       return frame_;
     }
-      
+
     const std::string& GetInstanceId() const
     {
       assert(mode_ == Mode_FrameGeometry ||
              mode_ == Mode_InstanceGeometry);
       return instanceId_;
     }
-      
+
     static Operation* DownloadSeriesGeometry()
     {
       return new Operation(Mode_SeriesGeometry);
     }
-
+    
     static Operation* DownloadInstanceGeometry(const std::string& instanceId)
     {
       std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
       operation->instanceId_ = instanceId;
       return operation.release();
     }
-
+    
     static Operation* DownloadFrameGeometry(const std::string& instanceId,
                                             unsigned int frame)
     {
@@ -145,7 +146,7 @@
       operation->frame_ = frame;
       return operation.release();
     }
-
+    
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
                                          const Slice&  slice,
                                          SliceImageQuality quality)
@@ -156,106 +157,149 @@
       tmp->quality_ = quality;
       return tmp.release();
     }
-
+    
     static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
                                             const Slice&  slice)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
       tmp->sliceIndex_ = sliceIndex;
       tmp->slice_ = &slice;
-      tmp->quality_ = SliceImageQuality_Full;
+      tmp->quality_ = SliceImageQuality_InternalRaw;
       return tmp.release();
     }
+
+    static Operation* DownloadDicomFile(const Slice&  slice)
+    {
+      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
+      tmp->slice_ = &slice;
+      return tmp.release();
+    }
+
   };
-    
 
+  
   class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
   {
   private:
     OrthancSlicesLoader&  that_;
-
+    
   public:
-    WebCallback(OrthancSlicesLoader&  that) :
+    WebCallback(MessageBroker& broker, OrthancSlicesLoader&  that) :
+      IWebService::ICallback(broker),
       that_(that)
     {
     }
-
-    virtual void NotifySuccess(const std::string& uri,
-                               const void* answer,
-                               size_t answerSize,
-                               Orthanc::IDynamicObject* payload)
+    
+    virtual void OnHttpRequestSuccess(const std::string& uri,
+                                      const void* answer,
+                                      size_t answerSize,
+                                      Orthanc::IDynamicObject* payload)
     {
       std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-
+      
       switch (operation->GetMode())
       {
-        case Mode_SeriesGeometry:
-          that_.ParseSeriesGeometry(answer, answerSize);
+      case Mode_SeriesGeometry:
+        that_.ParseSeriesGeometry(answer, answerSize);
+        break;
+        
+      case Mode_InstanceGeometry:
+        that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
+        break;
+        
+      case Mode_FrameGeometry:
+        that_.ParseFrameGeometry(operation->GetInstanceId(),
+                                 operation->GetFrame(), answer, answerSize);
+        break;
+        
+      case Mode_LoadImage:
+        switch (operation->GetQuality())
+        {
+        case SliceImageQuality_FullPng:
+          that_.ParseSliceImagePng(*operation, answer, answerSize);
           break;
-
-        case Mode_InstanceGeometry:
-          that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
-          break;
-
-        case Mode_FrameGeometry:
-          that_.ParseFrameGeometry(operation->GetInstanceId(),
-                                   operation->GetFrame(), answer, answerSize);
+        case SliceImageQuality_FullPam:
+          that_.ParseSliceImagePam(*operation, answer, answerSize);
           break;
 
-        case Mode_LoadImage:
-          switch (operation->GetQuality())
-          {
-            case SliceImageQuality_Full:
-              that_.ParseSliceImagePng(*operation, answer, answerSize);
-              break;
-
-            case SliceImageQuality_Jpeg50:
-            case SliceImageQuality_Jpeg90:
-            case SliceImageQuality_Jpeg95:
-              that_.ParseSliceImageJpeg(*operation, answer, answerSize);
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-                
-          break;
-
-        case Mode_LoadRawImage:
-          that_.ParseSliceRawImage(*operation, answer, answerSize);
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-    virtual void NotifyError(const std::string& uri,
-                             Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      LOG(ERROR) << "Cannot download " << uri;
-
-      switch (operation->GetMode())
-      {
-        case Mode_FrameGeometry:
-        case Mode_SeriesGeometry:
-          that_.userCallback_.NotifyGeometryError(that_);
-          that_.state_ = State_Error;
-          break;
-
-        case Mode_LoadImage:
-          that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
-                                                    operation->GetSlice(),
-                                                    operation->GetQuality());
+        case SliceImageQuality_Jpeg50:
+        case SliceImageQuality_Jpeg90:
+        case SliceImageQuality_Jpeg95:
+          that_.ParseSliceImageJpeg(*operation, answer, answerSize);
           break;
           
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        break;
+        
+      case Mode_LoadRawImage:
+        that_.ParseSliceRawImage(*operation, answer, answerSize);
+        break;
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
-    }     
+    }
+    
+    virtual void OnHttpRequestError(const std::string& uri,
+                                    Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+      LOG(ERROR) << "Cannot download " << uri;
+      
+      switch (operation->GetMode())
+      {
+      case Mode_FrameGeometry:
+      case Mode_SeriesGeometry:
+        that_.userCallback_.OnSliceGeometryError(that_);
+        that_.state_ = State_Error;
+        break;
+        
+      case Mode_LoadImage:
+        that_.userCallback_.OnSliceImageError(that_, operation->GetSliceIndex(),
+                                              operation->GetSlice(),
+                                              operation->GetQuality());
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
   };
-
+  
+  void OrthancSlicesLoader::ISliceLoaderObserver::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    switch (message.GetType())
+    {
+    case MessageType_SliceGeometryReady:
+      OnSliceGeometryReady(dynamic_cast<OrthancSlicesLoader&>(from));
+      break;
+    case MessageType_SliceGeometryError:
+      OnSliceGeometryError(dynamic_cast<OrthancSlicesLoader&>(from));
+      break;
+    case MessageType_SliceImageReady:
+    {
+      const SliceImageReadyMessage& msg = dynamic_cast<const SliceImageReadyMessage&>(message);
+      OnSliceImageReady(dynamic_cast<OrthancSlicesLoader&>(from),
+                        msg.sliceIndex_,
+                        msg.slice_,
+                        msg.image_,
+                        msg.effectiveQuality_);
+    }; break;
+    case MessageType_SliceImageError:
+    {
+      const SliceImageErrorMessage& msg = dynamic_cast<const SliceImageErrorMessage&>(message);
+      OnSliceImageError(dynamic_cast<OrthancSlicesLoader&>(from),
+                        msg.sliceIndex_,
+                        msg.slice_,
+                        msg.effectiveQuality_);
+    }; break;
+    default:
+      VLOG("unhandled message type" << message.GetType());
+    }
+  }
 
   
   void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
@@ -267,19 +311,19 @@
     }
     else
     {
-      userCallback_.NotifySliceImageReady
-        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      userCallback_.OnSliceImageReady
+          (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
     }
   }
-
+  
   
   void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
   {
-    userCallback_.NotifySliceImageError
-      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    userCallback_.OnSliceImageError
+        (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
   }
-
-
+  
+  
   void OrthancSlicesLoader::SortAndFinalizeSlices()
   {
     bool ok = false;
@@ -295,21 +339,21 @@
         ok = true;
       }
     }
-
+    
     state_ = State_GeometryReady;
-
+    
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      userCallback_.NotifyGeometryReady(*this);
+      userCallback_.OnSliceGeometryReady(*this);
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
     }
   }
-
+  
   
   void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
                                                 size_t size)
@@ -318,18 +362,18 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     Json::Value::Members instances = series.getMemberNames();
-
+    
     slices_.Reserve(instances.size());
-
+    
     for (size_t i = 0; i < instances.size(); i++)
     {
       OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
-
+      
       Orthanc::DicomMap dicom;
       MessagingToolbox::ConvertDataset(dicom, dataset);
       
@@ -338,7 +382,7 @@
       {
         frames = 1;
       }
-
+      
       for (unsigned int frame = 0; frame < frames; frame++)
       {
         std::auto_ptr<Slice> slice(new Slice);
@@ -352,11 +396,11 @@
         }
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
                                                   const void* answer,
                                                   size_t size)
@@ -365,15 +409,15 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     OrthancPlugins::FullOrthancDataset dataset(tags);
-
+    
     Orthanc::DicomMap dicom;
     MessagingToolbox::ConvertDataset(dicom, dataset);
-      
+
     unsigned int frames;
     if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
     {
@@ -381,7 +425,7 @@
     }
     
     LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)";
-
+    
     for (unsigned int frame = 0; frame < frames; frame++)
     {
       std::auto_ptr<Slice> slice(new Slice);
@@ -392,15 +436,15 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        userCallback_.NotifyGeometryError(*this);
+        userCallback_.OnSliceGeometryError(*this);
         return;
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
                                                unsigned int frame,
                                                const void* answer,
@@ -410,33 +454,74 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     OrthancPlugins::FullOrthancDataset dataset(tags);
-
+    
     state_ = State_GeometryReady;
-
+    
     Orthanc::DicomMap dicom;
     MessagingToolbox::ConvertDataset(dicom, dataset);
-
+    
     std::auto_ptr<Slice> slice(new Slice);
     if (slice->ParseOrthancFrame(dicom, instanceId, frame))
     {
       LOG(INFO) << "Loaded instance " << instanceId;
       slices_.AddSlice(slice.release());
-      userCallback_.NotifyGeometryReady(*this);
+      userCallback_.OnSliceGeometryReady(*this);
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
     }
   }
-
+  
+  
+  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
+                                               const void* answer,
+                                               size_t size)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor>  image;
+    
+    try
+    {
+      image.reset(new Orthanc::PngReader);
+      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
 
-  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+        Orthanc::PixelFormat_SignedGrayscale16)
+    {
+      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+      else
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+    
+    NotifySliceImageSuccess(operation, image);
+  }
+  
+  void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation,
                                                const void* answer,
                                                size_t size)
   {
@@ -444,8 +529,8 @@
 
     try
     {
-      image.reset(new Orthanc::PngReader);
-      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size);
+      image.reset(new Orthanc::PamReader);
+      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(answer), size));
     }
     catch (Orthanc::OrthancException&)
     {
@@ -459,7 +544,7 @@
       NotifySliceImageError(operation);
       return;
     }
-      
+
     if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
         Orthanc::PixelFormat_SignedGrayscale16)
     {
@@ -475,9 +560,9 @@
     }
 
     NotifySliceImageSuccess(operation, image);
-  } 
+  }
 
-  
+
   void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
                                                 const void* answer,
                                                 size_t size)
@@ -491,7 +576,7 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     Json::Value& info = encoded["Orthanc"];
     if (!info.isMember("PixelData") ||
         !info.isMember("Stretched") ||
@@ -504,30 +589,30 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     bool isSigned = false;
     bool isStretched = info["Stretched"].asBool();
-
+    
     if (info.isMember("IsSigned"))
     {
       if (info["IsSigned"].type() != Json::booleanValue)
       {
         NotifySliceImageError(operation);
         return;
-      }          
+      }
       else
       {
         isSigned = info["IsSigned"].asBool();
       }
     }
-
+    
     std::auto_ptr<Orthanc::ImageAccessor> reader;
-
+    
     {
       std::string jpeg;
       //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
       jpeg = base64_decode(info["PixelData"].asString());
-
+      
       try
       {
         reader.reset(new Orthanc::JpegReader);
@@ -539,10 +624,10 @@
         return;
       }
     }
-
+    
     Orthanc::PixelFormat expectedFormat =
-      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
-
+        operation.GetSlice().GetConverter().GetExpectedPixelFormat();
+    
     if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
     {
       if (expectedFormat != Orthanc::PixelFormat_RGB24)
@@ -550,7 +635,7 @@
         NotifySliceImageError(operation);
         return;
       }
-
+      
       if (isSigned || isStretched)
       {
         NotifySliceImageError(operation);
@@ -562,13 +647,13 @@
         return;
       }
     }
-
+    
     if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
     {
       NotifySliceImageError(operation);
       return;
     }
-
+    
     if (!isStretched)
     {
       if (expectedFormat != reader->GetFormat())
@@ -582,10 +667,10 @@
         return;
       }
     }
-
+    
     int32_t stretchLow = 0;
     int32_t stretchHigh = 0;
-
+    
     if (!info.isMember("StretchLow") ||
         !info.isMember("StretchHigh") ||
         info["StretchLow"].type() != Json::intValue ||
@@ -594,10 +679,10 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     stretchLow = info["StretchLow"].asInt();
     stretchHigh = info["StretchHigh"].asInt();
-
+    
     if (stretchLow < -32768 ||
         stretchHigh > 65535 ||
         (stretchLow < 0 && stretchHigh > 32767))
@@ -606,29 +691,29 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     // Decode a grayscale JPEG 8bpp image coming from the Web viewer
     std::auto_ptr<Orthanc::ImageAccessor> image
-      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
-     
+        (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
+
     Orthanc::ImageProcessing::Convert(*image, *reader);
     reader.reset(NULL);
-
+    
     float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-
+    
     if (!LinearAlgebra::IsCloseToZero(scaling))
     {
       float offset = static_cast<float>(stretchLow) / scaling;
       Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
     }
-
+    
     NotifySliceImageSuccess(operation, image);
   }
-
-
+  
+  
   class StringImage :
-    public Orthanc::ImageAccessor,
-    public boost::noncopyable
+      public Orthanc::ImageAccessor,
+      public boost::noncopyable
   {
   private:
     std::string  buffer_;
@@ -643,23 +728,22 @@
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
       }
-
+      
       buffer_.swap(buffer);  // The source buffer is now empty
-
+      
       void* data = (buffer_.empty() ? NULL : &buffer_[0]);
-
+      
       AssignWritable(format, width, height,
                      Orthanc::GetBytesPerPixel(format) * width, data);
     }
   };
-
   
   void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation,
                                                const void* answer,
                                                size_t size)
   {
     Orthanc::GzipCompressor compressor;
-
+    
     std::string raw;
     compressor.Uncompress(raw, answer, size);
     
@@ -676,9 +760,9 @@
       // This is the case of RT-DOSE (uint32_t values)
       
       std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
-                         info.GetHeight(), raw));
-
+          (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
+                           info.GetHeight(), raw));
+      
       // TODO - Only for big endian
       for (unsigned int y = 0; y < image->GetHeight(); y++)
       {
@@ -688,7 +772,7 @@
           *p = le32toh(*p);
         }
       }
-
+      
       NotifySliceImageSuccess(operation, image);
     }
     else if (info.GetBitsAllocated() == 16 &&
@@ -700,30 +784,31 @@
              raw.size() == info.GetWidth() * info.GetHeight() * 2)
     {
       std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
-                         info.GetHeight(), raw));
-
+          (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
+                           info.GetHeight(), raw));
+      
       // TODO - Big endian ?
-
+      
       NotifySliceImageSuccess(operation, image);
     }
     else
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
-        
+
   }
-
-
-  OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback,
+  
+  
+  OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
+                                           ISliceLoaderObserver& callback,
                                            IWebService& orthanc) :
-    webCallback_(new WebCallback(*this)),
+    webCallback_(new WebCallback(broker, *this)),
     userCallback_(callback),
     orthanc_(orthanc),
     state_(State_Initialization)
   {
   }
-
+  
   
   void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
   {
@@ -735,11 +820,11 @@
     {
       state_ = State_LoadingGeometry;
       std::string uri = "/series/" + seriesId + "/instances-tags";
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry());
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry());
     }
   }
-
-
+  
+  
   void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
   {
     if (state_ != State_Initialization)
@@ -749,16 +834,16 @@
     else
     {
       state_ = State_LoadingGeometry;
-
+      
       // Tag "3004-000c" is "Grid Frame Offset Vector", which is
       // mandatory to read RT DOSE, but is too long to be returned by default
       std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
       orthanc_.ScheduleGetRequest
-        (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId));
+          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
-
+  
   void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId,
                                               unsigned int frame)
   {
@@ -771,27 +856,27 @@
       state_ = State_LoadingGeometry;
       std::string uri = "/instances/" + instanceId + "/tags";
       orthanc_.ScheduleGetRequest
-        (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame));
+          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
-
+  
   bool OrthancSlicesLoader::IsGeometryReady() const
   {
     return state_ == State_GeometryReady;
   }
-
-
+  
+  
   size_t OrthancSlicesLoader::GetSliceCount() const
   {
     if (state_ != State_GeometryReady)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.GetSliceCount();
   }
-
+  
   
   const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
   {
@@ -799,11 +884,11 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.GetSlice(index);
   }
   
-
+  
   bool OrthancSlicesLoader::LookupSlice(size_t& index,
                                         const CoordinateSystem3D& plane) const
   {
@@ -811,76 +896,109 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.LookupSlice(index, plane);
   }
   
-
+  
   void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
                                                   size_t index)
   {
-    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+    
+    switch (slice.GetConverter().GetExpectedPixelFormat())
+    {
+    case Orthanc::PixelFormat_RGB24:
+      uri += "/preview";
+      break;
+      
+    case Orthanc::PixelFormat_Grayscale16:
+      uri += "/image-uint16";
+      break;
+      
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      uri += "/image-int16";
+      break;
+      
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    IWebService::Headers headers;
+    headers["Accept"] = "image/png";
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
+  }
+  
+  void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
+                                                  size_t index)
+  {
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
 
     switch (slice.GetConverter().GetExpectedPixelFormat())
     {
-      case Orthanc::PixelFormat_RGB24:
-        uri += "/preview";
-        break;
+    case Orthanc::PixelFormat_RGB24:
+      uri += "/preview";
+      break;
 
-      case Orthanc::PixelFormat_Grayscale16:
-        uri += "/image-uint16";
-        break;
+    case Orthanc::PixelFormat_Grayscale16:
+      uri += "/image-uint16";
+      break;
 
-      case Orthanc::PixelFormat_SignedGrayscale16:
-        uri += "/image-int16";
-        break;
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      uri += "/image-int16";
+      break;
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    orthanc_.ScheduleGetRequest(*webCallback_, uri,
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
+    IWebService::Headers headers;
+    headers["Accept"] = "image/x-portable-arbitrarymap";
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, headers,
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
   }
 
 
+  
   void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
                                                    size_t index,
                                                    SliceImageQuality quality)
   {
     unsigned int value;
-
+    
     switch (quality)
     {
-      case SliceImageQuality_Jpeg50:
-        value = 50;
-        break;
-    
-      case SliceImageQuality_Jpeg90:
-        value = 90;
-        break;
-    
-      case SliceImageQuality_Jpeg95:
-        value = 95;
-        break;
+    case SliceImageQuality_Jpeg50:
+      value = 50;
+      break;
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    case SliceImageQuality_Jpeg90:
+      value = 90;
+      break;
+
+    case SliceImageQuality_Jpeg95:
+      value = 95;
+      break;
+      
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
     // This requires the official Web viewer plugin to be installed!
-    std::string uri = ("/web-viewer/instances/jpeg" + 
-                       boost::lexical_cast<std::string>(value) + 
-                       "-" + slice.GetOrthancInstanceId() + "_" + 
+    std::string uri = ("/web-viewer/instances/jpeg" +
+                       boost::lexical_cast<std::string>(value) +
+                       "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
-      
-    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
                                 Operation::DownloadSliceImage(index, slice, quality));
   }
-
-
-
+  
+  
+  
   void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
                                                    SliceImageQuality quality)
   {
@@ -888,25 +1006,28 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     const Slice& slice = GetSlice(index);
-
+    
     if (slice.HasOrthancDecoding())
     {
-      if (quality == SliceImageQuality_Full)
+      switch (quality)
       {
+      case SliceImageQuality_FullPng:
         ScheduleSliceImagePng(slice, index);
-      }
-      else
-      {
+        break;
+      case SliceImageQuality_FullPam:
+        ScheduleSliceImagePam(slice, index);
+        break;
+      default:
         ScheduleSliceImageJpeg(slice, index, quality);
       }
     }
     else
     {
-      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.ScheduleGetRequest(*webCallback_, uri,
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
                                   Operation::DownloadSliceRawImage(index, slice));
     }
   }
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Jul 17 14:43:42 2018 +0200
@@ -32,24 +32,70 @@
   class OrthancSlicesLoader : public boost::noncopyable
   {
   public:
-    class ICallback : public boost::noncopyable
+    struct SliceImageReadyMessage : public IMessage
+    {
+      unsigned int sliceIndex_;
+      const Slice& slice_;
+      std::auto_ptr<Orthanc::ImageAccessor>& image_;
+      SliceImageQuality effectiveQuality_;
+
+      SliceImageReadyMessage(unsigned int sliceIndex,
+                        const Slice& slice,
+                        std::auto_ptr<Orthanc::ImageAccessor>& image,
+                        SliceImageQuality effectiveQuality)
+        : IMessage(MessageType_SliceImageReady),
+          sliceIndex_(sliceIndex),
+          slice_(slice),
+          image_(image),
+          effectiveQuality_(effectiveQuality)
+      {
+      }
+    };
+
+    struct SliceImageErrorMessage : public IMessage
+    {
+      const Slice& slice_;
+      unsigned int sliceIndex_;
+      SliceImageQuality effectiveQuality_;
+
+      SliceImageErrorMessage(unsigned int sliceIndex,
+                        const Slice& slice,
+                        SliceImageQuality effectiveQuality)
+        : IMessage(MessageType_SliceImageError),
+          slice_(slice),
+          sliceIndex_(sliceIndex),
+          effectiveQuality_(effectiveQuality)
+      {
+      }
+    };
+
+  public:
+    class ISliceLoaderObserver : public IObserver
     {
     public:
-      virtual ~ICallback()
+
+      ISliceLoaderObserver(MessageBroker& broker)
+        : IObserver(broker)
       {
       }
 
-      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
+      virtual ~ISliceLoaderObserver()
+      {
+      }
+
+      virtual void HandleMessage(IObservable& from, const IMessage& message);
 
-      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
+      virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader) = 0;
 
-      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+      virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader) = 0;
+
+      virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
                                          unsigned int sliceIndex,
                                          const Slice& slice,
                                          std::auto_ptr<Orthanc::ImageAccessor>& image,
                                          SliceImageQuality effectiveQuality) = 0;
 
-      virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+      virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                          unsigned int sliceIndex,
                                          const Slice& slice,
                                          SliceImageQuality quality) = 0;
@@ -70,7 +116,8 @@
       Mode_InstanceGeometry,
       Mode_FrameGeometry,
       Mode_LoadImage,
-      Mode_LoadRawImage
+      Mode_LoadRawImage,
+      Mode_LoadDicomFile
     };
 
     class Operation;
@@ -78,7 +125,7 @@
 
     boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
 
-    ICallback&    userCallback_;
+    ISliceLoaderObserver&    userCallback_;
     IWebService&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
@@ -104,6 +151,10 @@
                             const void* answer,
                             size_t size);
 
+    void ParseSliceImagePam(const Operation& operation,
+                            const void* answer,
+                            size_t size);
+
     void ParseSliceImageJpeg(const Operation& operation,
                              const void* answer,
                              size_t size);
@@ -114,7 +165,10 @@
 
     void ScheduleSliceImagePng(const Slice& slice,
                                size_t index);
-    
+
+    void ScheduleSliceImagePam(const Slice& slice,
+                               size_t index);
+
     void ScheduleSliceImageJpeg(const Slice& slice,
                                 size_t index,
                                 SliceImageQuality quality);
@@ -122,7 +176,8 @@
     void SortAndFinalizeSlices();
     
   public:
-    OrthancSlicesLoader(ICallback& callback,
+    OrthancSlicesLoader(MessageBroker& broker,
+                        ISliceLoaderObserver& callback,
                         IWebService& orthanc);
 
     void ScheduleLoadSeries(const std::string& seriesId);
--- a/Framework/Viewport/WidgetViewport.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Viewport/WidgetViewport.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -64,7 +64,7 @@
     }
 
     mouseTracker_.reset(NULL);
-      
+
     centralWidget_.reset(widget);
     centralWidget_->SetViewport(*this);
 
--- a/Framework/Volumes/StructureSetLoader.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -61,14 +61,14 @@
   };
 
 
-  void StructureSetLoader::NotifyError(const std::string& uri,
+  void StructureSetLoader::OnHttpRequestError(const std::string& uri,
                                        Orthanc::IDynamicObject* payload)
   {
     // TODO
   }
 
   
-  void StructureSetLoader::NotifySuccess(const std::string& uri,
+  void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri,
                                          const void* answer,
                                          size_t answerSize,
                                          Orthanc::IDynamicObject* payload)
@@ -88,7 +88,7 @@
         for (std::set<std::string>::const_iterator it = instances.begin();
              it != instances.end(); ++it)
         {
-          orthanc_.SchedulePostRequest(*this, "/tools/lookup", *it,
+          orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it,
                                        new Operation(Operation::Type_LookupSopInstanceUid, *it));
         }
         
@@ -115,7 +115,7 @@
           }
 
           const std::string& instance = lookup[0]["ID"].asString();
-          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags",
+          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(),
                                       new Operation(Operation::Type_LoadReferencedSlice, instance));
         }
         else
@@ -145,7 +145,8 @@
   } 
 
   
-  StructureSetLoader::StructureSetLoader(IWebService& orthanc) :
+  StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) :
+    IWebService::ICallback(broker),
     orthanc_(orthanc)
   {
   }
@@ -160,7 +161,7 @@
     else
     {
       const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050";
-      orthanc_.ScheduleGetRequest(*this, uri, new Operation(Operation::Type_LoadStructureSet, instance));
+      orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance));
     }
   }
 
--- a/Framework/Volumes/StructureSetLoader.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.h	Tue Jul 17 14:43:42 2018 +0200
@@ -34,10 +34,10 @@
   private:
     class Operation;
     
-    virtual void NotifyError(const std::string& uri,
+    virtual void OnHttpRequestError(const std::string& uri,
                              Orthanc::IDynamicObject* payload);
 
-    virtual void NotifySuccess(const std::string& uri,
+    virtual void OnHttpRequestSuccess(const std::string& uri,
                                const void* answer,
                                size_t answerSize,
                                Orthanc::IDynamicObject* payload);
@@ -46,7 +46,7 @@
     std::auto_ptr<DicomStructureSet>  structureSet_;
 
   public:
-    StructureSetLoader(IWebService& orthanc);
+    StructureSetLoader(MessageBroker& broker, IWebService& orthanc);
 
     void ScheduleLoadInstance(const std::string& instance);
 
--- a/Framework/Widgets/EmptyWidget.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Widgets/EmptyWidget.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -26,19 +26,16 @@
 
 namespace OrthancStone
 {
-  namespace Samples
+  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
   {
-    bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
-    {
-      // Note: This call is slow
-      Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
-      return true;
-    }
+    // Note: This call is slow
+    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
+    return true;
+  }
 
-  
-    void EmptyWidget::UpdateContent()
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
+
+  void EmptyWidget::UpdateContent()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 }
--- a/Framework/Widgets/EmptyWidget.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Widgets/EmptyWidget.h	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -25,93 +25,90 @@
 
 namespace OrthancStone
 {
-  namespace Samples
-  {
-    /**
+  /**
      * This is a test widget that simply fills its surface with an
      * uniform color.
      **/
-    class EmptyWidget : public IWidget
-    {
-    private:
-      uint8_t  red_;
-      uint8_t  green_;
-      uint8_t  blue_;
+  class EmptyWidget : public IWidget
+  {
+  private:
+    uint8_t  red_;
+    uint8_t  green_;
+    uint8_t  blue_;
 
-    public:
-      EmptyWidget(uint8_t red,
-                  uint8_t green,
-                  uint8_t blue) :
-        red_(red),
-        green_(green),
-        blue_(blue)
-      {
-      }
+  public:
+    EmptyWidget(uint8_t red,
+                uint8_t green,
+                uint8_t blue) :
+      red_(red),
+      green_(green),
+      blue_(blue)
+    {
+    }
+
+    virtual void SetDefaultView()
+    {
+    }
 
-      virtual void SetDefaultView()
-      {
-      }
-  
-      virtual void SetParent(OrthancStone::IWidget& widget)
-      {
-      }
-    
-      virtual void SetViewport(IViewport& viewport)
-      {
-      }
+    virtual void SetParent(IWidget& widget)
+    {
+    }
+
+    virtual void SetViewport(IViewport& viewport)
+    {
+    }
 
-      virtual void NotifyChange()
-      {
-      }
+    virtual void NotifyChange()
+    {
+    }
 
-      virtual void SetStatusBar(IStatusBar& statusBar)
-      {
-      }
+    virtual void SetStatusBar(IStatusBar& statusBar)
+    {
+    }
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height)
+    {
+    }
 
-      virtual void SetSize(unsigned int width, 
-                           unsigned int height)
-      {
-      }
- 
-      virtual bool Render(Orthanc::ImageAccessor& surface);
+    virtual bool Render(Orthanc::ImageAccessor& surface);
 
-      virtual IMouseTracker* CreateMouseTracker(MouseButton button,
-                                                int x,
-                                                int y,
-                                                KeyboardModifiers modifiers)
-      {
-        return NULL;
-      }
+    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                              int x,
+                                              int y,
+                                              KeyboardModifiers modifiers)
+    {
+      return NULL;
+    }
 
-      virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                   int x,
-                                   int y)
-      {
-      }
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y)
+    {
+    }
 
-      virtual void MouseWheel(MouseWheelDirection direction,
-                              int x,
-                              int y,
-                              KeyboardModifiers modifiers)
-      {
-      }
+    virtual void MouseWheel(MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            KeyboardModifiers modifiers)
+    {
+    }
 
-      virtual void KeyPressed(char key,
-                              KeyboardModifiers modifiers)
-      {
-      }
+    virtual void KeyPressed(char key,
+                            KeyboardModifiers modifiers)
+    {
+    }
 
-      virtual bool HasUpdateContent() const
-      {
-        return false;
-      }
+    virtual bool HasUpdateContent() const
+    {
+      return false;
+    }
 
-      virtual void UpdateContent();
+    virtual void UpdateContent();
 
-      virtual bool HasRenderMouseOver()
-      {
-        return false;
-      }
-    };
-  }
+    virtual bool HasRenderMouseOver()
+    {
+      return false;
+    }
+  };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -55,7 +55,7 @@
         countMissing_++;
       }
     }
-      
+
   public:
     Scene(const CoordinateSystem3D& slice,
           double thickness,
@@ -184,7 +184,7 @@
 #endif
         
         cairo_set_line_width(cr, 2.0 / view.GetZoom());
-        cairo_set_source_rgb(cr, 1, 1, 1); 
+        cairo_set_source_rgb(cr, 1, 1, 1);
         cairo_stroke_preserve(cr);
         cairo_set_source_rgb(cr, 1, 0, 0);
         cairo_fill(cr);
@@ -215,7 +215,7 @@
       {
         double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) -
                     slice_.ProjectAlongNormal(slice_.GetOrigin()));
-      
+
         if (z < 0)
         {
           z = -z;
@@ -249,7 +249,7 @@
       return true;
     }
   }
-    
+
 
   void LayerWidget::GetLayerExtent(Extent2D& extent,
                                    ILayerSource& source) const
@@ -268,7 +268,7 @@
     }
   }
 
-        
+
   Extent2D LayerWidget::GetSceneExtent()
   {
     Extent2D sceneExtent;
@@ -359,7 +359,8 @@
   }
 
   
-  LayerWidget::LayerWidget() :
+  LayerWidget::LayerWidget(MessageBroker& broker) :
+    IObserver(broker),
     started_(false)
   {
     SetBackgroundCleared(true);
@@ -388,14 +389,36 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->Register(*this);
+    layer->RegisterObserver(*this);
 
     ResetChangedLayers();
 
     return index;
   }
 
-  
+  void LayerWidget::ReplaceLayer(size_t index, ILayerSource* layer)  // Takes ownership
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    delete layers_[index];
+    layers_[index] = layer;
+    layersIndex_[layer] = index;
+
+    ResetPendingScene();
+    layer->RegisterObserver(*this);
+
+    InvalidateLayer(index);
+  }
+
+
   const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const
   {
     if (layer >= layers_.size())
@@ -457,8 +480,35 @@
     }
   }
 
+  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    switch (message.GetType()) {
+    case MessageType_GeometryReady:
+      OnGeometryReady(dynamic_cast<ILayerSource&>(from));
+      break;
+    case MessageType_GeometryError:
+      LOG(ERROR) << "Cannot get geometry";
+      break;
+    case MessageType_ContentChanged:
+      OnContentChanged(dynamic_cast<ILayerSource&>(from));
+      break;
+    case MessageType_SliceChanged:
+      OnSliceChanged(dynamic_cast<ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
+      break;
+    case MessageType_LayerReady:
+    {
+      const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
+      OnLayerReady(layerReadyMessage.layer_,
+                   dynamic_cast<ILayerSource&>(from),
+                   layerReadyMessage.slice_,
+                   layerReadyMessage.isError_);
+    }; break;
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
 
-  void LayerWidget::NotifyGeometryReady(const ILayerSource& source)
+  void LayerWidget::OnGeometryReady(const ILayerSource& source)
   {
     size_t i;
     if (LookupLayer(i, source))
@@ -470,13 +520,6 @@
     }
   }
   
-
-  void LayerWidget::NotifyGeometryError(const ILayerSource& source)
-  {
-    LOG(ERROR) << "Cannot get geometry";
-  }
-  
-
   void LayerWidget::InvalidateAllLayers()
   {
     for (size_t i = 0; i < layers_.size(); i++)
@@ -503,7 +546,7 @@
   }
 
 
-  void LayerWidget::NotifyContentChange(const ILayerSource& source)
+  void LayerWidget::OnContentChanged(const ILayerSource& source)
   {
     size_t index;
     if (LookupLayer(index, source))
@@ -513,8 +556,8 @@
   }
   
 
-  void LayerWidget::NotifySliceChange(const ILayerSource& source,
-                                      const Slice& slice)
+  void LayerWidget::OnSliceChanged(const ILayerSource& source,
+                                   const Slice& slice)
   {
     if (slice.ContainsPlane(slice_))
     {
@@ -527,10 +570,10 @@
   }
   
   
-  void LayerWidget::NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                     const ILayerSource& source,
-                                     const CoordinateSystem3D& slice,
-                                     bool isError)
+  void LayerWidget::OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+                                 const ILayerSource& source,
+                                 const CoordinateSystem3D& slice,
+                                 bool isError)
   {
     size_t index;
     if (LookupLayer(index, source))
--- a/Framework/Widgets/LayerWidget.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Tue Jul 17 14:43:42 2018 +0200
@@ -24,6 +24,7 @@
 #include "WorldSceneWidget.h"
 #include "../Layers/ILayerSource.h"
 #include "../Toolbox/Extent2D.h"
+#include "../../Framework/Messages/IObserver.h"
 
 #include <map>
 
@@ -31,7 +32,7 @@
 {
   class LayerWidget :
     public WorldSceneWidget,
-    private ILayerSource::IObserver
+    public IObserver
   {
   private:
     class Scene;
@@ -53,23 +54,26 @@
     void GetLayerExtent(Extent2D& extent,
                         ILayerSource& source) const;
 
-    virtual void NotifyGeometryReady(const ILayerSource& source);
-
-    virtual void NotifyGeometryError(const ILayerSource& source);
+    void OnGeometryReady(const ILayerSource& source);
 
-    virtual void NotifyContentChange(const ILayerSource& source);
+    virtual void OnContentChanged(const ILayerSource& source);
 
-    virtual void NotifySliceChange(const ILayerSource& source,
+    virtual void OnSliceChanged(const ILayerSource& source,
                                    const Slice& slice);
 
-    virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+    virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
                                   const ILayerSource& source,
                                   const CoordinateSystem3D& slice,
                                   bool isError);
 
+
     void ResetChangedLayers();
 
   public:
+    LayerWidget(MessageBroker& broker);
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
     virtual Extent2D GetSceneExtent();
  
   protected:
@@ -93,6 +97,8 @@
 
     size_t AddLayer(ILayerSource* layer);  // Takes ownership
 
+    void ReplaceLayer(size_t layerIndex, ILayerSource* layer); // Takes ownership
+
     size_t GetLayerCount() const
     {
       return layers_.size();
--- a/Framework/Widgets/LayoutWidget.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/Widgets/LayoutWidget.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -82,12 +82,10 @@
     int                     top_;
     unsigned int            width_;
     unsigned int            height_;
-    bool                    hasUpdate_;
 
   public:
     ChildWidget(IWidget* widget) :
-      widget_(widget),
-      hasUpdate_(widget->HasUpdateContent())
+      widget_(widget)
     {
       assert(widget != NULL);
       SetEmpty();
@@ -95,7 +93,7 @@
 
     void UpdateContent()
     {
-      if (hasUpdate_)
+      if (widget_->HasUpdateContent())
       {
         widget_->UpdateContent();
       }
--- a/Framework/dev.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Framework/dev.h	Tue Jul 17 14:43:42 2018 +0200
@@ -43,7 +43,7 @@
   // TODO: Handle errors while loading
   class OrthancVolumeImage : 
     public SlicedVolumeBase,
-    private OrthancSlicesLoader::ICallback
+    private OrthancSlicesLoader::ISliceLoaderObserver
   { 
   private:
     OrthancSlicesLoader           loader_;
@@ -106,7 +106,7 @@
     }
 
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
+    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader)
     {
       if (loader.GetSliceCount() == 0)
       {
@@ -173,13 +173,13 @@
       SlicedVolumeBase::NotifyGeometryReady();
     }
 
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader)
+    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader)
     {
       LOG(ERROR) << "Unable to download a volume image";
       SlicedVolumeBase::NotifyGeometryError();
     }
 
-    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        std::auto_ptr<Orthanc::ImageAccessor>& image,
@@ -205,7 +205,7 @@
       ScheduleSliceDownload();
     }
 
-    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        SliceImageQuality quality)
@@ -215,9 +215,11 @@
     }
 
   public:
-    OrthancVolumeImage(IWebService& orthanc,
+    OrthancVolumeImage(MessageBroker& broker,
+                       IWebService& orthanc,
                        bool computeRange) : 
-      loader_(*this, orthanc),
+      OrthancSlicesLoader::ISliceLoaderObserver(broker),
+      loader_(broker, *this, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)
     {
@@ -576,7 +578,8 @@
 
 
   public:
-    VolumeImageSource(OrthancVolumeImage&  volume) :
+    VolumeImageSource(MessageBroker& broker, OrthancVolumeImage&  volume) :
+      LayerSourceBase(broker),
       volume_(volume)
     {
       volume_.Register(*this);
@@ -814,7 +817,8 @@
     LayerWidget&  otherPlane_;
 
   public:
-    SliceLocationSource(LayerWidget&  otherPlane) :
+    SliceLocationSource(MessageBroker& broker, LayerWidget&  otherPlane) :
+      LayerSourceBase(broker),
       otherPlane_(otherPlane)
     {
       NotifyGeometryReady();
--- a/Platforms/Generic/CMakeLists.txt	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/CMakeLists.txt	Tue Jul 17 14:43:42 2018 +0200
@@ -45,9 +45,13 @@
 ## Build all the sample applications
 #####################################################################
 
-macro(BuildSample Target Sample)
+macro(BuildSample Target Header Sample)
   add_executable(${Target}
     ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainSdl.cpp
+#    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header}
     ${APPLICATIONS_SOURCES}
     )
   set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
@@ -57,13 +61,14 @@
 
 # TODO - Re-enable all these samples!
 
-BuildSample(OrthancStoneEmpty 1)
-BuildSample(OrthancStoneTestPattern 2)
-BuildSample(OrthancStoneSingleFrame 3)
-BuildSample(OrthancStoneSingleVolume 4)
-#BuildSample(OrthancStoneBasicPetCtFusion 5)
-#BuildSample(OrthancStoneSynchronizedSeries 6)
-#BuildSample(OrthancStoneLayoutPetCtFusion 7)
+#BuildSample(OrthancStoneEmpty EmptyApplication.h 1)
+#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2)
+#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3)
+#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4)
+##BuildSample(OrthancStoneBasicPetCtFusion 5)
+##BuildSample(OrthancStoneSynchronizedSeries 6)
+##BuildSample(OrthancStoneLayoutPetCtFusion 7)
+BuildSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8)
 
 
 #####################################################################
@@ -72,6 +77,7 @@
 
 add_executable(UnitTests
   ${GOOGLE_TEST_SOURCES}
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
   ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
   )
 
--- a/Platforms/Generic/OracleWebService.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Tue Jul 17 14:43:42 2018 +0200
@@ -35,8 +35,10 @@
     Orthanc::WebServiceParameters  parameters_;
 
   public:
-    OracleWebService(Oracle& oracle,
+    OracleWebService(MessageBroker& broker,
+                     Oracle& oracle,
                      const Orthanc::WebServiceParameters& parameters) : 
+      IWebService(broker),
       oracle_(oracle),
       parameters_(parameters)
     {
@@ -44,17 +46,29 @@
 
     virtual void ScheduleGetRequest(ICallback& callback,
                                     const std::string& uri,
+                                    const Headers& headers,
                                     Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServiceGetCommand(callback, parameters_, uri, payload));
+      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload));
     }
 
     virtual void SchedulePostRequest(ICallback& callback,
                                      const std::string& uri,
+                                     const Headers& headers,
                                      const std::string& body,
                                      Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServicePostCommand(callback, parameters_, uri, body, payload));
+      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload));
+    }
+
+    void Start()
+    {
+        oracle_.Start();
+    }
+
+    void Stop()
+    {
+        oracle_.Stop();
     }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,58 @@
+/**
+ * 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 "WebServiceCommandBase.h"
+
+#include <Core/HttpClient.h>
+
+namespace OrthancStone
+{
+  WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker,
+                                             IWebService::ICallback& callback,
+                                             const Orthanc::WebServiceParameters& parameters,
+                                             const std::string& uri,
+                                             const IWebService::Headers& headers,
+                                             Orthanc::IDynamicObject* payload /* takes ownership */) :
+    IObservable(broker),
+    callback_(callback),
+    parameters_(parameters),
+    uri_(uri),
+    headers_(headers),
+    payload_(payload)
+  {
+    RegisterObserver(callback);
+  }
+
+
+  void WebServiceCommandBase::Commit()
+  {
+    if (success_)
+    {
+      IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release());
+      EmitMessage(message);
+    }
+    else
+    {
+      IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release());
+      EmitMessage(message);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceCommandBase.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,58 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "IOracleCommand.h"
+
+#include "../../Framework/Toolbox/IWebService.h"
+#include "../../Framework/Messages/IObservable.h"
+
+#include <Core/WebServiceParameters.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class WebServiceCommandBase : public IOracleCommand, IObservable
+  {
+  protected:
+    IWebService::ICallback&                 callback_;
+    Orthanc::WebServiceParameters           parameters_;
+    std::string                             uri_;
+    std::map<std::string, std::string>      headers_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    bool                                    success_;
+    std::string                             answer_;
+
+  public:
+    WebServiceCommandBase(MessageBroker& broker,
+                         IWebService::ICallback& callback,
+                         const Orthanc::WebServiceParameters& parameters,
+                         const std::string& uri,
+                         const std::map<std::string, std::string>& headers,
+                         Orthanc::IDynamicObject* payload /* takes ownership */);
+
+    virtual void Execute() = 0;
+
+    virtual void Commit();
+  };
+}
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -25,14 +25,13 @@
 
 namespace OrthancStone
 {
-  WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback,
+  WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker,
+                                             IWebService::ICallback& callback,
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
+                                             const IWebService::Headers& headers,
                                              Orthanc::IDynamicObject* payload /* takes ownership */) :
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    payload_(payload)
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload)
   {
   }
 
@@ -42,19 +41,13 @@
     Orthanc::HttpClient client(parameters_, uri_);
     client.SetTimeout(60);
     client.SetMethod(Orthanc::HttpMethod_Get);
+
+    for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+
     success_ = client.Apply(answer_);
   }
 
-
-  void WebServiceGetCommand::Commit()
-  {
-    if (success_)
-    {
-      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
-    }
-    else
-    {
-      callback_.NotifyError(uri_, payload_.release());
-    }
-  }
 }
--- a/Platforms/Generic/WebServiceGetCommand.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Tue Jul 17 14:43:42 2018 +0200
@@ -21,34 +21,20 @@
 
 #pragma once
 
-#include "IOracleCommand.h"
-
-#include "../../Framework/Toolbox/IWebService.h"
-
-#include <Core/WebServiceParameters.h>
-
-#include <memory>
+#include "WebServiceCommandBase.h"
 
 namespace OrthancStone
 {
-  class WebServiceGetCommand : public IOracleCommand
+  class WebServiceGetCommand : public WebServiceCommandBase
   {
-  private:
-    IWebService::ICallback&                 callback_;
-    Orthanc::WebServiceParameters           parameters_;
-    std::string                             uri_;
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-    bool                                    success_;
-    std::string                             answer_;
-
   public:
-    WebServiceGetCommand(IWebService::ICallback& callback,
+    WebServiceGetCommand(MessageBroker& broker,
+                         IWebService::ICallback& callback,
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
+                         const IWebService::Headers& headers,
                          Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- a/Platforms/Generic/WebServicePostCommand.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -25,16 +25,15 @@
 
 namespace OrthancStone
 {
-  WebServicePostCommand::WebServicePostCommand(IWebService::ICallback& callback,
+  WebServicePostCommand::WebServicePostCommand(MessageBroker& broker,
+                                               IWebService::ICallback& callback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
+                                               const IWebService::Headers& headers,
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */) :
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    body_(body),
-    payload_(payload)
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload),
+    body_(body)
   {
   }
 
@@ -44,18 +43,13 @@
     client.SetTimeout(60);
     client.SetMethod(Orthanc::HttpMethod_Post);
     client.GetBody().swap(body_);
+
+    for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+
     success_ = client.Apply(answer_);
   }
 
-  void WebServicePostCommand::Commit()
-  {
-    if (success_)
-    {
-      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
-    }
-    else
-    {
-      callback_.NotifyError(uri_, payload_.release());
-    }
-  }
 }
--- a/Platforms/Generic/WebServicePostCommand.h	Sat Jul 14 11:20:07 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Tue Jul 17 14:43:42 2018 +0200
@@ -21,36 +21,24 @@
 
 #pragma once
 
-#include "IOracleCommand.h"
-
-#include "../../Framework/Toolbox/IWebService.h"
-
-#include <Core/WebServiceParameters.h>
-
-#include <memory>
+#include "WebServiceCommandBase.h"
 
 namespace OrthancStone
 {
-  class WebServicePostCommand : public IOracleCommand
+  class WebServicePostCommand : public WebServiceCommandBase
   {
-  private:
-    IWebService::ICallback&                 callback_;
-    Orthanc::WebServiceParameters           parameters_;
-    std::string                             uri_;
+  protected:
     std::string                             body_;
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-    bool                                    success_;
-    std::string                             answer_;
 
   public:
-    WebServicePostCommand(IWebService::ICallback& callback,
+    WebServicePostCommand(MessageBroker& broker,
+                          IWebService::ICallback& callback,
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
+                          const IWebService::Headers& headers,
                           const std::string& body,
                           Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/CMakeLists.txt	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,86 @@
+# Usage (Linux):
+# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake ..
+
+cmake_minimum_required(VERSION 2.8.3)
+
+
+#####################################################################
+## Configuration of the Emscripten compiler for WebAssembly target
+#####################################################################
+
+set(WASM_FLAGS "-s WASM=1")
+set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js  -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+
+# Handling of memory
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")  # Resize
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")  # 512MB
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000")  # 512MB + resize
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824")  # 1GB + resize
+
+# To debug exceptions
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2")
+
+
+#####################################################################
+## Build a static library containing the Orthanc Stone framework
+#####################################################################
+
+include(../../Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ORTHANC_SANDBOXED ON)
+SET(ENABLE_SDL OFF)
+
+include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
+
+add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
+
+
+
+# Regenerate a dummy "WasmWebService.c" file each time the "WasmWebService.js" file
+# is modified, so as to force a new execution of the linking
+add_custom_command(
+    OUTPUT "${AUTOGENERATED_DIR}/WasmWebService.c"
+    COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmWebService.c" ""
+    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.js")
+
+add_custom_command(
+    OUTPUT "${AUTOGENERATED_DIR}/default-library.c"
+    COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/default-library.c" ""
+    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js")
+
+
+#####################################################################
+## Build all the sample applications
+#####################################################################
+
+include_directories(${ORTHANC_STONE_ROOT})
+
+
+macro(BuildSample Target Header Sample)
+  add_executable(${Target}
+    ${STONE_WASM_SOURCES}
+
+    ${AUTOGENERATED_DIR}/WasmWebService.c
+    ${AUTOGENERATED_DIR}/default-library.c
+
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp
+#    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header}
+    )
+  set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
+  target_link_libraries(${Target} OrthancStone)
+endmacro()
+
+#BuildSample(OrthancStoneEmpty EmptyApplication.h 1)
+#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2)
+#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3)
+#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4)
+#BuildSample(OrthancStoneBasicPetCtFusion 5)
+#BuildSample(OrthancStoneSynchronizedSeries 6)
+#BuildSample(OrthancStoneLayoutPetCtFusion 7)
+BuildSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/Defaults.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,259 @@
+#include "Defaults.h"
+
+#include "WasmWebService.h"
+#include <Framework/dev.h>
+#include "Framework/Widgets/TestCairoWidget.h"
+#include <Framework/Viewport/WidgetViewport.h>
+#include <Framework/Widgets/LayerWidget.h>
+#include <algorithm>
+#include "Applications/Wasm/StartupParametersBuilder.h"
+
+static unsigned int width_ = 0;
+static unsigned int height_ = 0;
+
+/**********************************/
+
+static std::unique_ptr<OrthancStone::IBasicApplication> application;
+static std::unique_ptr<OrthancStone::BasicApplicationContext> context;
+static OrthancStone::StartupParametersBuilder startupParametersBuilder;
+static OrthancStone::MessageBroker broker;
+
+static OrthancStone::ChangeObserver changeObserver_;
+static OrthancStone::StatusBar statusBar_;
+
+static std::list<std::shared_ptr<OrthancStone::WidgetViewport>> viewports_;
+
+std::shared_ptr<OrthancStone::WidgetViewport> FindViewportSharedPtr(ViewportHandle viewport) {
+  for (const auto& v : viewports_) {
+    if (v.get() == viewport) {
+      return v;
+    }
+  }
+  assert(false);
+  return std::shared_ptr<OrthancStone::WidgetViewport>();
+}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  using namespace OrthancStone;
+
+  // when WASM needs a C++ viewport
+  ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() {
+    
+    std::shared_ptr<OrthancStone::WidgetViewport> viewport(new OrthancStone::WidgetViewport);
+    printf("viewport %x\n", viewport.get());
+
+    viewports_.push_back(viewport);
+
+    printf("There are now %d viewports in C++\n", viewports_.size());
+
+    viewport->SetStatusBar(statusBar_);
+    viewport->Register(changeObserver_);
+
+    return viewport.get();
+  }
+
+  // when WASM does not need a viewport anymore, it should release it 
+  void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) {
+    viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;});
+
+    printf("There are now %d viewports in C++\n", viewports_.size());
+  }
+
+  void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) {
+
+    printf("CreateWasmApplication\n");
+
+    application.reset(CreateUserApplication(broker));
+    WasmWebService::SetBroker(broker);
+
+    startupParametersBuilder.Clear();
+  }
+
+  void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc,
+                                                  const char* value) {
+    startupParametersBuilder.SetStartupParameter(keyc, value);
+  }
+
+  void EMSCRIPTEN_KEEPALIVE StartWasmApplication() {
+
+    printf("StartWasmApplication\n");
+
+    // recreate a command line from uri arguments and parse it
+    boost::program_options::variables_map parameters;
+    boost::program_options::options_description options;
+    application->DeclareStartupOptions(options);
+    startupParametersBuilder.GetStartupParameters(parameters, options);
+
+    context.reset(new OrthancStone::BasicApplicationContext(OrthancStone::WasmWebService::GetInstance()));
+    application->Initialize(context.get(), statusBar_, parameters);
+    application->InitializeWasm();
+
+//    viewport->SetSize(width_, height_);
+    printf("StartWasmApplication - completed\n");
+  }
+  
+  void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent()
+  {
+    for (auto viewport : viewports_) {
+      // TODO Only launch the JavaScript timer if "HasUpdateContent()"
+      if (viewport->HasUpdateContent())
+      {
+        viewport->UpdateContent();
+      }
+
+    }
+
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height)
+  {
+    width_ = width;
+    height_ = height;
+    
+    viewport->SetSize(width, height);
+  }
+
+  int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport,
+                                          unsigned int width,
+                                          unsigned int height,
+                                          uint8_t* data)
+  {
+    changeObserver_.Reset();
+
+    //printf("ViewportRender called %dx%d\n", width, height);
+    if (width == 0 ||
+        height == 0)
+    {
+      return 1;
+    }
+
+    Orthanc::ImageAccessor surface;
+    surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data);
+
+    viewport->Render(surface);
+
+    // Convert from BGRA32 memory layout (only color mode supported by
+    // Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 (as
+    // expected by HTML5 canvas). This simply amounts to swapping the
+    // B and R channels.
+    uint8_t* p = data;
+    for (unsigned int y = 0; y < height; y++) {
+      for (unsigned int x = 0; x < width; x++) {
+        uint8_t tmp = p[0];
+        p[0] = p[2];
+        p[2] = tmp;
+        
+        p += 4;
+      }
+    }
+
+    return 1;
+  }
+
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport,
+                                              unsigned int rawButton,
+                                              int x,
+                                              int y,
+                                              unsigned int rawModifiers)
+  {
+    OrthancStone::MouseButton button;
+    switch (rawButton)
+    {
+      case 0:
+        button = OrthancStone::MouseButton_Left;
+        break;
+
+      case 1:
+        button = OrthancStone::MouseButton_Middle;
+        break;
+
+      case 2:
+        button = OrthancStone::MouseButton_Right;
+        break;
+
+      default:
+        return;  // Unknown button
+    }
+
+    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */);
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport,
+                                               int deltaY,
+                                               int x,
+                                               int y,
+                                               int isControl)
+  {
+    if (deltaY != 0)
+    {
+      OrthancStone::MouseWheelDirection direction = (deltaY < 0 ?
+                                                     OrthancStone::MouseWheelDirection_Up :
+                                                     OrthancStone::MouseWheelDirection_Down);
+      OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None;
+
+      if (isControl != 0)
+      {
+        modifiers = OrthancStone::KeyboardModifiers_Control;
+      }
+
+      viewport->MouseWheel(direction, x, y, modifiers);
+    }
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport,
+                                              int x,
+                                              int y)
+  {
+    viewport->MouseMove(x, y);
+  }
+  
+  void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport,
+                                               const char* key, 
+                                               bool isShiftPressed, 
+                                               bool isControlPressed,
+                                               bool isAltPressed)
+                                               
+  {
+    OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None;
+    if (isShiftPressed) {
+      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Shift);
+    }
+    if (isControlPressed) {
+      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Control);
+    }
+    if (isAltPressed) {
+      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Alt);
+    }
+    printf("key pressed : %c\n", key[0]);
+    viewport->KeyPressed(key[0], modifiers);
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport)
+  {
+    viewport->MouseUp();
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport)
+  {
+    viewport->MouseEnter();
+  }
+  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport)
+  {
+    viewport->MouseLeave();
+  }
+
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/Defaults.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <emscripten/emscripten.h>
+
+#include <Framework/dev.h>
+#include <Framework/Viewport/WidgetViewport.h>
+#include <Framework/Widgets/LayerWidget.h>
+#include <Framework/Widgets/LayoutWidget.h>
+#include <Applications/IBasicApplication.h>
+
+typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+  // JS methods accessible from C++
+  extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle);
+  
+  // C++ methods accessible from JS
+  extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle);
+  extern void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, const char* value);
+  
+
+#ifdef __cplusplus
+}
+#endif
+
+extern OrthancStone::IBasicApplication* CreateUserApplication(OrthancStone::MessageBroker& broker);
+
+namespace OrthancStone {
+
+  // default Ovserver to trigger Viewport redraw when something changes in the Viewport
+  class ChangeObserver :
+    public OrthancStone::IViewport::IObserver
+  {
+  private:
+    // Flag to avoid flooding JavaScript with redundant Redraw requests
+    bool isScheduled_; 
+
+  public:
+    ChangeObserver() :
+      isScheduled_(false)
+    {
+    }
+
+    void Reset()
+    {
+      isScheduled_ = false;
+    }
+
+    virtual void NotifyChange(const OrthancStone::IViewport &viewport)
+    {
+      if (!isScheduled_)
+      {
+        ScheduleWebViewportRedrawFromCpp((ViewportHandle)&viewport);  // loosing constness when transmitted to Web
+        isScheduled_ = true;
+      }
+    }
+  };
+
+  // default status bar to log messages on the console/stdout
+  class StatusBar : public OrthancStone::IStatusBar
+  {
+  public:
+    virtual void ClearMessage()
+    {
+    }
+
+    virtual void SetMessage(const std::string& message)
+    {
+      printf("%s\n", message.c_str());
+    }
+  };
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmViewport.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,13 @@
+#include "WasmViewport.h"
+
+#include <vector>
+#include <memory>
+
+std::vector<std::shared_ptr<OrthancStone::WidgetViewport>> wasmViewports;
+
+void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget) {
+    std::shared_ptr<OrthancStone::WidgetViewport> viewport(CreateWasmViewportFromCpp(htmlCanvasId));
+    viewport->SetCentralWidget(centralWidget);
+
+    wasmViewports.push_back(viewport);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmViewport.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <Framework/Viewport/WidgetViewport.h>
+
+#include <emscripten/emscripten.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  // JS methods accessible from C++
+  extern OrthancStone::WidgetViewport* CreateWasmViewportFromCpp(const char* htmlCanvasId);
+
+#ifdef __cplusplus
+}
+#endif
+
+extern void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmWebService.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,122 @@
+#include "WasmWebService.h"
+#include "json/value.h"
+#include "json/writer.h"
+#include <emscripten/emscripten.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  extern void WasmWebService_ScheduleGetRequest(void* callback,
+                                                const char* uri,
+                                                const char* headersInJsonString,
+                                                void* payload);
+  
+  extern void WasmWebService_SchedulePostRequest(void* callback,
+                                                 const char* uri,
+                                                 const char* headersInJsonString,
+                                                 const void* body,
+                                                 size_t bodySize,
+                                                 void* payload);
+
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback,
+                                                       const char* uri,
+                                                       void* payload)
+  {
+    if (callback == NULL)
+    {
+      throw;
+    }
+    else
+    {
+      reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)->
+        OnHttpRequestError(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload));
+    }
+  }
+
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback,
+                                                         const char* uri,
+                                                         const void* body,
+                                                         size_t bodySize,
+                                                         void* payload)
+  {
+    if (callback == NULL)
+    {
+      throw;
+    }
+    else
+    {
+      reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)->
+        OnHttpRequestSuccess(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); 
+   }
+  }
+
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_SetBaseUri(const char* baseUri)
+  {
+    OrthancStone::WasmWebService::GetInstance().SetBaseUri(baseUri);
+  }
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+namespace OrthancStone
+{
+  MessageBroker* WasmWebService::broker_ = NULL;
+
+  void WasmWebService::SetBaseUri(const std::string baseUri)
+  {
+    // Make sure the base url ends with "/"
+    if (baseUri.empty() ||
+        baseUri[baseUri.size() - 1] != '/')
+    {
+      baseUri_ = baseUri + "/";
+    }
+    else
+    {
+      baseUri_ = baseUri;
+    }
+  }
+
+  void ToJsonString(std::string& output, const IWebService::Headers& headers)
+  {
+    Json::Value jsonHeaders;
+    for (IWebService::Headers::const_iterator it = headers.begin(); it != headers.end(); it++ )
+    {
+      jsonHeaders[it->first] = it->second;
+    }
+
+    Json::StreamWriterBuilder builder;
+    std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+    std::ostringstream outputStr;
+
+    writer->write(jsonHeaders, &outputStr);
+    output = outputStr.str();
+  }
+
+  void WasmWebService::ScheduleGetRequest(ICallback& callback,
+                                          const std::string& relativeUri,
+                                          const Headers& headers,
+                                          Orthanc::IDynamicObject* payload)
+  {
+    std::string uri = baseUri_ + relativeUri;
+    std::string headersInJsonString;
+    ToJsonString(headersInJsonString, headers);
+    WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), headersInJsonString.c_str(), payload);
+  }
+
+  void WasmWebService::SchedulePostRequest(ICallback& callback,
+                                           const std::string& relativeUri,
+                                           const Headers& headers,
+                                           const std::string& body,
+                                           Orthanc::IDynamicObject* payload)
+  {
+    std::string uri = baseUri_ + relativeUri;
+    std::string headersInJsonString;
+    ToJsonString(headersInJsonString, headers);
+    WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(),
+                                       body.c_str(), body.size(), payload);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmWebService.h	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <Framework/Toolbox/IWebService.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class WasmWebService : public IWebService
+  {
+  private:
+    std::string  baseUri_;
+    static MessageBroker* broker_;
+
+    // Private constructor => Singleton design pattern
+    WasmWebService(MessageBroker& broker) :
+      IWebService(broker),
+      baseUri_("../../")   // note: this is configurable from the JS code by calling WasmWebService_SetBaseUri
+    {
+    }
+
+  public:
+    static WasmWebService& GetInstance()
+    {
+      if (broker_ == NULL)
+      {
+        printf("WasmWebService::GetInstance(): broker not initialized\n");
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      static WasmWebService instance(*broker_);
+      return instance;
+    }
+
+    static void SetBroker(MessageBroker& broker)
+    {
+      broker_ = &broker;
+    }
+
+    void SetBaseUri(const std::string baseUri);
+
+    virtual void ScheduleGetRequest(ICallback& callback,
+                                    const std::string& uri,
+                                    const Headers& headers,
+                                    Orthanc::IDynamicObject* payload);
+
+    virtual void SchedulePostRequest(ICallback& callback,
+                                     const std::string& uri,
+                                     const Headers& headers,
+                                     const std::string& body,
+                                     Orthanc::IDynamicObject* payload);
+
+    virtual void Start()
+    {
+    }
+    
+    virtual void Stop()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmWebService.js	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,59 @@
+mergeInto(LibraryManager.library, {
+  WasmWebService_ScheduleGetRequest: function(callback, url, headersInJsonString, payload) {
+    // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data
+    // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
+    var xhr = new XMLHttpRequest();
+    var url_ = UTF8ToString(url);
+    var headersInJsonString_ = UTF8ToString(headersInJsonString);
+
+    xhr.open('GET', url_, true);
+    xhr.responseType = 'arraybuffer';
+    var headers = JSON.parse(headersInJsonString_);
+    for (var key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
+    // console.log(xhr); 
+    xhr.onreadystatechange = function() {
+      if (this.readyState == XMLHttpRequest.DONE) {
+        if (xhr.status === 200) {
+          // TODO - Is "new Uint8Array()" necessary? This copies the
+          // answer to the WebAssembly stack, hence necessitating
+          // increasing the TOTAL_STACK parameter of Emscripten
+          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
+                                       this.response.byteLength, payload);
+        } else {
+          WasmWebService_NotifyError(callback, url_, payload);
+        }
+      }
+    }
+    
+    xhr.send();
+  },
+
+  WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) {
+    var xhr = new XMLHttpRequest();
+    var url_ = UTF8ToString(url);
+    var headersInJsonString_ = UTF8ToString(headersInJsonString);
+    xhr.open('POST', url_, true);
+    xhr.responseType = 'arraybuffer';
+    xhr.setRequestHeader('Content-type', 'application/octet-stream');
+
+    var headers = JSON.parse(headersInJsonString_);
+    for (var key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
+    
+    xhr.onreadystatechange = function() {
+      if (this.readyState == XMLHttpRequest.DONE) {
+        if (xhr.status === 200) {
+          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
+                                       this.response.byteLength, payload);
+        } else {
+          WasmWebService_NotifyError(callback, url_, payload);
+        }
+      }
+    }
+
+    xhr.send(new Uint8ClampedArray(HEAPU8.buffer, body, bodySize));
+  }
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/build-wasm.sh	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+currentDir=$(pwd)
+wasmRootDir=$(pwd)
+
+mkdir -p $wasmRootDir/build
+cd $wasmRootDir/build
+
+source ~/Downloads/emsdk/emsdk_env.sh
+cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON ..
+make -j 5
+
+echo "-- building the web application -- "
+cd $currentDir
+./build-web.sh
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/build-web.sh	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# this script currently assumes that the wasm code has been built on its side and is availabie in Wasm/build/
+
+currentDir=$(pwd)
+wasmRootDir=$(pwd)
+samplesRootDir=$(pwd)/../../Applications/Samples/
+
+outputDir=$wasmRootDir/build-web/
+mkdir -p $outputDir
+
+cp $samplesRootDir/Web/index.html $outputDir
+cp $samplesRootDir/Web/samples-styles.css $outputDir
+
+cp $samplesRootDir/Web/simple-viewer.html $outputDir
+tsc --allowJs --project $samplesRootDir/Web/tsconfig-simple-viewer.json
+cp $currentDir/build/OrthancStoneSimpleViewer.js  $outputDir
+cp $currentDir/build/OrthancStoneSimpleViewer.wasm  $outputDir
+
+
+# cat ../wasm/build/wasm-app.js $currentDir/../../../orthanc-stone/Platforms/WebAssembly/defaults.js > $outputDir/app.js
+
+cd $currentDir
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/default-library.js	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,11 @@
+// this file contains the JS method you want to expose to C++ code
+
+mergeInto(LibraryManager.library, {
+  ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) {
+    ScheduleWebViewportRedraw(cppViewportHandle);
+  },
+  CreateWasmViewportFromCpp: function(htmlCanvasId) {
+    return CreateWasmViewport(htmlCanvasId);
+  }
+});
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/stone-framework-loader.ts	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,96 @@
+module Stone {
+    /**
+     * This file contains primitives to interface with WebAssembly and
+     * with the Stone framework.
+     **/
+    
+    export declare type InitializationCallback = () => void;
+    
+    export declare var StoneFrameworkModule : any;
+    
+    //const ASSETS_FOLDER : string = "assets/lib";
+    //const WASM_FILENAME : string = "orthanc-framework";
+    
+    
+    export class Framework
+    {
+      private static singleton_ : Framework = null;
+      private static wasmModuleName_ : string = null;
+
+      public static Configure(wasmModuleName: string) {
+        this.wasmModuleName_ = wasmModuleName;
+      }
+
+      private constructor(verbose : boolean) 
+      {
+        //this.ccall('Initialize', null, [ 'number' ], [ verbose ]);
+      }
+    
+      
+      public ccall(name: string,
+                   returnType: string,
+                   argTypes: Array<string>,
+                   argValues: Array<any>) : any
+      {
+        return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues);
+      }
+    
+      
+      public cwrap(name: string,
+                   returnType: string,
+                   argTypes: Array<string>) : any
+      {
+        return StoneFrameworkModule.cwrap(name, returnType, argTypes);
+      }
+    
+      
+      public static GetInstance() : Framework
+      {
+        if (Framework.singleton_ == null) {
+          throw new Error('The WebAssembly module is not loaded yet');
+        } else {
+          return Framework.singleton_;
+        }
+      }
+      
+    
+      public static Initialize(verbose: boolean,
+                               callback: InitializationCallback)
+      {
+        console.log('Initializing WebAssembly Module');
+    
+        (<any> window).StoneFrameworkModule = {
+          preRun: [ 
+            function() {
+              console.log('Loading the Stone Framework using WebAssembly');
+            }
+          ],
+          postRun: [ 
+            function()  {
+              // This function is called by ".js" wrapper once the ".wasm"
+              // WebAssembly module has been loaded and compiled by the
+              // browser
+              console.log('WebAssembly is ready');
+              Framework.singleton_ = new Framework(verbose);
+              callback();
+            }
+          ],
+          print: function(text : string) {
+            console.log(text);
+          },
+          printErr: function(text : string) {
+            console.error(text);
+          },
+          totalDependencies: 0
+        };
+    
+        // Dynamic loading of the JavaScript wrapper around WebAssembly
+        var script = document.createElement('script');
+        script.type = 'application/javascript';
+        //script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js';
+        script.src = this.wasmModuleName_ + ".js";//  "OrthancStoneSimpleViewer.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js';
+        script.async = true;
+        document.head.appendChild(script);
+      }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/tsconfig-stone.json	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,7 @@
+{
+    "include" : [
+        "../../../Platforms/Wasm/stone-framework-loader.ts",
+        "../../../Platforms/Wasm/wasm-application.ts",
+        "../../../Platforms/Wasm/wasm-viewport.ts"
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/wasm-application.ts	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,122 @@
+///<reference path='stone-framework-loader.ts'/>
+///<reference path='wasm-viewport.ts'/>
+
+if (!('WebAssembly' in window)) {
+  alert('Sorry, your browser does not support WebAssembly :(');
+}
+
+declare var StoneFrameworkModule : Stone.Framework;
+
+// global functions
+var WasmWebService_NotifyError: Function = null;
+var WasmWebService_NotifySuccess: Function = null;
+var WasmWebService_SetBaseUri: Function = null;
+var NotifyUpdateContent: Function = null;
+var SetStartupParameter: Function = null;
+var CreateWasmApplication: Function = null;
+var CreateCppViewport: Function = null;
+var ReleaseCppViewport: Function = null;
+var StartWasmApplication: Function = null;
+
+
+function UpdateContentThread() {
+  if (NotifyUpdateContent != null) {
+    NotifyUpdateContent();
+  }
+
+  setTimeout(UpdateContentThread, 100);  // Update the viewport content every 100ms if need be
+}
+
+
+function GetUriParameters() {
+  var parameters = window.location.search.substr(1);
+
+  if (parameters != null &&
+    parameters != '') {
+    var result = {};
+    var tokens = parameters.split('&');
+
+    for (var i = 0; i < tokens.length; i++) {
+      var tmp = tokens[i].split('=');
+      if (tmp.length == 2) {
+        result[tmp[0]] = decodeURIComponent(tmp[1]);
+      }
+    }
+
+    return result;
+  }
+  else {
+    return {};
+  }
+}
+
+module Stone {
+
+  export class WasmApplication {
+
+    private viewport_: WasmViewport;
+    private canvasId_: string;
+
+    private pimpl_: any; // Private pointer to the underlying WebAssembly C++ object
+
+    public constructor(canvasId: string) {
+      this.canvasId_ = canvasId;
+      //this.module_ = module;
+    }
+  }
+}
+
+
+function _InitializeWasmApplication(canvasId: string, orthancBaseUrl: string): void {
+
+  /************************************** */
+  CreateWasmApplication();
+  WasmWebService_SetBaseUri(orthancBaseUrl);
+
+
+  // parse uri and transmit the parameters to the app before initializing it
+  var parameters = GetUriParameters();
+
+  for (var key in parameters) {
+    if (parameters.hasOwnProperty(key)) {
+      SetStartupParameter(key, parameters[key]);
+    }
+  }
+
+  StartWasmApplication();
+  /************************************** */
+
+  UpdateContentThread();
+}
+
+function InitializeWasmApplication(wasmModuleName: string, orthancBaseUrl: string) {
+  
+  Stone.Framework.Configure(wasmModuleName);
+
+  // Wait for the Orthanc Framework to be initialized (this initializes
+  // the WebAssembly environment) and then, create and initialize the Wasm application
+  Stone.Framework.Initialize(true, function () {
+
+    console.log("Connecting C++ methods to JS methods");
+    
+    SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']);
+    CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']);
+    CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []);
+    ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']);
+    StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['number']);
+
+    WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']);
+    WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']);
+    WasmWebService_SetBaseUri = StoneFrameworkModule.cwrap('WasmWebService_SetBaseUri', null, ['string']);
+    NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []);
+
+    console.log("Connecting C++ methods to JS methods - done - 2");
+
+    // Prevent scrolling
+    document.body.addEventListener('touchmove', function (event) {
+      event.preventDefault();
+    }, false);
+
+    _InitializeWasmApplication("canvas", orthancBaseUrl);
+  });
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/wasm-viewport.ts	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,272 @@
+var isPendingRedraw = false;
+
+function ScheduleWebViewportRedraw(cppViewportHandle: any) : void
+{
+  if (!isPendingRedraw) {
+    isPendingRedraw = true;
+    console.log('Scheduling a refresh of the viewport, as its content changed');
+    window.requestAnimationFrame(function() {
+      isPendingRedraw = false;
+      Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw();
+    });
+  }
+}
+
+declare function UTF8ToString(any): string;
+
+function CreateWasmViewport(htmlCanvasId: string) : any {
+  var cppViewportHandle = CreateCppViewport();
+  var canvasId = UTF8ToString(htmlCanvasId);
+  var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle);  // viewports are stored in a static map in WasmViewport -> won't be deleted
+  webViewport.Initialize();
+
+  return cppViewportHandle;
+}
+
+module Stone {
+  
+//  export declare type InitializationCallback = () => void;
+  
+//  export declare var StoneFrameworkModule : any;
+  
+  //const ASSETS_FOLDER : string = "assets/lib";
+  //const WASM_FILENAME : string = "orthanc-framework";
+
+  export class WasmViewport {
+
+    private static cppWebViewportsMaps_ : Map<number, WasmViewport> = new Map<number, WasmViewport>();
+
+    private module_ : any;
+    private canvasId_ : string;
+    private htmlCanvas_ : HTMLCanvasElement;
+    private context_ : CanvasRenderingContext2D;
+    private imageData_ : any = null;
+    private renderingBuffer_ : any = null;
+    private touchZoom_ : any = false;
+    private touchTranslation_ : any = false;
+
+    private ViewportSetSize : Function;
+    private ViewportRender : Function;
+    private ViewportMouseDown : Function;
+    private ViewportMouseMove : Function;
+    private ViewportMouseUp : Function;
+    private ViewportMouseEnter : Function;
+    private ViewportMouseLeave : Function;
+    private ViewportMouseWheel : Function;
+    private ViewportKeyPressed : Function;
+
+    private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object
+
+    public constructor(module: any, canvasId: string, cppViewport: any) {
+      
+      this.pimpl_ = cppViewport;
+      WasmViewport.cppWebViewportsMaps_[this.pimpl_] = this;
+
+      this.module_ = module;
+      this.canvasId_ = canvasId;
+      this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement;
+      if (this.htmlCanvas_ == null) {
+        console.log("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'");
+      }
+      this.context_ = this.htmlCanvas_.getContext('2d');
+
+      this.ViewportSetSize = this.module_.cwrap('ViewportSetSize', null, [ 'number', 'number', 'number' ]);
+      this.ViewportRender = this.module_.cwrap('ViewportRender', null, [ 'number', 'number', 'number', 'number' ]);
+      this.ViewportMouseDown = this.module_.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportMouseMove = this.module_.cwrap('ViewportMouseMove', null, [ 'number', 'number', 'number' ]);
+      this.ViewportMouseUp = this.module_.cwrap('ViewportMouseUp', null, [ 'number' ]);
+      this.ViewportMouseEnter = this.module_.cwrap('ViewportMouseEnter', null, [ 'number' ]);
+      this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]);
+      this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'string', 'number', 'number' ]);
+    }
+
+    public GetCppViewport() : number {
+      return this.pimpl_;
+    }
+
+    public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport {
+      if (WasmViewport.cppWebViewportsMaps_[cppViewportHandle] !== undefined) {
+        return WasmViewport.cppWebViewportsMaps_[cppViewportHandle];
+      }
+      console.log("WasmViewport not found !");
+      return undefined;
+    }
+
+    public Redraw() {
+      if (this.imageData_ === null ||
+          this.renderingBuffer_ === null ||
+          this.ViewportRender(this.pimpl_,
+                         this.imageData_.width,
+                         this.imageData_.height,
+                         this.renderingBuffer_) == 0) {
+        console.log('The rendering has failed');
+      } else {
+        // Create an accessor to the rendering buffer (i.e. create a
+        // "window" above the heap of the WASM module), then copy it to
+        // the ImageData object
+        this.imageData_.data.set(new Uint8ClampedArray(
+          this.module_.buffer,
+          this.renderingBuffer_,
+          this.imageData_.width * this.imageData_.height * 4));
+        
+        this.context_.putImageData(this.imageData_, 0, 0);
+      }
+    }
+  
+    public Resize() {
+      if (this.imageData_ != null &&
+          (this.imageData_.width != window.innerWidth ||
+           this.imageData_.height != window.innerHeight)) {
+        this.imageData_ = null;
+      }
+      
+      // width/height can be defined in percent of window width/height through html attributes like data-width-ratio="50" and data-height-ratio="20"
+      var widthRatio = Number(this.htmlCanvas_.dataset["widthRatio"]) || 100;
+      var heightRatio = Number(this.htmlCanvas_.dataset["heightRatio"]) || 100;
+
+      this.htmlCanvas_.width = window.innerWidth * (widthRatio / 100);  
+      this.htmlCanvas_.height = window.innerHeight * (heightRatio / 100);
+
+      console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height);
+
+      if (this.imageData_ === null) {
+        this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height);
+        this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height);
+  
+        if (this.renderingBuffer_ != null) {
+          this.module_._free(this.renderingBuffer_);
+        }
+        
+        this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4);
+      }
+      
+      this.Redraw();
+    }
+
+    public Initialize() {
+      
+      // Force the rendering of the viewport for the first time
+      this.Resize();
+    
+      var that : WasmViewport = this;
+      // Register an event listener to call the Resize() function 
+      // each time the window is resized.
+      window.addEventListener('resize', function(event) {
+        that.Resize();
+      }, false);
+  
+      this.htmlCanvas_.addEventListener('contextmenu', function(event) {
+        // Prevent right click on the canvas
+        event.preventDefault();
+      }, false);
+      
+      this.htmlCanvas_.addEventListener('mouseleave', function(event) {
+        that.ViewportMouseLeave(that.pimpl_);
+      });
+      
+      this.htmlCanvas_.addEventListener('mouseenter', function(event) {
+        that.ViewportMouseEnter(that.pimpl_);
+      });
+    
+      this.htmlCanvas_.addEventListener('mousedown', function(event) {
+        var x = event.pageX - this.offsetLeft;
+        var y = event.pageY - this.offsetTop;
+        that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO */);    
+      });
+    
+      this.htmlCanvas_.addEventListener('mousemove', function(event) {
+        var x = event.pageX - this.offsetLeft;
+        var y = event.pageY - this.offsetTop;
+        that.ViewportMouseMove(that.pimpl_, x, y);
+      });
+    
+      this.htmlCanvas_.addEventListener('mouseup', function(event) {
+        that.ViewportMouseUp(that.pimpl_);
+      });
+    
+      window.addEventListener('keydown', function(event) {
+        that.ViewportKeyPressed(that.pimpl_, event.key, event.shiftKey, event.ctrlKey, event.altKey);
+      });
+    
+      this.htmlCanvas_.addEventListener('wheel', function(event) {
+        var x = event.pageX - this.offsetLeft;
+        var y = event.pageY - this.offsetTop;
+        that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey);
+        event.preventDefault();
+      });
+
+      this.htmlCanvas_.addEventListener('touchstart', function(event) {
+        that.ResetTouch();
+      });
+    
+      this.htmlCanvas_.addEventListener('touchend', function(event) {
+        that.ResetTouch();
+      });
+    
+      this.htmlCanvas_.addEventListener('touchmove', function(event) {
+        if (that.touchTranslation_.length == 2) {
+          var t = that.GetTouchTranslation(event);
+          that.ViewportMouseMove(that.pimpl_, t[0], t[1]);
+        }
+        else if (that.touchZoom_.length == 3) {
+          var z0 = that.touchZoom_;
+          var z1 = that.GetTouchZoom(event);
+          that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]);
+        }
+        else {
+          // Realize the gesture event
+          if (event.targetTouches.length == 1) {
+            // Exactly one finger inside the canvas => Setup a translation
+            that.touchTranslation_ = that.GetTouchTranslation(event);
+            that.ViewportMouseDown(that.pimpl_, 
+                                  1 /* middle button */,
+                                  that.touchTranslation_[0],
+                                  that.touchTranslation_[1], 0);
+          } else if (event.targetTouches.length == 2) {
+            // Exactly 2 fingers inside the canvas => Setup a pinch/zoom
+            that.touchZoom_ = that.GetTouchZoom(event);
+            var z0 = that.touchZoom_;
+            that.ViewportMouseDown(that.pimpl_, 
+                                  2 /* right button */,
+                                  z0[0],
+                                  z0[1], 0);
+          }        
+        }
+      });
+    }  
+
+  public ResetTouch() {
+    if (this.touchTranslation_ ||
+        this.touchZoom_) {
+      this.ViewportMouseUp(this.pimpl_);
+    }
+
+    this.touchTranslation_ = false;
+    this.touchZoom_ = false;
+  }
+  
+  public GetTouchTranslation(event) {
+    var touch = event.targetTouches[0];
+    return [
+      touch.pageX,
+      touch.pageY
+    ];
+  }
+    
+  public GetTouchZoom(event) {
+    var touch1 = event.targetTouches[0];
+    var touch2 = event.targetTouches[1];
+    var dx = (touch1.pageX - touch2.pageX);
+    var dy = (touch1.pageY - touch2.pageY);
+    var d = Math.sqrt(dx * dx + dy * dy);
+    return [
+      (touch1.pageX + touch2.pageX) / 2.0,
+      (touch1.pageY + touch2.pageY) / 2.0,
+      d
+    ];
+  }
+    
+}
+}
+  
\ No newline at end of file
--- a/Platforms/WebAssembly/CMakeLists.txt	Sat Jul 14 11:20:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-# Usage (Linux):
-# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake ..
-
-cmake_minimum_required(VERSION 2.8.3)
-
-
-#####################################################################
-## Configuration of the Emscripten compiler for WebAssembly target
-#####################################################################
-
-set(WASM_FLAGS "-s WASM=1")
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js")
-
-# Handling of memory
-#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")  # Resize
-#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")  # 512MB
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912")  # 512MB + resize
-#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824")  # 1GB + resize
-
-# To debug exceptions
-#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2")
-
-
-#####################################################################
-## Build a static library containing the Orthanc Stone framework
-#####################################################################
-
-include(../../Resources/CMake/OrthancStoneParameters.cmake)
-
-SET(ORTHANC_SANDBOXED ON)
-SET(ENABLE_SDL OFF)
-
-include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
-
-add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
-
-
-
-
-
-
-# Regenerate a dummy "library.c" file each time the "library.js" file
-# is modified, so as to force a new execution of the linking
-add_custom_command(
-    OUTPUT "${AUTOGENERATED_DIR}/library.c"
-    COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" ""
-    DEPENDS "${CMAKE_SOURCE_DIR}/library.js")
--- a/Platforms/WebAssembly/library.js	Sat Jul 14 11:20:07 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-mergeInto(LibraryManager.library, {
-});
--- a/README	Sat Jul 14 11:20:07 2018 +0200
+++ b/README	Tue Jul 17 14:43:42 2018 +0200
@@ -72,6 +72,10 @@
 * Optionally, SDL, a cross-platform multimedia library:
   https://www.libsdl.org/
 
+Prerequisites to compile on Ubuntu: 
+```
+sudo apt-get install -y libcairo-dev libpixman-1-dev libsdl2-dev
+```
 
 Installation and usage
 ----------------------
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Sat Jul 14 11:20:07 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Jul 17 14:43:42 2018 +0200
@@ -141,21 +141,38 @@
 ## All the source files required to build Stone of Orthanc
 #####################################################################
 
+set(APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/IBasicApplication.h
+    ${ORTHANC_STONE_ROOT}/Applications/BasicApplicationContext.cpp
+    )
+
 if (NOT ORTHANC_SANDBOXED)
   set(PLATFORM_SOURCES
+    ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.h
     )
 
-  set(APPLICATIONS_SOURCES
-    ${ORTHANC_STONE_ROOT}/Applications/BasicApplicationContext.cpp
-    ${ORTHANC_STONE_ROOT}/Applications/IBasicApplication.cpp
+  list(APPEND APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplication.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplicationContext.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp
     )
+else()
+  list(APPEND APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp
+    )
+
+  set(STONE_WASM_SOURCES
+    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmViewport.cpp
+  )
 endif()
 
 list(APPEND ORTHANC_STONE_SOURCES
@@ -163,11 +180,13 @@
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SiblingSliceLocationFactory.cpp
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomStructureSetRendererFactory.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/FrameRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/GrayscaleFrameRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Layers/ILayerSource.h
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LayerSourceBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
@@ -195,6 +214,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/SlicedVolumeBase.cpp
@@ -210,6 +230,12 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp
 
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h
+
   ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
@@ -229,3 +255,4 @@
   ${SDL_SOURCES}
   ${BOOST_EXTENDED_SOURCES}
   )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -0,0 +1,109 @@
+/**
+ * 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 "../Framework/Messages/MessageBroker.h"
+#include "../Framework/Messages/IMessage.h"
+#include "../Framework/Messages/IObservable.h"
+#include "../Framework/Messages/IObserver.h"
+#include "../Framework/StoneEnumerations.h"
+
+
+static int globalCounter = 0;
+class MyObserver : public OrthancStone::IObserver
+{
+
+public:
+  MyObserver(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObserver(broker)
+  {}
+
+
+  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+    if (message.GetType() == OrthancStone::MessageType_Generic) {
+      globalCounter++;
+    }
+
+  }
+
+};
+
+
+TEST(MessageBroker, NormalUsage)
+{
+  OrthancStone::MessageBroker broker;
+  OrthancStone::IObservable observable(broker);
+
+  globalCounter = 0;
+
+  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+
+  // no observers have been registered -> nothing shall happen
+  observable.EmitMessage(genericMessage);
+
+  ASSERT_EQ(0, globalCounter);
+
+  // register an observer, check it is called
+  MyObserver observer(broker);
+  observable.RegisterObserver(observer);
+
+  observable.EmitMessage(genericMessage);
+
+  ASSERT_EQ(1, globalCounter);
+
+  // check the observer is not called when another message is issued
+  OrthancStone::IMessage wrongMessage(OrthancStone::MessageType_GeometryReady);
+  // no observers have been registered
+  observable.EmitMessage(wrongMessage);
+
+  ASSERT_EQ(1, globalCounter);
+
+  // unregister the observer, make sure nothing happens afterwards
+  observable.UnregisterObserver(observer);
+  observable.EmitMessage(genericMessage);
+  ASSERT_EQ(1, globalCounter);
+}
+
+TEST(MessageBroker, DeleteObserverWhileRegistered)
+{
+  OrthancStone::MessageBroker broker;
+  OrthancStone::IObservable observable(broker);
+
+  globalCounter = 0;
+
+  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+
+  {
+    // register an observer, check it is called
+    MyObserver observer(broker);
+    observable.RegisterObserver(observer);
+
+    observable.EmitMessage(genericMessage);
+
+    ASSERT_EQ(1, globalCounter);
+  }
+
+  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
+  observable.EmitMessage(genericMessage);
+
+  ASSERT_EQ(1, globalCounter);
+}
--- a/UnitTestsSources/UnitTestsMain.cpp	Sat Jul 14 11:20:07 2018 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Jul 17 14:43:42 2018 +0200
@@ -55,7 +55,7 @@
 
       for (size_t i = 0; i < loader.GetSliceCount(); i++)
       {
-        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_Full);
+        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng);
       }
     }