changeset 289:36ebe6ec8fe8 am-2

merged default into am-2
author am@osimis.io
date Thu, 30 Aug 2018 16:56:08 +0200
parents 8c8da145fefa (diff) 3a8bac805352 (current diff)
children 87376a645ee1
files
diffstat 128 files changed, 6152 insertions(+), 1673 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,7 @@
+Platforms/Generic/CMakeLists.txt.user
+Platforms/Generic/ThirdPartyDownloads/
+Platforms/Wasm/CMakeLists.txt.user
+Platforms/Wasm/build/
+Platforms/Wasm/build-web/
+Platforms/Wasm/ThirdPartyDownloads/
+Applications/Qt/archive/
--- a/Applications/BasicApplicationContext.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/**
- * 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 "BasicApplicationContext.h"
-
-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	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/**
- * 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>
-
-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()
-    {
-      return webService_;
-    }
-    
-    ISlicedVolume& AddSlicedVolume(ISlicedVolume* volume);
-
-    IVolumeLoader& AddVolumeLoader(IVolumeLoader* loader);
-
-    IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
-
-    void Start();
-
-    void Stop();
-
-    void SetUpdateDelay(unsigned int delay)  // In milliseconds
-    {
-      updateDelay_ = delay;
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/BasicNativeApplicationContext.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,80 @@
+/**
+ * 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 "BasicNativeApplicationContext.h"
+#include "../../Platforms/Generic/OracleWebService.h"
+
+namespace OrthancStone
+{
+  IWidget& BasicNativeApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
+  {
+    centralViewport_->SetCentralWidget(widget);
+    return *widget;
+  }
+
+
+  void BasicNativeApplicationContext::UpdateThread(BasicNativeApplicationContext* that)
+  {
+    while (!that->stopped_)
+    {
+      {
+        GlobalMutexLocker locker(*that);
+        that->GetCentralViewport().UpdateContent();
+      }
+      
+      boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelayInMs_));
+    }
+  }
+  
+
+  BasicNativeApplicationContext::BasicNativeApplicationContext() :
+    centralViewport_(new OrthancStone::WidgetViewport()),
+    stopped_(true),
+    updateDelayInMs_(100)   // By default, 100ms between each refresh of the content
+  {
+    srand(time(NULL)); 
+  }
+
+
+  void BasicNativeApplicationContext::Start()
+  {
+    dynamic_cast<OracleWebService*>(webService_)->Start();
+
+    if (centralViewport_->HasUpdateContent())
+    {
+      stopped_ = false;
+      updateThread_ = boost::thread(UpdateThread, this);
+    }
+  }
+
+
+  void BasicNativeApplicationContext::Stop()
+  {
+    stopped_ = true;
+    
+    if (updateThread_.joinable())
+    {
+      updateThread_.join();
+    }
+    
+    dynamic_cast<OracleWebService*>(webService_)->Stop();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/BasicNativeApplicationContext.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,73 @@
+/**
+ * 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 <list>
+#include <boost/thread.hpp>
+#include "../StoneApplicationContext.h"
+
+namespace OrthancStone
+{
+  class BasicNativeApplicationContext : public StoneApplicationContext
+  {
+  private:
+
+    static void UpdateThread(BasicNativeApplicationContext* that);
+
+    boost::mutex        globalMutex_;
+    std::unique_ptr<WidgetViewport>      centralViewport_;
+    boost::thread       updateThread_;
+    bool                stopped_;
+    unsigned int        updateDelayInMs_;
+
+  public:
+    class GlobalMutexLocker: public boost::noncopyable
+    {
+      boost::mutex::scoped_lock  lock_;
+    public:
+      GlobalMutexLocker(BasicNativeApplicationContext& that):
+        lock_(that.globalMutex_)
+      {}
+    };
+
+    BasicNativeApplicationContext();
+
+    virtual ~BasicNativeApplicationContext() {}
+
+    virtual IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
+    IViewport& GetCentralViewport() {return *(centralViewport_.get());}
+
+    void Start();
+
+    void Stop();
+
+    void SetUpdateDelay(unsigned int delayInMs)
+    {
+      updateDelayInMs_ = delayInMs;
+    }
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/BasicNativeApplicationRunner.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,240 @@
+/**
+ * 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_NATIVE != 1
+#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1
+#endif
+
+#include "BasicNativeApplicationRunner.h"
+#include "BasicNativeApplicationContext.h"
+#include <boost/program_options.hpp>
+
+#include "../../Framework/Toolbox/MessagingToolbox.h"
+
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>
+#include "../../Platforms/Generic/OracleWebService.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;
+      }
+    };
+  }
+
+  int BasicNativeApplicationRunner::Execute(int argc,
+                                            char* argv[])
+  {
+    /******************************************************************
+     * Initialize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Orthanc::Logging::Initialize();
+    Orthanc::Toolbox::InitializeOpenSsl();
+    Orthanc::HttpClient::GlobalInitialize();
+
+    Initialize();
+
+    /******************************************************************
+     * Declare and parse the command-line options of the application
+     ******************************************************************/
+
+    boost::program_options::options_description options;
+
+    { // generic options
+      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);
+    }
+
+    // platform specific options
+    DeclareCommandLineOptions(options);
+    
+    // application specific 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);
+    }
+
+    ParseCommandLineOptions(parameters);
+
+
+    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;
+
+      BasicNativeApplicationContext context;
+      Oracle oracle(4); // use 4 threads to download content
+      OracleWebService webService(broker_, oracle, webServiceParameters, context);
+      context.SetWebService(webService);
+
+      application_.Initialize(&context, statusBar, parameters);
+
+      {
+        BasicNativeApplicationContext::GlobalMutexLocker locker(context);
+        context.SetCentralWidget(application_.GetCentralWidget());
+        context.GetCentralViewport().SetStatusBar(statusBar);
+      }
+
+      std::string title = application_.GetTitle();
+      if (title.empty())
+      {
+        title = "Stone of Orthanc";
+      }
+
+      /****************************************************************
+       * Run the application
+       ****************************************************************/
+
+      Run(context, title, argc, argv);
+
+      /****************************************************************
+       * Finalize the application
+       ****************************************************************/
+
+      LOG(WARNING) << "The application is stopping";
+      application_.Finalize();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+      success = false;
+    }
+
+
+    /******************************************************************
+     * Finalize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Finalize();
+    Orthanc::HttpClient::GlobalFinalize();
+    Orthanc::Toolbox::FinalizeOpenSsl();
+
+    return (success ? 0 : -1);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/BasicNativeApplicationRunner.h	Thu Aug 30 16:56:08 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 "../IStoneApplication.h"
+
+#if ORTHANC_ENABLE_NATIVE != 1
+#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1
+#endif
+
+namespace OrthancStone
+{
+  class BasicNativeApplicationContext;
+
+  class BasicNativeApplicationRunner
+  {
+  protected:
+    MessageBroker&      broker_;
+    IStoneApplication&  application_;
+  public:
+
+    BasicNativeApplicationRunner(MessageBroker& broker,
+                                 IStoneApplication& application)
+      : broker_(broker),
+        application_(application)
+    {
+    }
+    int Execute(int argc,
+                char* argv[]);
+
+    virtual void Initialize() = 0;
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
+    virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters) = 0;
+
+    virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]) = 0;
+    virtual void Finalize() = 0;
+  };
+
+}
--- a/Applications/IBasicApplication.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,300 +0,0 @@
-/**
- * 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 <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;
-      }
-    };
-  }
-
-
-#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::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.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>());
-      }
-
-      std::string username, password;
-      
-      if (parameters.count("username"))
-      {
-        username = parameters["username"].as<std::string>();
-      }
-
-      if (parameters.count("password"))
-      {
-        password = parameters["password"].as<std::string>();
-      }
-
-      if (!username.empty() ||
-          !password.empty())
-      {
-        webService.SetCredentials(username, password);
-      }
-
-      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::Toolbox::FinalizeOpenSsl();
-
-    return (success ? 0 : -1);
-  }
-#endif
-
-}
--- a/Applications/IBasicApplication.h	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * 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 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
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/IStoneApplication.h	Thu Aug 30 16:56:08 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 "StoneApplicationContext.h"
+#include <boost/program_options.hpp>
+#include "../Framework/Viewport/WidgetViewport.h"
+#include "json/json.h"
+
+namespace OrthancStone
+{
+  // a StoneApplication is an application that can actually be executed
+  // in multiple environments.  i.e: it can run natively integrated in a QtApplication
+  // or it can be executed as part of a WebPage when compiled into WebAssembly.
+  class IStoneApplication : public boost::noncopyable
+  {
+  protected:
+    StoneApplicationContext* context_;
+
+  public:
+    virtual ~IStoneApplication()
+    {
+    }
+
+    virtual void DeclareStartupOptions(boost::program_options::options_description& options) = 0;
+    virtual void Initialize(StoneApplicationContext* context,
+                            IStatusBar& statusBar,
+                            const boost::program_options::variables_map& parameters) = 0;
+#if ORTHANC_ENABLE_WASM==1
+    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 IWidget* GetCentralWidget() = 0;
+
+    virtual void Finalize() = 0;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/BasicQtApplicationRunner.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,67 @@
+/**
+ * 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_QT != 1
+#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1
+#endif
+
+#include "BasicQtApplicationRunner.h"
+#include <boost/program_options.hpp>
+#include <QApplication>
+
+#include "../../Framework/Toolbox/MessagingToolbox.h"
+
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>
+#include "../../Platforms/Generic/OracleWebService.h"
+
+
+namespace OrthancStone
+{
+  void BasicQtApplicationRunner::Initialize()
+  {
+  }
+
+  void BasicQtApplicationRunner::DeclareCommandLineOptions(boost::program_options::options_description& options)
+  {
+  }
+
+  void BasicQtApplicationRunner::Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[])
+  {
+    context.Start();
+
+    QApplication app(argc, argv);
+    InitializeMainWindow(context);
+
+    window_->show();
+    app.exec();
+
+    context.Stop();
+  }
+
+  void BasicQtApplicationRunner::Finalize()
+  {
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/BasicQtApplicationRunner.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,55 @@
+/**
+ * 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 "../Generic/BasicNativeApplicationRunner.h"
+#include "QStoneMainWindow.h"
+
+#if ORTHANC_ENABLE_QT != 1
+#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1
+#endif
+
+namespace OrthancStone
+{
+  class BasicQtApplicationRunner : public BasicNativeApplicationRunner
+  {
+  protected:
+    std::auto_ptr<QStoneMainWindow> window_;
+
+    virtual void InitializeMainWindow(BasicNativeApplicationContext& context) = 0;
+  public:
+    BasicQtApplicationRunner(MessageBroker& broker,
+                             IStoneApplication& application)
+      : BasicNativeApplicationRunner(broker, application)
+    {
+    }
+
+
+    virtual void Initialize();
+
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options);
+    virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters) {}
+    virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]);
+    virtual void Finalize();
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/QCairoWidget.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,164 @@
+/**
+ * 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 "QCairoWidget.h"
+
+#include <QPainter>
+#include <QPaintEvent>
+
+#include <stdexcept>
+
+QCairoWidget::QCairoWidget(QWidget *parent) :
+  QWidget(parent),
+  context_(NULL)
+{
+  setFocusPolicy(Qt::StrongFocus); // catch keyPressEvents
+}
+
+QCairoWidget::~QCairoWidget()
+{
+}
+
+void QCairoWidget::SetContext(OrthancStone::BasicNativeApplicationContext& context)
+{
+  context_ = &context;
+  context_->GetCentralViewport().Register(*this); // get notified each time the content of the central viewport changes
+}
+
+void QCairoWidget::paintEvent(QPaintEvent* /*event*/)
+{
+  QPainter painter(this);
+
+  if (image_.get() != NULL && context_ != NULL)
+  {
+    OrthancStone::BasicNativeApplicationContext::GlobalMutexLocker locker(*context_);
+    OrthancStone::IViewport& viewport = context_->GetCentralViewport();
+    Orthanc::ImageAccessor a = surface_.GetAccessor();
+    viewport.Render(a);
+    painter.drawImage(0, 0, *image_);
+  }
+  else
+  {
+    painter.fillRect(rect(), Qt::red);
+  }
+}
+
+OrthancStone::KeyboardModifiers GetKeyboardModifiers(QInputEvent* event)
+{
+  Qt::KeyboardModifiers qtModifiers = event->modifiers();
+  int stoneModifiers = static_cast<int>(OrthancStone::KeyboardModifiers_None);
+  if ((qtModifiers & Qt::AltModifier) != 0)
+  {
+    stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Alt);
+  }
+  if ((qtModifiers & Qt::ControlModifier) != 0)
+  {
+    stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Control);
+  }
+  if ((qtModifiers & Qt::ShiftModifier) != 0)
+  {
+    stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Shift);
+  }
+  return static_cast<OrthancStone::KeyboardModifiers>(stoneModifiers);
+}
+
+void QCairoWidget::mousePressEvent(QMouseEvent* event)
+{
+  OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event);
+
+  OrthancStone::MouseButton button;
+
+  switch (event->button())
+  {
+    case Qt::LeftButton:
+      button = OrthancStone::MouseButton_Left;
+      break;
+
+    case Qt::RightButton:
+      button = OrthancStone::MouseButton_Right;
+      break;
+
+    case Qt::MiddleButton:
+      button = OrthancStone::MouseButton_Middle;
+      break;
+
+    default:
+      return;  // Unsupported button
+  }
+  context_->GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers);
+}
+
+
+void QCairoWidget::mouseReleaseEvent(QMouseEvent* /*eventNotUsed*/)
+{
+  context_->GetCentralViewport().MouseLeave();
+}
+
+
+void QCairoWidget::mouseMoveEvent(QMouseEvent* event)
+{
+  context_->GetCentralViewport().MouseMove(event->pos().x(), event->pos().y());
+}
+
+
+void QCairoWidget::wheelEvent(QWheelEvent * event)
+{
+  OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event);
+
+  if (event->orientation() == Qt::Vertical)
+  {
+    if (event->delta() < 0)  // TODO: compare direction with SDL and make sure we send the same directions
+    {
+       context_->GetCentralViewport().MouseWheel(OrthancStone::MouseWheelDirection_Up, event->pos().x(), event->pos().y(), stoneModifiers);
+    }
+    else
+    {
+      context_->GetCentralViewport().MouseWheel(OrthancStone::MouseWheelDirection_Down, event->pos().x(), event->pos().y(), stoneModifiers);
+    }
+  }
+}
+
+void QCairoWidget::keyPressEvent(QKeyEvent *event)
+{
+  OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event);
+
+  context_->GetCentralViewport().KeyPressed(event->text()[0].toLatin1(), stoneModifiers);
+}
+
+
+void QCairoWidget::resizeEvent(QResizeEvent* event)
+{
+  grabGesture(Qt::PanGesture);
+  QWidget::resizeEvent(event);
+
+  if (event)
+  {
+    surface_.SetSize(event->size().width(), event->size().height());
+
+    image_.reset(new QImage(reinterpret_cast<uchar*>(surface_.GetBuffer()),
+                            event->size().width(), 
+                            event->size().height(),
+                            surface_.GetPitch(),
+                            QImage::Format_RGB32));
+
+    context_->GetCentralViewport().SetSize(event->size().width(), event->size().height());
+
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/QCairoWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,75 @@
+/**
+ * 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/Widgets/CairoWidget.h"
+#include "../../Applications/Generic/BasicNativeApplicationContext.h"
+#include "../../Framework/Viewport/CairoSurface.h"
+
+#include <QWidget>
+#include <QGestureEvent>
+#include <memory>
+#include <cassert>
+
+class QCairoWidget : public QWidget, public OrthancStone::IViewport::IObserver
+{
+  Q_OBJECT
+
+private:
+  std::auto_ptr<QImage>         image_;
+  OrthancStone::CairoSurface    surface_;
+  OrthancStone::BasicNativeApplicationContext* context_;
+
+protected:
+  virtual void paintEvent(QPaintEvent *event);
+
+  virtual void resizeEvent(QResizeEvent *event);
+
+  virtual void mouseMoveEvent(QMouseEvent *event);
+
+  virtual void mousePressEvent(QMouseEvent *event);
+
+  virtual void mouseReleaseEvent(QMouseEvent *event);
+
+  virtual void wheelEvent(QWheelEvent *event);
+
+  virtual void keyPressEvent(QKeyEvent *event);
+
+public:
+  explicit QCairoWidget(QWidget *parent);
+ 
+  virtual ~QCairoWidget();
+
+  void SetContext(OrthancStone::BasicNativeApplicationContext& context);
+
+  virtual void OnViewportContentChanged(const OrthancStone::IViewport& /*sceneNotUsed*/)
+  {
+    update();  // schedule a repaint (handled by Qt)
+    emit ContentChanged();
+  }
+
+signals:
+
+  void ContentChanged();
+                                               
+public slots:
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/QStoneMainWindow.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,41 @@
+/**
+ * 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 "QStoneMainWindow.h"
+
+namespace OrthancStone
+{
+
+  QStoneMainWindow::QStoneMainWindow(BasicNativeApplicationContext& context, QWidget *parent) :
+    QMainWindow(parent),
+    context_(context)
+  {
+  }
+
+  void QStoneMainWindow::SetCentralStoneWidget(QCairoWidget *centralWidget)
+  {
+    cairoCentralWidget_ = centralWidget;
+    cairoCentralWidget_->SetContext(context_);
+  }
+
+  QStoneMainWindow::~QStoneMainWindow()
+  {
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Qt/QStoneMainWindow.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,45 @@
+/**
+ * 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 <QMainWindow>
+
+#include "QCairoWidget.h"
+#include "../Generic/BasicNativeApplicationContext.h"
+
+namespace OrthancStone
+{
+  class QStoneMainWindow : public QMainWindow
+  {
+    Q_OBJECT
+
+  private:
+    OrthancStone::BasicNativeApplicationContext& context_;
+    QCairoWidget          *cairoCentralWidget_;
+
+  protected:  // you must inherit this class
+    QStoneMainWindow(BasicNativeApplicationContext& context, QWidget *parent = 0);
+    void SetCentralStoneWidget(QCairoWidget* centralWidget);
+  public:
+    virtual ~QStoneMainWindow();
+
+  };
+
+}
--- a/Applications/Samples/EmptyApplication.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Samples/EmptyApplication.h	Thu Aug 30 16:56:08 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));
       }
     };
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Qt/SampleMainWindow.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,86 @@
+/**
+ * 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 "SampleMainWindow.h"
+
+/**
+ * Don't use "ui_MainWindow.h" instead of <ui_MainWindow.h> below, as
+ * this makes CMake unable to detect when the UI file changes.
+ **/
+#include <ui_SampleMainWindow.h>
+#include "../../Applications/Samples/SampleApplicationBase.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+
+    SampleMainWindow::SampleMainWindow(OrthancStone::BasicNativeApplicationContext& context, OrthancStone::Samples::SampleApplicationBase& stoneSampleApplication, QWidget *parent) :
+      QStoneMainWindow(context, parent),
+      ui_(new Ui::SampleMainWindow),
+      stoneSampleApplication_(stoneSampleApplication)
+    {
+      ui_->setupUi(this);
+      SetCentralStoneWidget(ui_->cairoCentralWidget);
+
+      connect(ui_->toolButton1, &QToolButton::clicked, this, &SampleMainWindow::tool1Clicked);
+      connect(ui_->toolButton2, &QToolButton::clicked, this, &SampleMainWindow::tool2Clicked);
+      connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindow::pushButton1Clicked);
+      connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindow::pushButton2Clicked);
+
+      std::string pushButton1Name;
+      std::string pushButton2Name;
+      std::string tool1Name;
+      std::string tool2Name;
+      stoneSampleApplication_.GetButtonNames(pushButton1Name, pushButton2Name, tool1Name, tool2Name);
+
+      ui_->toolButton1->setText(QString::fromStdString(tool1Name));
+      ui_->toolButton2->setText(QString::fromStdString(tool2Name));
+      ui_->pushButton1->setText(QString::fromStdString(pushButton1Name));
+      ui_->pushButton2->setText(QString::fromStdString(pushButton2Name));
+    }
+
+    SampleMainWindow::~SampleMainWindow()
+    {
+      delete ui_;
+    }
+
+    void SampleMainWindow::tool1Clicked()
+    {
+      stoneSampleApplication_.OnTool1Clicked();
+    }
+
+    void SampleMainWindow::tool2Clicked()
+    {
+      stoneSampleApplication_.OnTool2Clicked();
+    }
+
+    void SampleMainWindow::pushButton1Clicked()
+    {
+      stoneSampleApplication_.OnPushButton1Clicked();
+    }
+
+    void SampleMainWindow::pushButton2Clicked()
+    {
+      stoneSampleApplication_.OnPushButton2Clicked();
+    }
+
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Qt/SampleMainWindow.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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 "../../Qt/QCairoWidget.h"
+#include "../../Qt/QStoneMainWindow.h"
+
+namespace Ui 
+{
+  class SampleMainWindow;
+}
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+
+    class SampleApplicationBase;
+
+    class SampleMainWindow : public QStoneMainWindow
+    {
+      Q_OBJECT
+
+    private:
+      Ui::SampleMainWindow*   ui_;
+      SampleApplicationBase&  stoneSampleApplication_;
+
+    public:
+      explicit SampleMainWindow(OrthancStone::BasicNativeApplicationContext& context, SampleApplicationBase& stoneSampleApplication, QWidget *parent = 0);
+      ~SampleMainWindow();
+
+    private slots:
+      void tool1Clicked();
+      void tool2Clicked();
+      void pushButton1Clicked();
+      void pushButton2Clicked();
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Qt/SampleMainWindow.ui	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SampleMainWindow</class>
+ <widget class="QMainWindow" name="SampleMainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>903</width>
+    <height>634</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>500</width>
+    <height>300</height>
+   </size>
+  </property>
+  <property name="baseSize">
+   <size>
+    <width>500</width>
+    <height>300</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Stone of Orthanc</string>
+  </property>
+  <property name="layoutDirection">
+   <enum>Qt::LeftToRight</enum>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
+   </property>
+   <property name="layoutDirection">
+    <enum>Qt::LeftToRight</enum>
+   </property>
+   <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0">
+    <property name="sizeConstraint">
+     <enum>QLayout::SetDefaultConstraint</enum>
+    </property>
+    <item>
+     <widget class="QCairoWidget" name="cairoCentralWidget">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>500</height>
+       </size>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <widget class="QGroupBox" name="horizontalGroupBox">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>100</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>16777215</width>
+        <height>100</height>
+       </size>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <widget class="QToolButton" name="toolButton1">
+         <property name="text">
+          <string>tool1</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QToolButton" name="toolButton2">
+         <property name="text">
+          <string>tool2</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="pushButton1">
+         <property name="text">
+          <string>action1</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="pushButton2">
+         <property name="text">
+          <string>action2</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>903</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuTest">
+    <property name="title">
+     <string>Test</string>
+    </property>
+   </widget>
+   <addaction name="menuTest"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>QCairoWidget</class>
+   <extends>QGraphicsView</extends>
+   <header location="global">QCairoWidget.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Qt/SampleQtApplicationRunner.h	Thu Aug 30 16:56:08 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/>.
+ **/
+
+
+#pragma once
+
+#include "../../Qt/BasicQtApplicationRunner.h"
+#include "SampleMainWindow.h"
+
+#if ORTHANC_ENABLE_QT != 1
+#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1
+#endif
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SampleQtApplicationRunner : public OrthancStone::BasicQtApplicationRunner
+    {
+    protected:
+      virtual void InitializeMainWindow(OrthancStone::BasicNativeApplicationContext& context)
+      {
+        window_.reset(new SampleMainWindow(context, dynamic_cast<OrthancStone::Samples::SampleApplicationBase&>(application_)));
+      }
+    public:
+      SampleQtApplicationRunner(MessageBroker& broker,
+                                SampleApplicationBase& application)
+        : OrthancStone::BasicQtApplicationRunner(broker, application)
+      {
+      }
+
+    };
+  }
+}
--- a/Applications/Samples/SampleApplicationBase.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Samples/SampleApplicationBase.h	Thu Aug 30 16:56:08 2018 +0200
@@ -21,27 +21,44 @@
 
 #pragma once
 
-#include "../IBasicApplication.h"
+#include "../../Applications/IStoneApplication.h"
 
 namespace OrthancStone
 {
   namespace Samples
   {
-    class SampleApplicationBase : public IBasicApplication
+    class SampleApplicationBase : public IStoneApplication
     {
     public:
+      virtual void Initialize(StoneApplicationContext* context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+    }
+
+
+
       virtual std::string GetTitle() const
       {
         return "Stone of Orthanc - Sample";
       }
 
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
+      virtual void OnPushButton1Clicked() {}
+      virtual void OnPushButton2Clicked() {}
+      virtual void OnTool1Clicked() {}
+      virtual void OnTool2Clicked() {}
+
+      virtual void GetButtonNames(std::string& pushButton1, 
+      std::string& pushButton2, 
+      std::string& tool1, 
+      std::string& tool2
+      ) {
+        pushButton1 = "action1";
+        pushButton2 = "action2";
+        tool1 = "tool1";
+        tool2 = "tool2";
       }
 
-      virtual void Finalize()
-      {
-      }
     };
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleList.h	Thu Aug 30 16:56:08 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 SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 2
+#include "TestPatternApplication.h"
+typedef OrthancStone::Samples::TestPatternApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 3
+#include "SingleFrameApplication.h"
+typedef OrthancStone::Samples::SingleFrameApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 4
+#include "SingleVolumeApplication.h"
+typedef OrthancStone::Samples::SingleVolumeApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 5
+#include "BasicPetCtFusionApplication.h"
+typedef OrthancStone::Samples::BasicPetCtFusionApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 6
+#include "SynchronizedSeriesApplication.h"
+typedef OrthancStone::Samples::SynchronizedSeriesApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 7
+#include "LayoutPetCtFusionApplication.h"
+typedef OrthancStone::Samples::LayoutPetCtFusionApplication SampleApplication;
+
+#elif ORTHANC_STONE_SAMPLE == 8
+#include "SimpleViewerApplication.h"
+typedef OrthancStone::Samples::SimpleViewerApplication SampleApplication;
+
+#else
+#error Please set the ORTHANC_STONE_SAMPLE macro
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleMainNative.cpp	Thu Aug 30 16:56:08 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/>.
+ **/
+
+
+#include "SampleList.h"
+#if ORTHANC_ENABLE_SDL==1
+#include "../Sdl/BasicSdlApplication.h"
+#endif
+#if ORTHANC_ENABLE_QT==1
+#include "Qt/SampleQtApplicationRunner.h"
+#endif
+#include "../../Framework/Messages/MessageBroker.h"
+
+int main(int argc, char* argv[]) 
+{
+  OrthancStone::MessageBroker broker;
+  SampleApplication sampleStoneApplication(broker);
+
+#if ORTHANC_ENABLE_SDL==1
+  OrthancStone::BasicSdlApplication sdlApplication;
+  return sdlApplication.Execute(broker, sampleStoneApplication, argc, argv);
+#endif
+#if ORTHANC_ENABLE_QT==1
+  OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication);
+  return qtAppRunner.Execute(argc, argv);
+#endif
+}
--- a/Applications/Samples/SampleMainSdl.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * 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/>.
- **/
-
-
-// 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
-
-
-int main(int argc, char* argv[]) 
-{
-  Application application;
-
-  return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleMainWasm.cpp	Thu Aug 30 16:56:08 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::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) {
+  
+  return new SampleApplication(broker);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SimpleViewerApplication.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,417 @@
+/**
+ * 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/Layers/CircleMeasureTracker.h"
+#include "../../Framework/Layers/LineMeasureTracker.h"
+#include "../../Framework/Widgets/LayerWidget.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/SmartLoader.h"
+
+#if ORTHANC_ENABLE_WASM==1
+#include "../../Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h"
+#include "../../Platforms/Wasm/Defaults.h"
+#endif
+#include <Core/Logging.h>
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SimpleViewerApplication :
+        public SampleApplicationBase,
+#if ORTHANC_ENABLE_WASM==1
+        public IStoneApplicationToWebApplicationAdapter,
+#endif
+        public IObserver
+    {
+    private:
+      class ThumbnailInteractor : public IWorldSceneInteractor
+      {
+      private:
+        SimpleViewerApplication&  application_;
+      public:
+        ThumbnailInteractor(SimpleViewerApplication&  application) :
+          application_(application)
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            KeyboardModifiers modifiers,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            statusBar->SetMessage("selected thumbnail " + widget.GetName());
+            std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-"));
+            application_.SelectSeriesInMainViewport(seriesId);
+          }
+          return NULL;
+        }
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {}
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                MouseWheelDirection direction,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {}
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {}
+
+      };
+
+      class MainWidgetInteractor : public IWorldSceneInteractor
+      {
+      private:
+        SimpleViewerApplication&  application_;
+        
+      public:
+        MainWidgetInteractor(SimpleViewerApplication&  application) :
+          application_(application)
+        {
+        }
+        
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            KeyboardModifiers modifiers,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            if (application_.currentTool_ == Tools_LineMeasure)
+            {
+              return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
+            }
+            else if (application_.currentTool_ == Tools_CircleMeasure)
+            {
+              return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
+            }
+          }
+          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)
+        {
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          switch (key)
+          {
+          case 's':
+            widget.SetDefaultView();
+            break;
+
+          default:
+            break;
+          }
+        }
+      };
+
+      enum Tools {
+        Tools_LineMeasure,
+        Tools_CircleMeasure
+      };
+
+      Tools                           currentTool_;
+      std::unique_ptr<MainWidgetInteractor> mainWidgetInteractor_;
+      std::unique_ptr<ThumbnailInteractor>  thumbnailInteractor_;
+      LayoutWidget*                   mainLayout_;
+      LayoutWidget*                   thumbnailsLayout_;
+      LayerWidget*                    mainWidget_;
+      std::vector<LayerWidget*>       thumbnails_;
+      std::map<std::string, std::vector<std::string>> instancesIdsPerSeriesId_;
+      std::map<std::string, Json::Value> seriesTags_;
+
+      unsigned int                    currentInstanceIndex_;
+      OrthancStone::WidgetViewport*   wasmViewport1_;
+      OrthancStone::WidgetViewport*   wasmViewport2_;
+
+      IStatusBar*                     statusBar_;
+      std::unique_ptr<SmartLoader>    smartLoader_;
+      std::unique_ptr<OrthancApiClient>      orthancApiClient_;
+
+    public:
+      SimpleViewerApplication(MessageBroker& broker) :
+        IObserver(broker),
+        currentTool_(Tools_LineMeasure),
+        mainLayout_(NULL),
+        currentInstanceIndex_(0),
+        wasmViewport1_(NULL),
+        wasmViewport2_(NULL)
+      {
+        DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
+        DeclareHandledMessage(MessageType_Widget_GeometryChanged);
+
+        DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready);
+        DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready);
+        DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready);
+      }
+
+      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()
+            ("studyId", boost::program_options::value<std::string>(),
+             "Orthanc ID of the study")
+            ;
+
+        options.add(generic);
+      }
+
+      virtual void Initialize(StoneApplicationContext* context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        context_ = context;
+        statusBar_ = &statusBar;
+
+        {// initialize viewports and layout
+          mainLayout_ = new LayoutWidget("main-layout");
+          mainLayout_->SetPadding(10);
+          mainLayout_->SetBackgroundCleared(true);
+          mainLayout_->SetBackgroundColor(0, 0, 0);
+          mainLayout_->SetHorizontal();
+
+          thumbnailsLayout_ = new LayoutWidget("thumbnail-layout");
+          thumbnailsLayout_->SetPadding(10);
+          thumbnailsLayout_->SetBackgroundCleared(true);
+          thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
+          thumbnailsLayout_->SetVertical();
+
+          mainWidget_ = new LayerWidget(broker_, "main-viewport");
+          mainWidget_->RegisterObserver(*this);
+
+          // hierarchy
+          mainLayout_->AddWidget(thumbnailsLayout_);
+          mainLayout_->AddWidget(mainWidget_);
+
+          // sources
+          smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
+          smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+
+          mainLayout_->SetTransmitMouseOver(true);
+          mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
+          mainWidget_->SetInteractor(*mainWidgetInteractor_);
+          thumbnailInteractor_.reset(new ThumbnailInteractor(*this));
+        }
+
+        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");
+
+        orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService()));
+
+        if (parameters.count("studyId") < 1)
+        {
+          LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
+          orthancApiClient_->ScheduleGetStudyIds(*this);
+        }
+        else
+        {
+          SelectStudy(parameters["studyId"].as<std::string>());
+        }
+      }
+
+      void OnStudyListReceived(const Json::Value& response)
+      {
+        if (response.isArray() && response.size() > 1)
+        {
+          SelectStudy(response[0].asString());
+        }
+      }
+      void OnStudyReceived(const Json::Value& response)
+      {
+        if (response.isObject() && response["Series"].isArray())
+        {
+          for (size_t i=0; i < response["Series"].size(); i++)
+          {
+            orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString());
+          }
+        }
+      }
+
+      void OnSeriesReceived(const Json::Value& response)
+      {
+        if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
+        {
+          // keep track of all instances IDs
+          const std::string& seriesId = response["ID"].asString();
+          seriesTags_[seriesId] = response;
+          instancesIdsPerSeriesId_[seriesId] = std::vector<std::string>();
+          for (size_t i = 0; i < response["Instances"].size(); i++)
+          {
+            const std::string& instanceId = response["Instances"][static_cast<int>(i)].asString();
+            instancesIdsPerSeriesId_[seriesId].push_back(instanceId);
+          }
+
+          // load the first instance in the thumbnail
+          LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]);
+
+          // if this is the first thumbnail loaded, load the first instance in the mainWidget
+          if (mainWidget_->GetLayerCount() == 0)
+          {
+            mainWidget_->AddLayer(smartLoader_->GetFrame(instancesIdsPerSeriesId_[seriesId][0], 0));
+          }
+        }
+      }
+
+      void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
+      {
+        LOG(INFO) << "Loading thumbnail for series " << seriesId;
+        LayerWidget* thumbnailWidget = new LayerWidget(broker_, "thumbnail-series-" + seriesId);
+        thumbnails_.push_back(thumbnailWidget);
+        thumbnailsLayout_->AddWidget(thumbnailWidget);
+        thumbnailWidget->RegisterObserver(*this);
+        thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0));
+        thumbnailWidget->SetInteractor(*thumbnailInteractor_);
+      }
+
+      void SelectStudy(const std::string& studyId)
+      {
+        orthancApiClient_->ScheduleGetStudy(*this, studyId);
+      }
+
+      virtual void HandleMessage(IObservable& from, const IMessage& message) {
+        switch (message.GetType()) {
+        case MessageType_Widget_GeometryChanged:
+          LOG(INFO) << "Widget geometry ready: " << dynamic_cast<LayerWidget&>(from).GetName();
+          dynamic_cast<LayerWidget&>(from).SetDefaultView();
+          break;
+        case MessageType_OrthancApi_GetStudyIds_Ready:
+          OnStudyListReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        case MessageType_OrthancApi_GetSeries_Ready:
+          OnSeriesReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        case MessageType_OrthancApi_GetStudy_Ready:
+          OnStudyReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        default:
+          VLOG("unhandled message type" << message.GetType());
+        }
+      }
+
+      void SelectSeriesInMainViewport(const std::string& seriesId)
+      {
+        mainWidget_->ReplaceLayer(0, smartLoader_->GetFrame(instancesIdsPerSeriesId_[seriesId][0], 0));
+#if ORTHANC_ENABLE_WASM==1
+        NotifyStatusUpdateFromCppToWeb("series-description=" + seriesTags_[seriesId]["MainDicomTags"]["SeriesDescription"].asString());
+#endif
+      }
+
+      virtual void OnPushButton1Clicked() {}
+      virtual void OnPushButton2Clicked() {}
+      virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;}
+      virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;}
+
+      virtual void GetButtonNames(std::string& pushButton1,
+                                  std::string& pushButton2,
+                                  std::string& tool1,
+                                  std::string& tool2
+                                  ) {
+        tool1 = "line";
+        tool2 = "circle";
+        pushButton1 = "action1";
+        pushButton2 = "action2";
+      }
+
+#if ORTHANC_ENABLE_WASM==1
+      virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
+        if (input == "select-tool:line-measure")
+        {
+          currentTool_ = Tools_LineMeasure;
+          NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
+        }
+        else if (input == "select-tool:circle-measure")
+        {
+          currentTool_ = Tools_CircleMeasure;
+          NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
+        }
+
+        output = "ok";
+      }
+
+      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
+        UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
+      }
+
+      virtual void InitializeWasm() {
+
+        AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
+        AttachWidgetToWasmViewport("canvas2", mainWidget_);
+      }
+#endif
+    };
+
+
+  }
+}
--- a/Applications/Samples/SingleFrameApplication.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Samples/SingleVolumeApplication.h	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Samples/TestPatternApplication.h	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,34 @@
+<!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 id="breadcrumb">
+    <span id="patient-id"></span>
+    <span id="study-description"></span>
+    <span id="series-description"></span>
+  </div>
+  <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>
+  <div id="toolbox">
+    <input tool-selector="line-measure" type="radio" name="radio-tool-selector" class="tool-selector">line
+    <input tool-selector="circle-measure" type="radio" name="radio-tool-selector" class="tool-selector">circle
+    <button action-trigger="action1" class="action-trigger">action1</button>
+    <button action-trigger="action2" class="action-trigger">action2</button>
+  </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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,39 @@
+///<reference path='../../../Platforms/Wasm/wasm-application-runner.ts'/>
+
+InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc");
+
+function SelectTool(toolName: string) {
+    SendMessageToStoneApplication("select-tool:" + toolName);
+}
+
+function PerformAction(actionName: string) {
+    SendMessageToStoneApplication("perform-action:" + actionName);
+}
+
+//initializes the buttons
+//-----------------------
+// install "SelectTool" handlers
+document.querySelectorAll("[tool-selector]").forEach((e) => {
+    console.log(e);
+    (e as HTMLInputElement).addEventListener("click", () => {
+        console.log(e);
+        SelectTool(e.attributes["tool-selector"].value);
+    });
+});
+
+// install "PerformAction" handlers
+document.querySelectorAll("[action-trigger]").forEach((e) => {
+    (e as HTMLInputElement).addEventListener("click", () => {
+        PerformAction(e.attributes["action-trigger"].value);
+    });
+});
+
+// this method is called "from the C++ code" when the StoneApplication is updated.
+// it can be used to update the UI of the application
+function UpdateWebApplication(statusUpdateMessage: string) {
+  console.log(statusUpdateMessage);
+  
+  if (statusUpdateMessage.startsWith("series-description=")) {
+      document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/tsconfig-samples.json	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,11 @@
+{
+    "extends" : "../../../Platforms/Wasm/tsconfig-stone",
+    "compilerOptions": {
+        "sourceMap": false,
+        "lib" : [
+            "es2017",
+            "dom",
+            "dom.iterable"
+        ]
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/Web/tsconfig-simple-viewer.json	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,10 @@
+{
+    "extends" : "./tsconfig-samples",
+    "compilerOptions": {
+        "outFile": "../../../Platforms/Wasm/build-web/app-simple-viewer.js"
+    },
+    "include" : [
+        "simple-viewer.ts",
+        "common-samples.ts"
+    ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/samples-library.js	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,127 @@
+/**
+ * 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>
+#include "../../Platforms/Generic/OracleWebService.h"
+
+namespace OrthancStone
+{
+  void BasicSdlApplication::Initialize()
+  {
+    SdlWindow::GlobalInitialize();
+  }
+
+  void BasicSdlApplication::DeclareCommandLineOptions(boost::program_options::options_description& options)
+  {
+    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);
+  }
+
+  void BasicSdlApplication::ParseCommandLineOptions(const boost::program_options::variables_map& parameters)
+  {
+    if (!parameters.count("width") ||
+        !parameters.count("height") ||
+        !parameters.count("opengl"))
+    {
+      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    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";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    width_ = static_cast<unsigned int>(w);
+    height_ = static_cast<unsigned int>(h);
+    LOG(WARNING) << "Initial display size: " << width_ << "x" << height_;
+
+    enableOpenGl_ = parameters["opengl"].as<bool>();
+    if (enableOpenGl_)
+    {
+      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";
+    }
+
+  }
+
+  void BasicSdlApplication::Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[])
+  {
+    /**************************************************************
+     * Run the application inside a SDL window
+     **************************************************************/
+
+    LOG(WARNING) << "Starting the application";
+
+    SdlWindow window(title.c_str(), width_, height_, enableOpenGl_);
+    SdlEngine sdl(window, context);
+
+    {
+      BasicNativeApplicationContext::GlobalMutexLocker locker(context);
+      context.GetCentralViewport().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();
+  }
+
+  void BasicSdlApplication::Finalize()
+  {
+    SdlWindow::GlobalFinalize();
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/BasicSdlApplication.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,47 @@
+/**
+ * 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 "../Generic/BasicNativeApplication.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 BasicNativeApplication
+  {
+    unsigned int width_;
+    unsigned int height_;
+    bool enableOpenGl_;
+  public:
+    virtual void Initialize();
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options);
+    virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]);
+    virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters);
+    virtual void Finalize();
+  };
+
+}
--- a/Applications/Sdl/SdlEngine.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Sdl/SdlEngine.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -29,11 +29,10 @@
 
 namespace OrthancStone
 {
-  void SdlEngine::SetSize(BasicApplicationContext::ViewportLocker& locker,
-                          unsigned int width,
+  void SdlEngine::SetSize(unsigned int width,
                           unsigned int height)
   {
-    locker.GetViewport().SetSize(width, height);
+    context_.GetCentralViewport().SetSize(width, height);
     surface_.SetSize(width, height);
   }
     
@@ -42,8 +41,8 @@
   {
     if (viewportChanged_)
     {
-      BasicApplicationContext::ViewportLocker locker(context_);
-      surface_.Render(locker.GetViewport());
+      BasicNativeApplicationContext::GlobalMutexLocker locker(context_);
+      surface_.Render(context_.GetCentralViewport());
 
       viewportChanged_ = false;
     }
@@ -99,7 +98,7 @@
 
 
   SdlEngine::SdlEngine(SdlWindow& window,
-                       BasicApplicationContext& context) :
+                       BasicNativeApplicationContext& context) :
     window_(window),
     context_(context),
     surface_(window),
@@ -119,9 +118,9 @@
     const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
 
     {
-      BasicApplicationContext::ViewportLocker locker(context_);
-      SetSize(locker, window_.GetWidth(), window_.GetHeight());
-      locker.GetViewport().SetDefaultView();
+      BasicNativeApplicationContext::GlobalMutexLocker locker(context_);
+      SetSize(window_.GetWidth(), window_.GetHeight());
+      context_.GetCentralViewport().SetDefaultView();
     }
     
     bool stop = false;
@@ -134,7 +133,7 @@
       while (!stop &&
              SDL_PollEvent(&event))
       {
-        BasicApplicationContext::ViewportLocker locker(context_);
+        BasicNativeApplicationContext::GlobalMutexLocker locker(context_);
 
         if (event.type == SDL_QUIT) 
         {
@@ -148,15 +147,15 @@
           switch (event.button.button)
           {
             case SDL_BUTTON_LEFT:
-              locker.GetViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
+              context_.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
               break;
             
             case SDL_BUTTON_RIGHT:
-              locker.GetViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
+              context_.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
               break;
             
             case SDL_BUTTON_MIDDLE:
-              locker.GetViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
+              context_.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
               break;
 
             default:
@@ -165,26 +164,26 @@
         }
         else if (event.type == SDL_MOUSEMOTION)
         {
-          locker.GetViewport().MouseMove(event.button.x, event.button.y);
+          context_.GetCentralViewport().MouseMove(event.button.x, event.button.y);
         }
         else if (event.type == SDL_MOUSEBUTTONUP)
         {
-          locker.GetViewport().MouseUp();
+          context_.GetCentralViewport().MouseUp();
         }
         else if (event.type == SDL_WINDOWEVENT)
         {
           switch (event.window.event)
           {
             case SDL_WINDOWEVENT_LEAVE:
-              locker.GetViewport().MouseLeave();
+              context_.GetCentralViewport().MouseLeave();
               break;
 
             case SDL_WINDOWEVENT_ENTER:
-              locker.GetViewport().MouseEnter();
+              context_.GetCentralViewport().MouseEnter();
               break;
 
             case SDL_WINDOWEVENT_SIZE_CHANGED:
-              SetSize(locker, event.window.data1, event.window.data2);
+              SetSize(event.window.data1, event.window.data2);
               break;
 
             default:
@@ -200,11 +199,11 @@
 
           if (event.wheel.y > 0)
           {
-            locker.GetViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
+            context_.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
           }
           else if (event.wheel.y < 0)
           {
-            locker.GetViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
+            context_.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
           }
         }
         else if (event.type == SDL_KEYDOWN &&
@@ -214,50 +213,50 @@
 
           switch (event.key.keysym.sym)
           {
-            case SDLK_a:    locker.GetViewport().KeyPressed('a', modifiers);  break;
-            case SDLK_b:    locker.GetViewport().KeyPressed('b', modifiers);  break;
-            case SDLK_c:    locker.GetViewport().KeyPressed('c', modifiers);  break;
-            case SDLK_d:    locker.GetViewport().KeyPressed('d', modifiers);  break;
-            case SDLK_e:    locker.GetViewport().KeyPressed('e', modifiers);  break;
+            case SDLK_a:    context_.GetCentralViewport().KeyPressed('a', modifiers);  break;
+            case SDLK_b:    context_.GetCentralViewport().KeyPressed('b', modifiers);  break;
+            case SDLK_c:    context_.GetCentralViewport().KeyPressed('c', modifiers);  break;
+            case SDLK_d:    context_.GetCentralViewport().KeyPressed('d', modifiers);  break;
+            case SDLK_e:    context_.GetCentralViewport().KeyPressed('e', modifiers);  break;
             case SDLK_f:    window_.ToggleMaximize();                         break;
-            case SDLK_g:    locker.GetViewport().KeyPressed('g', modifiers);  break;
-            case SDLK_h:    locker.GetViewport().KeyPressed('h', modifiers);  break;
-            case SDLK_i:    locker.GetViewport().KeyPressed('i', modifiers);  break;
-            case SDLK_j:    locker.GetViewport().KeyPressed('j', modifiers);  break;
-            case SDLK_k:    locker.GetViewport().KeyPressed('k', modifiers);  break;
-            case SDLK_l:    locker.GetViewport().KeyPressed('l', modifiers);  break;
-            case SDLK_m:    locker.GetViewport().KeyPressed('m', modifiers);  break;
-            case SDLK_n:    locker.GetViewport().KeyPressed('n', modifiers);  break;
-            case SDLK_o:    locker.GetViewport().KeyPressed('o', modifiers);  break;
-            case SDLK_p:    locker.GetViewport().KeyPressed('p', modifiers);  break;
+            case SDLK_g:    context_.GetCentralViewport().KeyPressed('g', modifiers);  break;
+            case SDLK_h:    context_.GetCentralViewport().KeyPressed('h', modifiers);  break;
+            case SDLK_i:    context_.GetCentralViewport().KeyPressed('i', modifiers);  break;
+            case SDLK_j:    context_.GetCentralViewport().KeyPressed('j', modifiers);  break;
+            case SDLK_k:    context_.GetCentralViewport().KeyPressed('k', modifiers);  break;
+            case SDLK_l:    context_.GetCentralViewport().KeyPressed('l', modifiers);  break;
+            case SDLK_m:    context_.GetCentralViewport().KeyPressed('m', modifiers);  break;
+            case SDLK_n:    context_.GetCentralViewport().KeyPressed('n', modifiers);  break;
+            case SDLK_o:    context_.GetCentralViewport().KeyPressed('o', modifiers);  break;
+            case SDLK_p:    context_.GetCentralViewport().KeyPressed('p', modifiers);  break;
             case SDLK_q:    stop = true;                                      break;
-            case SDLK_r:    locker.GetViewport().KeyPressed('r', modifiers);  break;
-            case SDLK_s:    locker.GetViewport().KeyPressed('s', modifiers);  break;
-            case SDLK_t:    locker.GetViewport().KeyPressed('t', modifiers);  break;
-            case SDLK_u:    locker.GetViewport().KeyPressed('u', modifiers);  break;
-            case SDLK_v:    locker.GetViewport().KeyPressed('v', modifiers);  break;
-            case SDLK_w:    locker.GetViewport().KeyPressed('w', modifiers);  break;
-            case SDLK_x:    locker.GetViewport().KeyPressed('x', modifiers);  break;
-            case SDLK_y:    locker.GetViewport().KeyPressed('y', modifiers);  break;
-            case SDLK_z:    locker.GetViewport().KeyPressed('z', modifiers);  break;
-            case SDLK_KP_0: locker.GetViewport().KeyPressed('0', modifiers);  break;
-            case SDLK_KP_1: locker.GetViewport().KeyPressed('1', modifiers);  break;
-            case SDLK_KP_2: locker.GetViewport().KeyPressed('2', modifiers);  break;
-            case SDLK_KP_3: locker.GetViewport().KeyPressed('3', modifiers);  break;
-            case SDLK_KP_4: locker.GetViewport().KeyPressed('4', modifiers);  break;
-            case SDLK_KP_5: locker.GetViewport().KeyPressed('5', modifiers);  break;
-            case SDLK_KP_6: locker.GetViewport().KeyPressed('6', modifiers);  break;
-            case SDLK_KP_7: locker.GetViewport().KeyPressed('7', modifiers);  break;
-            case SDLK_KP_8: locker.GetViewport().KeyPressed('8', modifiers);  break;
-            case SDLK_KP_9: locker.GetViewport().KeyPressed('9', modifiers);  break;
+            case SDLK_r:    context_.GetCentralViewport().KeyPressed('r', modifiers);  break;
+            case SDLK_s:    context_.GetCentralViewport().KeyPressed('s', modifiers);  break;
+            case SDLK_t:    context_.GetCentralViewport().KeyPressed('t', modifiers);  break;
+            case SDLK_u:    context_.GetCentralViewport().KeyPressed('u', modifiers);  break;
+            case SDLK_v:    context_.GetCentralViewport().KeyPressed('v', modifiers);  break;
+            case SDLK_w:    context_.GetCentralViewport().KeyPressed('w', modifiers);  break;
+            case SDLK_x:    context_.GetCentralViewport().KeyPressed('x', modifiers);  break;
+            case SDLK_y:    context_.GetCentralViewport().KeyPressed('y', modifiers);  break;
+            case SDLK_z:    context_.GetCentralViewport().KeyPressed('z', modifiers);  break;
+            case SDLK_KP_0: context_.GetCentralViewport().KeyPressed('0', modifiers);  break;
+            case SDLK_KP_1: context_.GetCentralViewport().KeyPressed('1', modifiers);  break;
+            case SDLK_KP_2: context_.GetCentralViewport().KeyPressed('2', modifiers);  break;
+            case SDLK_KP_3: context_.GetCentralViewport().KeyPressed('3', modifiers);  break;
+            case SDLK_KP_4: context_.GetCentralViewport().KeyPressed('4', modifiers);  break;
+            case SDLK_KP_5: context_.GetCentralViewport().KeyPressed('5', modifiers);  break;
+            case SDLK_KP_6: context_.GetCentralViewport().KeyPressed('6', modifiers);  break;
+            case SDLK_KP_7: context_.GetCentralViewport().KeyPressed('7', modifiers);  break;
+            case SDLK_KP_8: context_.GetCentralViewport().KeyPressed('8', modifiers);  break;
+            case SDLK_KP_9: context_.GetCentralViewport().KeyPressed('9', modifiers);  break;
 
             case SDLK_PLUS:
             case SDLK_KP_PLUS:
-              locker.GetViewport().KeyPressed('+', modifiers);  break;
+              context_.GetCentralViewport().KeyPressed('+', modifiers);  break;
 
             case SDLK_MINUS:
             case SDLK_KP_MINUS:
-              locker.GetViewport().KeyPressed('-', modifiers);  break;
+              context_.GetCentralViewport().KeyPressed('-', modifiers);  break;
 
             default:
               break;
--- a/Applications/Sdl/SdlEngine.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Applications/Sdl/SdlEngine.h	Thu Aug 30 16:56:08 2018 +0200
@@ -24,7 +24,7 @@
 #if ORTHANC_ENABLE_SDL == 1
 
 #include "SdlCairoSurface.h"
-#include "../BasicApplicationContext.h"
+#include "../Generic/BasicNativeApplicationContext.h"
 
 namespace OrthancStone
 {
@@ -32,12 +32,11 @@
   {
   private:
     SdlWindow&                window_;
-    BasicApplicationContext&  context_;
+    BasicNativeApplicationContext&  context_;
     SdlCairoSurface           surface_;
     bool                      viewportChanged_;
 
-    void SetSize(BasicApplicationContext::ViewportLocker& locker,
-                 unsigned int width,
+    void SetSize(unsigned int width,
                  unsigned int height);
     
     void RenderFrame();
@@ -47,11 +46,11 @@
 
   public:
     SdlEngine(SdlWindow& window,
-              BasicApplicationContext& context);
+              BasicNativeApplicationContext& context);
   
     virtual ~SdlEngine();
 
-    virtual void NotifyChange(const IViewport& viewport)
+    virtual void OnViewportContentChanged(const IViewport& viewport)
     {
       viewportChanged_ = true;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneApplicationContext.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,26 @@
+/**
+ * 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 "StoneApplicationContext.h"
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneApplicationContext.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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/Toolbox/IWebService.h"
+#include "../Framework/Viewport/WidgetViewport.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  // a StoneApplicationContext contains the services that a StoneApplication
+  // uses and that depends on the environment in which the Application executes.
+  // I.e, the StoneApplicationContext provides a WebService interface such that
+  // the StoneApplication can perform HTTP requests.  In a WASM environment,
+  // the WebService is provided by the browser while, in a native environment,
+  // the WebService is provided by the OracleWebService (a C++ Http client)
+  class StoneApplicationContext : public boost::noncopyable
+  {
+
+  protected:
+    IWebService* webService_;
+  public:
+    StoneApplicationContext()
+      : webService_(NULL)
+    {
+    }
+
+    IWebService& GetWebService() {return *webService_;}
+    void SetWebService(IWebService& webService)
+    {
+      webService_ = &webService;
+    }
+
+    virtual ~StoneApplicationContext() {}
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Wasm/StartupParametersBuilder.cpp	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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/CircleMeasureTracker.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -76,8 +76,10 @@
     if (fontSize_ != 0)
     {
       cairo_move_to(cr, x, y);
+#if ORTHANC_ENABLE_NATIVE==1 // text rendering currently fails in wasm
       CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
       font.Draw(context, FormatRadius(), static_cast<double>(fontSize_) / zoom);
+#endif
     }
   }
     
--- a/Framework/Layers/DicomStructureSetRendererFactory.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/DicomStructureSetRendererFactory.h	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Thu Aug 30 16:56:08 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_LayerSource_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_LayerSource_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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -25,63 +25,32 @@
 
 namespace OrthancStone
 {
-  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)
-      {
-      }
-
-      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_LayerSource_GeometryReady));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    observers_.Apply(*this, &IObserver::NotifyGeometryError);
-  }  
+    EmitMessage(IMessage(MessageType_LayerSource_GeometryError));
+  }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    observers_.Apply(*this, &IObserver::NotifyContentChange);
+    EmitMessage(IMessage(MessageType_LayerSource_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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Thu Aug 30 16:56:08 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/>.
  **/
@@ -28,11 +28,6 @@
 {
   class LayerSourceBase : public ILayerSource
   {
-  private:
-    typedef ObserversRegistry<ILayerSource, IObserver>  Observers;
-
-    Observers  observers_;
-
   protected:
     void NotifyGeometryReady();
     
@@ -46,7 +41,15 @@
                           const CoordinateSystem3D& slice,
                           bool isError);
 
-  public:
-    virtual void Register(IObserver& observer);
+    LayerSourceBase(MessageBroker& broker)
+      : ILayerSource(broker)
+    {
+      DeclareEmittableMessage(MessageType_LayerSource_GeometryReady);
+      DeclareEmittableMessage(MessageType_LayerSource_GeometryError);
+      DeclareEmittableMessage(MessageType_LayerSource_ContentChanged);
+      DeclareEmittableMessage(MessageType_LayerSource_SliceChanged);
+      DeclareEmittableMessage(MessageType_LayerSource_LayerReady);
+    }
+
   };
 }
--- a/Framework/Layers/LineMeasureTracker.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/LineMeasureTracker.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -63,8 +63,10 @@
     if (fontSize_ != 0)
     {
       cairo_move_to(cr, x2_, y2_ - static_cast<double>(fontSize_) / zoom);
+#if ORTHANC_ENABLE_NATIVE==1 // text rendering currently fails in wasm
       CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
       font.Draw(context, FormatLength(), static_cast<double>(fontSize_) / zoom);
+#endif
     }
   }
     
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -31,47 +31,59 @@
 
 namespace OrthancStone
 {
-  void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::HandleMessage(IObservable& from, const IMessage& message)
   {
-    if (loader.GetSliceCount() > 0)
+    switch (message.GetType())
+    {
+    case MessageType_SliceLoader_GeometryReady:
+    {
+      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
+      if (loader.GetSliceCount() > 0)
+      {
+        LayerSourceBase::NotifyGeometryReady();
+      }
+      else
+      {
+        LayerSourceBase::NotifyGeometryError();
+      }
+
+    }; break;
+    case MessageType_SliceLoader_GeometryError:
     {
-      LayerSourceBase::NotifyGeometryReady();
-    }
-    else
+      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
+      LayerSourceBase::NotifyGeometryError();
+    }; break;
+    case MessageType_SliceLoader_ImageReady:
     {
-      LayerSourceBase::NotifyGeometryError();
+      const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
+      bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam);
+      LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(msg.image_.release(), msg.slice_, isFull),
+                                        msg.slice_.GetGeometry(), false);
+
+    }; break;
+    case MessageType_SliceLoader_ImageError:
+    {
+      const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
+      LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true);
+    }; break;
+    default:
+      VLOG("unhandled message type" << message.GetType());
     }
   }
 
-  void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
-  {
-    LayerSourceBase::NotifyGeometryError();
-  }
 
-  void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
-                                                      unsigned int sliceIndex,
-                                                      const Slice& slice,
-                                                      std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                                      SliceImageQuality quality)
+  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
+    LayerSourceBase(broker),
+    IObserver(broker),
+    //OrthancSlicesLoader::ISliceLoaderObserver(broker),
+    loader_(broker, orthanc),
+    quality_(SliceImageQuality_FullPng)
   {
-    bool isFull = (quality == SliceImageQuality_Full);
-    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull),
-                                      slice.GetGeometry(), false);
-  }
-
-  void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
-                                                      unsigned int sliceIndex,
-                                                      const Slice& slice,
-                                                      SliceImageQuality quality)
-  {
-    LayerSourceBase::NotifyLayerReady(NULL, slice.GetGeometry(), true);
-  }
-
-
-  OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) :
-    loader_(*this, orthanc),
-    quality_(SliceImageQuality_Full)
-  {
+    DeclareHandledMessage(MessageType_SliceLoader_GeometryReady);
+    DeclareHandledMessage(MessageType_SliceLoader_GeometryError);
+    DeclareHandledMessage(MessageType_SliceLoader_ImageReady);
+    DeclareHandledMessage(MessageType_SliceLoader_ImageError);
+    loader_.RegisterObserver(*this);
   }
 
   
--- a/Framework/Layers/OrthancFrameLayerSource.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Thu Aug 30 16:56:08 2018 +0200
@@ -27,31 +27,20 @@
 
 namespace OrthancStone
 {  
+  // this class is in charge of loading a Frame.
+  // once it's been loaded (first the geometry and then the image),
+  // messages are sent to observers so they can use it
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    private OrthancSlicesLoader::ICallback
+    public IObserver
+    //private OrthancSlicesLoader::ISliceLoaderObserver
   {
   private:
     OrthancSlicesLoader  loader_;
     SliceImageQuality    quality_;
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader);
-
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader);
-
-    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                       SliceImageQuality quality);
-
-    virtual void NotifySliceImageError(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);
 
@@ -79,5 +68,7 @@
                            const CoordinateSystem3D& viewportSlice);
 
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice);
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IMessage.h	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,111 @@
+/**
+ * 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 <set>
+#include <assert.h>
+#include <algorithm>
+#include <iostream>
+
+#include "MessageBroker.h"
+#include "MessageType.h"
+#include "IObserver.h"
+
+namespace OrthancStone {
+
+  class MessageNotDeclaredException : public std::logic_error
+  {
+    MessageType messageType_;
+  public:
+    MessageNotDeclaredException(MessageType messageType)
+      : std::logic_error("Message not declared by observer."),
+        messageType_(messageType)
+    {
+    }
+  };
+
+  class IObservable : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                     broker_;
+
+    std::set<IObserver*>              observers_;
+    std::set<MessageType>             emittableMessages_;
+
+  public:
+
+    IObservable(MessageBroker& broker)
+      : broker_(broker)
+    {
+    }
+    virtual ~IObservable()
+    {
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      if (emittableMessages_.find(message.GetType()) == emittableMessages_.end())
+      {
+        throw MessageNotDeclaredException(message.GetType());
+      }
+
+      broker_.EmitMessage(*this, observers_, message);
+    }
+
+    void RegisterObserver(IObserver& observer)
+    {
+      CheckObserverDeclaredAllObservableMessages(observer);
+      observers_.insert(&observer);
+    }
+
+    void UnregisterObserver(IObserver& observer)
+    {
+      observers_.erase(&observer);
+    }
+
+    const std::set<MessageType>& GetEmittableMessages() const
+    {
+      return emittableMessages_;
+    }
+
+  protected:
+
+    void DeclareEmittableMessage(MessageType messageType)
+    {
+      emittableMessages_.insert(messageType);
+    }
+
+    void CheckObserverDeclaredAllObservableMessages(IObserver& observer)
+    {
+      for (std::set<MessageType>::const_iterator it = emittableMessages_.begin(); it != emittableMessages_.end(); it++)
+      {
+        // the observer must have "declared" all observable messages
+        if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end()
+            && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end())
+        {
+          throw MessageNotDeclaredException(*it);
+        }
+      }
+    }
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IObserver.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,88 @@
+/**
+ * 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 <set>
+#include <assert.h>
+
+namespace OrthancStone {
+
+  class IObservable;
+
+  class IObserver : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+    std::set<MessageType>             handledMessages_;
+    std::set<MessageType>             ignoredMessages_;
+
+  public:
+    IObserver(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IObserver()
+    {
+      broker_.Unregister(*this);
+    }
+
+    void HandleMessage_(IObservable &from, const IMessage &message)
+    {
+      assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling
+
+      HandleMessage(from, message);
+    }
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
+
+
+    const std::set<MessageType>& GetHandledMessages() const
+    {
+      return handledMessages_;
+    }
+
+    const std::set<MessageType>& GetIgnoredMessages() const
+    {
+      return ignoredMessages_;
+    }
+
+  protected:
+
+    // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration)
+    // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer
+    void DeclareHandledMessage(MessageType messageType)
+    {
+      handledMessages_.insert(messageType);
+    }
+
+    void DeclareIgnoredMessage(MessageType messageType)
+    {
+      ignoredMessages_.insert(messageType);
+    }
+
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageBroker.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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++)
+    {
+      if ((*observer)->GetHandledMessages().find(message.GetType()) != (*observer)->GetHandledMessages().end())
+      {
+        (*observer)->HandleMessage_(from, message);
+      }
+      else
+      {
+        assert((*observer)->GetIgnoredMessages().find(message.GetType()) != (*observer)->GetIgnoredMessages().end()); // message has not been declared by Observer (this should already have been checked during registration)
+      }
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageBroker.h	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,55 @@
+/**
+ * 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_Widget_GeometryChanged,
+    MessageType_Widget_ContentChanged,
+
+    MessageType_LayerSource_GeometryReady,
+    MessageType_LayerSource_GeometryError,
+    MessageType_LayerSource_ContentChanged,
+    MessageType_LayerSource_SliceChanged,
+    MessageType_LayerSource_LayerReady,
+
+    MessageType_SliceLoader_GeometryReady,
+    MessageType_SliceLoader_GeometryError,
+    MessageType_SliceLoader_ImageReady,
+    MessageType_SliceLoader_ImageError,
+
+    MessageType_HttpRequestSuccess,
+    MessageType_HttpRequestError,
+
+    MessageType_OrthancApi_InternalGetJsonResponseReady,
+    MessageType_OrthancApi_InternalGetJsonResponseError,
+
+    MessageType_OrthancApi_GetStudyIds_Ready,
+    MessageType_OrthancApi_GetStudy_Ready,
+    MessageType_OrthancApi_GetSeries_Ready,
+
+    // used in unit tests only
+    MessageType_Test1,
+    MessageType_Test2
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SmartLoader.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,103 @@
+/**
+ * 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),
+    orthancApiClient_(broker, webService)
+  {
+    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
+    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
+    DeclareIgnoredMessage(MessageType_LayerSource_GeometryError);
+    DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged);
+    DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged);
+
+//    DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+//    DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+  }
+
+  void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    switch (message.GetType()) {
+    case MessageType_LayerSource_GeometryReady:
+    {
+      const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
+      // TODO keep track of objects that have been loaded already
+    }; break;
+    case MessageType_LayerSource_LayerReady:
+    {
+      const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
+      // TODO keep track of objects that have been loaded already
+    }; break;
+//    case MessageType_OrthancApi_GetStudyIds_Ready:
+//    {
+
+//      const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast<OrthancApiClient::GetJsonResponseReadyMessage&>(message);
+
+//    }; break;
+    default:
+      VLOG("unhandled message type" << message.GetType());
+    }
+
+    // forward messages to its own observers
+    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
+  }
+
+  ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame)
+  {
+    // TODO: check if this frame has already been loaded or is already being loaded.
+    // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately"
+    //   (it can not be immediate because Observers needs to register first and this is done after this method returns)
+    // - if currently loading, we need to return an object that will observe the existing LayerSource and forward
+    //   the messages to its observables
+    // in both cases, we must be carefull about objects lifecycle !!!
+    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_));
+    layerSource->SetImageQuality(imageQuality_);
+    layerSource->RegisterObserver(*this);
+    layerSource->LoadFrame(instanceId, frame);
+
+    return layerSource.release();
+  }
+
+  void SmartLoader::LoadStudyList()
+  {
+//    orthancApiClient_.ScheduleGetJsonRequest("/studies");
+  }
+
+  void PreloadStudy(const std::string studyId)
+  {
+    /* TODO */
+  }
+
+  void PreloadSeries(const std::string seriesId)
+  {
+    /* TODO */
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SmartLoader.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,57 @@
+/**
+ * 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 <map>
+
+#include "Layers/ILayerSource.h"
+#include "Messages/IObservable.h"
+#include "../Platforms/Generic/OracleWebService.h"
+#include "Toolbox/OrthancApiClient.h"
+
+namespace OrthancStone
+{
+  class SmartLoader : public IObservable, IObserver
+  {
+    SliceImageQuality     imageQuality_;
+    IWebService&          webService_;
+    OrthancApiClient      orthancApiClient_;
+
+    int studyListRequest_;
+
+  public:
+    SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
+    void PreloadStudy(const std::string studyId);
+    void PreloadSeries(const std::string seriesId);
+    void LoadStudyList();
+
+    void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
+
+    ILayerSource* GetFrame(const std::string& instanceId, unsigned int frame);
+
+    void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId);
+
+  };
+
+}
--- a/Framework/StoneEnumerations.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/StoneEnumerations.h	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Thu Aug 30 16:56:08 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,40 +22,116 @@
 #pragma once
 
 #include <Core/IDynamicObject.h>
-
+#include "../../Framework/Messages/IObserver.h"
 #include <string>
+#include <Core/Logging.h>
 
 namespace OrthancStone
 {
-  class IWebService : public boost::noncopyable
+  // The IWebService performs HTTP requests.
+  // Since applications can run in native or WASM environment and, since
+  // in a WASM environment, the WebService is asynchronous, the IWebservice
+  // also implements an asynchronous interface: you must schedule a request
+  // and you'll be notified when the response/error is ready.
+  class IWebService
   {
+  protected:
+    MessageBroker& broker_;
   public:
-    class ICallback : public boost::noncopyable
+    typedef std::map<std::string, std::string> Headers;
+
+    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)
+      {
+        DeclareHandledMessage(MessageType_HttpRequestError);
+        DeclareHandledMessage(MessageType_HttpRequestSuccess);
+      }
       virtual ~ICallback()
       {
       }
 
-      virtual void NotifyError(const std::string& uri,
-                               Orthanc::IDynamicObject* payload) = 0;
+      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;
 
-      virtual void NotifySuccess(const std::string& uri,
-                                 const void* answer,
-                                 size_t answerSize,
-                                 Orthanc::IDynamicObject* payload) = 0;
+        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 OnHttpRequestSuccess(const std::string& uri,
+                                        const void* answer,
+                                        size_t answerSize,
+                                        Orthanc::IDynamicObject* payload) = 0;
     };
-    
+
+    IWebService(MessageBroker& broker)
+      : broker_(broker)
+    {}
+
     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;
   };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,205 @@
+/**
+ * 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 "OrthancApiClient.h"
+
+#include "MessagingToolbox.h"
+#include <Core/OrthancException.h>
+
+namespace OrthancStone {
+
+  struct OrthancApiClient::InternalGetJsonResponseReadyMessage :
+      public IMessage
+  {
+    OrthancApiClient::BaseRequest*  request_;
+    Json::Value   response_;
+
+    InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest*  request,
+                             const Json::Value& response)
+      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady),
+        request_(request),
+        response_(response)
+    {
+    }
+
+  };
+
+  struct OrthancApiClient::InternalGetJsonResponseErrorMessage :
+      public IMessage
+  {
+    OrthancApiClient::BaseRequest*  request_;
+
+    InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request)
+      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError),
+        request_(request)
+    {
+    }
+  };
+
+
+  // this class handles a single request to the OrthancApiClient.
+  // Once the response is ready, it will emit a message to the responseObserver
+  // the responseObserver must handle only that message (and not all messages from the OrthancApiClient)
+  class OrthancApiClient::BaseRequest:
+//      public IObserver,
+      public IObservable,
+      public Orthanc::IDynamicObject
+  {
+  public:
+    std::string               uri_;
+    OrthancApiClient&         orthanc_;
+    MessageType               messageToEmitWhenResponseReady_;
+    OrthancApiClient::Mode    mode_;
+
+  public:
+    BaseRequest(
+        OrthancApiClient& orthanc,
+                   IObserver& responseObserver,
+                   const std::string& uri,
+                   MessageType messageToEmitWhenResponseReady,
+                   OrthancApiClient::Mode mode)
+      :
+        //IObserver(orthanc.broker_),
+        IObservable(orthanc.broker_),
+        uri_(uri),
+        orthanc_(orthanc),
+        messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady),
+        mode_(mode)
+    {
+      // this object will emit only a single message, the one the final responseObserver is expecting
+      DeclareEmittableMessage(messageToEmitWhenResponseReady);
+
+//      // this object is observing the OrthancApi so it must handle all messages
+//      DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+//      DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+
+      //orthanc_.RegisterObserver(*this);
+      this->RegisterObserver(responseObserver);
+    }
+    virtual ~BaseRequest() {}
+
+//    // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver
+//    virtual void HandleMessage(IObservable& from, const IMessage& message)
+//    {
+//      switch (message.GetType())
+//      {
+//        case MessageType_OrthancApi_InternalGetJsonResponseReady:
+//      {
+//        const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message);
+//        EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_));
+//        orthanc_.ReleaseRequest(messageReceived.request_);
+//      }; break;
+//      default:
+//        throw MessageNotDeclaredException(message.GetType());
+//      }
+//    }
+
+  };
+
+
+  class OrthancApiClient::WebCallback : public IWebService::ICallback
+  {
+  private:
+    OrthancApiClient&  that_;
+
+  public:
+    WebCallback(MessageBroker& broker, OrthancApiClient&  that) :
+      IWebService::ICallback(broker),
+      that_(that)
+    {
+    }
+
+    virtual void OnHttpRequestSuccess(const std::string& uri,
+                                      const void* answer,
+                                      size_t answerSize,
+                                      Orthanc::IDynamicObject* payload)
+    {
+      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
+
+      switch (request->mode_)
+      {
+      case OrthancApiClient::Mode_GetJson:
+      {
+        Json::Value response;
+        if (MessagingToolbox::ParseJson(response, answer, answerSize))
+        {
+          request->EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(request->messageToEmitWhenResponseReady_, request->uri_, response));
+        }
+        else
+        {
+//          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+//          that_.EmitMessage(msg);
+        }
+      };  break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      that_.ReleaseRequest(request);
+    }
+
+    virtual void OnHttpRequestError(const std::string& uri,
+                                    Orthanc::IDynamicObject* payload)
+    {
+      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
+
+      switch (request->mode_)
+      {
+      case OrthancApiClient::Mode_GetJson:
+      {
+//        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+//        that_.EmitMessage(msg);
+          // TODO: the request shall send an error message
+      };  break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      that_.ReleaseRequest(request);
+    }
+  };
+
+  OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc)
+    : IObservable(broker),
+      orthanc_(orthanc),
+      webCallback_(new OrthancApiClient::WebCallback(broker, *this))
+  {
+    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+  }
+
+  void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady)
+  {
+    OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this,
+                                                                               responseObserver,
+                                                                               uri,
+                                                                               messageToEmitWhenResponseReady,
+                                                                               OrthancApiClient::Mode_GetJson);
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request);
+    requestsInProgress_.insert(request);
+  }
+
+  void OrthancApiClient::ReleaseRequest(BaseRequest* request)
+  {
+    requestsInProgress_.erase(request);
+    delete request;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancApiClient.h	Thu Aug 30 16:56:08 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 <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+#include "IWebService.h"
+#include "../Messages/IObservable.h"
+
+
+namespace OrthancStone
+{
+  class OrthancApiClient:
+      public IObservable
+  {
+  protected:
+    class BaseRequest;
+    class GetJsonRequest;
+
+    struct InternalGetJsonResponseReadyMessage;
+    struct InternalGetJsonResponseErrorMessage;
+
+  public:
+    struct GetJsonResponseReadyMessage : public IMessage
+    {
+      Json::Value   response_;
+      std::string   uri_;
+
+      GetJsonResponseReadyMessage(MessageType messageType,
+                                  const std::string& uri,
+                                  const Json::Value& response)
+        : IMessage(messageType),
+          response_(response),
+          uri_(uri)
+      {
+      }
+    };
+
+  public:
+
+    enum Mode
+    {
+      Mode_GetJson
+    };
+
+  protected:
+    IWebService&                      orthanc_;
+    class WebCallback;
+    boost::shared_ptr<WebCallback>    webCallback_;  // This is a PImpl pattern
+    std::set<BaseRequest*>            requestsInProgress_;
+
+//    int ScheduleGetJsonRequest(const std::string& uri);
+
+    void ReleaseRequest(BaseRequest* request);
+
+  public:
+    OrthancApiClient(MessageBroker& broker,
+                        IWebService& orthanc);
+    virtual ~OrthancApiClient() {}
+
+    // schedule a GET request expecting a JSON request.
+    // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call
+    void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady);
+
+    void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);}
+    void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);}
+    void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);}
+
+  };
+}
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Thu Aug 30 16:56:08 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,110 +157,129 @@
       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_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+        that_.state_ = State_Error;
+        break;
+        
+      case Mode_LoadImage:
+      {
+        OrthancSlicesLoader::SliceImageErrorMessage msg(operation->GetSliceIndex(),
+                                   operation->GetSlice(),
+                                   operation->GetQuality());
+        that_.EmitMessage(msg);
+      }; break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
   };
-
+  
+  void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    // forward messages to its own observers
+    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
+  }
 
   
   void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
-                                                    std::auto_ptr<Orthanc::ImageAccessor>& image) const
+                                                    std::auto_ptr<Orthanc::ImageAccessor>& image)
   {
     if (image.get() == NULL)
     {
@@ -267,19 +287,19 @@
     }
     else
     {
-      userCallback_.NotifySliceImageReady
-        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      OrthancSlicesLoader::SliceImageReadyMessage msg(operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      EmitMessage(msg);
     }
   }
-
+  
   
-  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
+  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation)
   {
-    userCallback_.NotifySliceImageError
-      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    OrthancSlicesLoader::SliceImageErrorMessage msg(operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    EmitMessage(msg);
   }
-
-
+  
+  
   void OrthancSlicesLoader::SortAndFinalizeSlices()
   {
     bool ok = false;
@@ -295,21 +315,21 @@
         ok = true;
       }
     }
-
+    
     state_ = State_GeometryReady;
-
+    
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      userCallback_.NotifyGeometryReady(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      userCallback_.NotifyGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
     }
   }
-
+  
   
   void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
                                                 size_t size)
@@ -318,18 +338,18 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       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 +358,7 @@
       {
         frames = 1;
       }
-
+      
       for (unsigned int frame = 0; frame < frames; frame++)
       {
         std::auto_ptr<Slice> slice(new Slice);
@@ -352,11 +372,11 @@
         }
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
                                                   const void* answer,
                                                   size_t size)
@@ -365,15 +385,15 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       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 +401,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 +412,15 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        userCallback_.NotifyGeometryError(*this);
+        EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
         return;
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
                                                unsigned int frame,
                                                const void* answer,
@@ -410,33 +430,74 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       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;
+      LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      userCallback_.NotifyGeometryReady(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      userCallback_.NotifyGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
     }
   }
-
+  
+  
+  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 +505,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 +520,7 @@
       NotifySliceImageError(operation);
       return;
     }
-      
+
     if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
         Orthanc::PixelFormat_SignedGrayscale16)
     {
@@ -475,9 +536,9 @@
     }
 
     NotifySliceImageSuccess(operation, image);
-  } 
+  }
 
-  
+
   void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
                                                 const void* answer,
                                                 size_t size)
@@ -491,7 +552,7 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     Json::Value& info = encoded["Orthanc"];
     if (!info.isMember("PixelData") ||
         !info.isMember("Stretched") ||
@@ -504,30 +565,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 +600,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 +611,7 @@
         NotifySliceImageError(operation);
         return;
       }
-
+      
       if (isSigned || isStretched)
       {
         NotifySliceImageError(operation);
@@ -562,13 +623,13 @@
         return;
       }
     }
-
+    
     if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
     {
       NotifySliceImageError(operation);
       return;
     }
-
+    
     if (!isStretched)
     {
       if (expectedFormat != reader->GetFormat())
@@ -582,10 +643,10 @@
         return;
       }
     }
-
+    
     int32_t stretchLow = 0;
     int32_t stretchHigh = 0;
-
+    
     if (!info.isMember("StretchLow") ||
         !info.isMember("StretchHigh") ||
         info["StretchLow"].type() != Json::intValue ||
@@ -594,10 +655,10 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     stretchLow = info["StretchLow"].asInt();
     stretchHigh = info["StretchHigh"].asInt();
-
+    
     if (stretchLow < -32768 ||
         stretchHigh > 65535 ||
         (stretchLow < 0 && stretchHigh > 32767))
@@ -606,29 +667,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 +704,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 +736,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 +748,7 @@
           *p = le32toh(*p);
         }
       }
-
+      
       NotifySliceImageSuccess(operation, image);
     }
     else if (info.GetBitsAllocated() == 16 &&
@@ -700,30 +760,36 @@
              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)),
-    userCallback_(callback),
+    IObservable(broker),
+    webCallback_(new WebCallback(broker, *this)),
+    //userCallback_(callback),
     orthanc_(orthanc),
     state_(State_Initialization)
   {
+    DeclareEmittableMessage(MessageType_SliceLoader_GeometryReady);
+    DeclareEmittableMessage(MessageType_SliceLoader_GeometryError);
+    DeclareEmittableMessage(MessageType_SliceLoader_ImageError);
+    DeclareEmittableMessage(MessageType_SliceLoader_ImageReady);
   }
-
+  
   
   void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
   {
@@ -735,11 +801,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 +815,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 +837,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 +865,11 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.GetSlice(index);
   }
   
-
+  
   bool OrthancSlicesLoader::LookupSlice(size_t& index,
                                         const CoordinateSystem3D& plane) const
   {
@@ -811,76 +877,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 +987,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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Thu Aug 30 16:56:08 2018 +0200
@@ -24,35 +24,49 @@
 #include "IWebService.h"
 #include "SlicesSorter.h"
 #include "../StoneEnumerations.h"
-
+#include "../Messages/IObservable.h"
 #include <boost/shared_ptr.hpp>
 
 namespace OrthancStone
 {
-  class OrthancSlicesLoader : public boost::noncopyable
+  class OrthancSlicesLoader : public IObservable
   {
   public:
-    class ICallback : public boost::noncopyable
+    struct SliceImageReadyMessage : public IMessage
     {
-    public:
-      virtual ~ICallback()
+      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_SliceLoader_ImageReady),
+          sliceIndex_(sliceIndex),
+          slice_(slice),
+          image_(image),
+          effectiveQuality_(effectiveQuality)
       {
       }
+    };
 
-      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
-
-      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
+    struct SliceImageErrorMessage : public IMessage
+    {
+      const Slice& slice_;
+      unsigned int sliceIndex_;
+      SliceImageQuality effectiveQuality_;
 
-      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice,
-                                         std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                         SliceImageQuality effectiveQuality) = 0;
-
-      virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice,
-                                         SliceImageQuality quality) = 0;
+      SliceImageErrorMessage(unsigned int sliceIndex,
+                        const Slice& slice,
+                        SliceImageQuality effectiveQuality)
+        : IMessage(MessageType_SliceLoader_ImageError),
+          slice_(slice),
+          sliceIndex_(sliceIndex),
+          effectiveQuality_(effectiveQuality)
+      {
+      }
     };
     
   private:
@@ -70,7 +84,8 @@
       Mode_InstanceGeometry,
       Mode_FrameGeometry,
       Mode_LoadImage,
-      Mode_LoadRawImage
+      Mode_LoadRawImage,
+      Mode_LoadDicomFile
     };
 
     class Operation;
@@ -78,15 +93,14 @@
 
     boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
 
-    ICallback&    userCallback_;
     IWebService&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
 
     void NotifySliceImageSuccess(const Operation& operation,
-                                 std::auto_ptr<Orthanc::ImageAccessor>& image) const;
+                                 std::auto_ptr<Orthanc::ImageAccessor>& image);
   
-    void NotifySliceImageError(const Operation& operation) const;
+    void NotifySliceImageError(const Operation& operation);
     
     void ParseSeriesGeometry(const void* answer,
                              size_t size);
@@ -104,6 +118,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 +132,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 +143,8 @@
     void SortAndFinalizeSlices();
     
   public:
-    OrthancSlicesLoader(ICallback& callback,
+    OrthancSlicesLoader(MessageBroker& broker,
+                        //ISliceLoaderObserver& callback,
                         IWebService& orthanc);
 
     void ScheduleLoadSeries(const std::string& seriesId);
@@ -143,5 +165,7 @@
 
     void ScheduleLoadSliceImage(size_t index,
                                 SliceImageQuality requestedQuality);
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
   };
 }
--- a/Framework/Viewport/IMouseTracker.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Viewport/IMouseTracker.h	Thu Aug 30 16:56:08 2018 +0200
@@ -25,6 +25,9 @@
 
 namespace OrthancStone
 {
+  // this is tracking a mouse in screen coordinates/pixels unlike
+  // the IWorldSceneMouseTracker that is tracking a mouse
+  // in scene coordinates/mm.
   class IMouseTracker : public boost::noncopyable
   {
   public:
--- a/Framework/Viewport/IViewport.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Viewport/IViewport.h	Thu Aug 30 16:56:08 2018 +0200
@@ -40,7 +40,7 @@
       {
       }
 
-      virtual void NotifyChange(const IViewport& scene) = 0;
+      virtual void OnViewportContentChanged(const IViewport& scene) = 0;
     };
 
     virtual ~IViewport()
@@ -86,6 +86,6 @@
     virtual void UpdateContent() = 0;
 
     // Should only be called from IWidget
-    virtual void NotifyChange(const IWidget& widget) = 0;
+    virtual void NotifyContentChanged(const IWidget& widget) = 0;
   };
 }
--- a/Framework/Viewport/WidgetViewport.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Viewport/WidgetViewport.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -64,7 +64,7 @@
     }
 
     mouseTracker_.reset(NULL);
-      
+
     centralWidget_.reset(widget);
     centralWidget_->SetViewport(*this);
 
@@ -74,16 +74,16 @@
     }
 
     backgroundChanged_ = true;
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
 
     return *widget;
   }
 
 
-  void WidgetViewport::NotifyChange(const IWidget& widget)
+  void WidgetViewport::NotifyContentChanged(const IWidget& widget)
   {
     backgroundChanged_ = true;
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
   }
 
 
@@ -97,7 +97,7 @@
       centralWidget_->SetSize(width, height);
     }
 
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
   }
 
 
@@ -154,7 +154,7 @@
       mouseTracker_.reset(NULL);
     }      
 
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
   }
 
 
@@ -164,7 +164,7 @@
     {
       mouseTracker_->MouseUp();
       mouseTracker_.reset(NULL);
-      observers_.Apply(*this, &IObserver::NotifyChange);
+      observers_.Apply(*this, &IObserver::OnViewportContentChanged);
     }
   }
 
@@ -195,7 +195,7 @@
     if (repaint)
     {
       // The scene must be repainted, notify the observers
-      observers_.Apply(*this, &IObserver::NotifyChange);
+      observers_.Apply(*this, &IObserver::OnViewportContentChanged);
     }
   }
 
@@ -203,7 +203,7 @@
   void WidgetViewport::MouseEnter()
   {
     isMouseOver_ = true;
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
   }
 
 
@@ -217,7 +217,7 @@
       mouseTracker_.reset(NULL);
     }
 
-    observers_.Apply(*this, &IObserver::NotifyChange);
+    observers_.Apply(*this, &IObserver::OnViewportContentChanged);
   }
 
 
--- a/Framework/Viewport/WidgetViewport.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Viewport/WidgetViewport.h	Thu Aug 30 16:56:08 2018 +0200
@@ -51,7 +51,7 @@
 
     IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
 
-    virtual void NotifyChange(const IWidget& widget);
+    virtual void NotifyContentChanged(const IWidget& widget);
 
     virtual void Register(IObserver& observer)
     {
--- a/Framework/Volumes/StructureSetLoader.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.h	Thu Aug 30 16:56:08 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/CairoWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/CairoWidget.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -32,6 +32,10 @@
     return true;
   }
 
+  CairoWidget::CairoWidget(const std::string& name)
+    : WidgetBase(name)
+  {
+  }
 
   void CairoWidget::SetSize(unsigned int width,
                             unsigned int height)
--- a/Framework/Widgets/CairoWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/CairoWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -38,6 +38,8 @@
                                       int y) = 0;
     
   public:
+    CairoWidget(const std::string& name);
+
     virtual void SetSize(unsigned int width,
                          unsigned int height);
 
--- a/Framework/Widgets/EmptyWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/EmptyWidget.cpp	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/EmptyWidget.h	Thu Aug 30 16:56:08 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 NotifyContentChanged()
+    {
+    }
 
-      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/IWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/IWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -73,6 +73,6 @@
 
     // Subclasses can call this method to signal the display of the
     // widget must be refreshed
-    virtual void NotifyChange() = 0;
+    virtual void NotifyContentChanged() = 0;
   };
 }
--- a/Framework/Widgets/IWorldSceneInteractor.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/IWorldSceneInteractor.h	Thu Aug 30 16:56:08 2018 +0200
@@ -41,6 +41,7 @@
     virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
                                                         const ViewportGeometry& view,
                                                         MouseButton button,
+                                                        KeyboardModifiers modifiers,
                                                         double x,
                                                         double y,
                                                         IStatusBar* statusBar) = 0;
--- a/Framework/Widgets/IWorldSceneMouseTracker.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/IWorldSceneMouseTracker.h	Thu Aug 30 16:56:08 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,16 +25,20 @@
 
 namespace OrthancStone
 {
+
+  // this is tracking a mouse in scene coordinates/mm unlike
+  // the IMouseTracker that is tracking a mouse
+  // in screen coordinates/pixels.
   class IWorldSceneMouseTracker : public boost::noncopyable
   {
   public:
     virtual ~IWorldSceneMouseTracker()
     {
     }
-    
+
     virtual void Render(CairoContext& context,
                         double zoom) = 0;
-    
+
     virtual void MouseUp() = 0;
 
     virtual void MouseMove(double x,
--- a/Framework/Widgets/LayerWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Thu Aug 30 16:56:08 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;
@@ -341,7 +341,7 @@
         currentScene_->ContainsPlane(slice))
     {
       currentScene_->SetLayer(index, tmp.release());
-      NotifyChange();
+      NotifyContentChanged();
     }
     else if (pendingScene_.get() != NULL &&
              pendingScene_->ContainsPlane(slice))
@@ -353,15 +353,27 @@
           pendingScene_->IsComplete())
       {
         currentScene_ = pendingScene_;
-        NotifyChange();
+        NotifyContentChanged();
       }
     }
   }
 
   
-  LayerWidget::LayerWidget() :
+  LayerWidget::LayerWidget(MessageBroker& broker, const std::string& name) :
+    WorldSceneWidget(name),
+    IObserver(broker),
+    IObservable(broker),
     started_(false)
   {
+    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
+    DeclareHandledMessage(MessageType_LayerSource_ContentChanged);
+    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
+    DeclareHandledMessage(MessageType_LayerSource_SliceChanged);
+    DeclareHandledMessage(MessageType_LayerSource_GeometryError);
+
+    DeclareEmittableMessage(MessageType_Widget_GeometryChanged);
+    DeclareEmittableMessage(MessageType_Widget_ContentChanged);
+
     SetBackgroundCleared(true);
   }
   
@@ -388,14 +400,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())
@@ -429,7 +463,7 @@
       pendingScene_->SetLayerStyle(layer, style);
     }
 
-    NotifyChange();
+    NotifyContentChanged();
   }
   
 
@@ -457,26 +491,47 @@
     }
   }
 
+  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    switch (message.GetType()) {
+    case MessageType_LayerSource_GeometryReady:
+      OnGeometryReady(dynamic_cast<const ILayerSource&>(from));
+      break;
+    case MessageType_LayerSource_GeometryError:
+      LOG(ERROR) << "Cannot get geometry";
+      break;
+    case MessageType_LayerSource_ContentChanged:
+      OnContentChanged(dynamic_cast<const ILayerSource&>(from));
+      break;
+    case MessageType_LayerSource_SliceChanged:
+      OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
+      break;
+    case MessageType_LayerSource_LayerReady:
+    {
+      const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
+      OnLayerReady(layerReadyMessage.layer_,
+                   dynamic_cast<const 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))
     {
-      LOG(INFO) << "Geometry ready for layer " << i;
+      LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
 
       changedLayers_[i] = true;
       //layers_[i]->ScheduleLayerCreation(slice_);
     }
+    EmitMessage(IMessage(MessageType_Widget_GeometryChanged));
   }
   
-
-  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 +558,7 @@
   }
 
 
-  void LayerWidget::NotifyContentChange(const ILayerSource& source)
+  void LayerWidget::OnContentChanged(const ILayerSource& source)
   {
     size_t index;
     if (LookupLayer(index, source))
@@ -513,8 +568,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 +582,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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Thu Aug 30 16:56:08 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/>.
  **/
@@ -24,14 +24,16 @@
 #include "WorldSceneWidget.h"
 #include "../Layers/ILayerSource.h"
 #include "../Toolbox/Extent2D.h"
+#include "../../Framework/Messages/IObserver.h"
 
 #include <map>
 
 namespace OrthancStone
 {
   class LayerWidget :
-    public WorldSceneWidget,
-    private ILayerSource::IObserver
+      public WorldSceneWidget,
+      public IObserver,
+      public IObservable
   {
   private:
     class Scene;
@@ -53,25 +55,28 @@
     void GetLayerExtent(Extent2D& extent,
                         ILayerSource& source) const;
 
-    virtual void NotifyGeometryReady(const ILayerSource& source);
+    void OnGeometryReady(const ILayerSource& source);
 
-    virtual void NotifyGeometryError(const ILayerSource& source);
-
-    virtual void NotifyContentChange(const ILayerSource& source);
+    virtual void OnContentChanged(const ILayerSource& source);
 
-    virtual void NotifySliceChange(const ILayerSource& source,
-                                   const Slice& slice);
+    virtual void OnSliceChanged(const ILayerSource& source,
+                                const Slice& slice);
 
-    virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                  const ILayerSource& source,
-                                  const CoordinateSystem3D& slice,
-                                  bool isError);
+    virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+                              const ILayerSource& source,
+                              const CoordinateSystem3D& slice,
+                              bool isError);
+
 
     void ResetChangedLayers();
 
   public:
+    LayerWidget(MessageBroker& broker, const std::string& name);
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
     virtual Extent2D GetSceneExtent();
- 
+
   protected:
     virtual bool RenderScene(CairoContext& context,
                              const ViewportGeometry& view);
@@ -87,12 +92,12 @@
     void InvalidateLayer(size_t layer);
     
   public:
-    LayerWidget();
-
     virtual ~LayerWidget();
 
     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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/LayoutWidget.cpp	Thu Aug 30 16:56:08 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();
       }
@@ -263,11 +261,12 @@
       }
     }
 
-    NotifyChange(*this);
+    NotifyContentChanged(*this);
   }
 
 
-  LayoutWidget::LayoutWidget() :
+  LayoutWidget::LayoutWidget(const std::string& name) :
+    WidgetBase(name),
     isHorizontal_(true),
     width_(0),
     height_(0),
@@ -298,10 +297,10 @@
   }
   
 
-  void LayoutWidget::NotifyChange(const IWidget& widget)
+  void LayoutWidget::NotifyContentChanged(const IWidget& widget)
   {
     // One of the children has changed
-    WidgetBase::NotifyChange();
+    WidgetBase::NotifyContentChanged();
   }
 
 
--- a/Framework/Widgets/LayoutWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/LayoutWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -49,13 +49,13 @@
     void ComputeChildrenExtents();
 
   public:
-    LayoutWidget();
+    LayoutWidget(const std::string& name);
 
     virtual ~LayoutWidget();
 
     virtual void SetDefaultView();
 
-    virtual void NotifyChange(const IWidget& widget);
+    virtual void NotifyContentChanged(const IWidget& widget);
 
     void SetHorizontal();
 
--- a/Framework/Widgets/TestCairoWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/TestCairoWidget.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -36,7 +36,7 @@
         value_ = 1;
       }
 
-      NotifyChange();
+      NotifyContentChanged();
     }
 
 
@@ -77,7 +77,8 @@
     }
 
 
-    TestCairoWidget::TestCairoWidget(bool animate) :
+    TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) :
+      CairoWidget(name),
       width_(0),
       height_(0),
       value_(1),
--- a/Framework/Widgets/TestCairoWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/TestCairoWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -43,7 +43,7 @@
                                         int y);
 
     public:
-      TestCairoWidget(bool animate);
+      TestCairoWidget(const std::string& name, bool animate);
 
       virtual void SetSize(unsigned int width, 
                            unsigned int height);
--- a/Framework/Widgets/TestWorldSceneWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/TestWorldSceneWidget.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -34,6 +34,7 @@
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
+                                                          KeyboardModifiers modifiers,
                                                           double x,
                                                           double y,
                                                           IStatusBar* statusBar)
@@ -110,7 +111,8 @@
     }
 
 
-    TestWorldSceneWidget::TestWorldSceneWidget(bool animate) :
+    TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) :
+      WorldSceneWidget(name),
       interactor_(new Interactor),
       animate_(animate),
       count_(0)
@@ -130,7 +132,7 @@
       if (animate_)
       {
         count_++;
-        NotifyChange();
+        NotifyContentChanged();
       }
       else
       {
--- a/Framework/Widgets/TestWorldSceneWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/TestWorldSceneWidget.h	Thu Aug 30 16:56:08 2018 +0200
@@ -43,7 +43,7 @@
                                const ViewportGeometry& view);
 
     public:
-      TestWorldSceneWidget(bool animate);
+      TestWorldSceneWidget(const std::string& name, bool animate);
 
       virtual Extent2D GetSceneExtent();
 
--- a/Framework/Widgets/WidgetBase.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/WidgetBase.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -27,16 +27,16 @@
 
 namespace OrthancStone
 {
-  void WidgetBase::NotifyChange()
+  void WidgetBase::NotifyContentChanged()
   {
     if (parent_ != NULL)
     {
-      parent_->NotifyChange();
+      parent_->NotifyContentChanged();
     }
 
     if (viewport_ != NULL)
     {
-      viewport_->NotifyChange(*this);
+      viewport_->NotifyContentChanged(*this);
     }
   }
 
@@ -101,12 +101,13 @@
   }
 
 
-  WidgetBase::WidgetBase() :
+  WidgetBase::WidgetBase(const std::string& name) :
     parent_(NULL),
     viewport_(NULL),
     statusBar_(NULL),
     backgroundCleared_(false),
-    transmitMouseOver_(false)
+    transmitMouseOver_(false),
+    name_(name)
   {
     backgroundColor_[0] = 0;
     backgroundColor_[1] = 0;
--- a/Framework/Widgets/WidgetBase.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/WidgetBase.h	Thu Aug 30 16:56:08 2018 +0200
@@ -36,6 +36,7 @@
     bool         backgroundCleared_;
     uint8_t      backgroundColor_[3];
     bool         transmitMouseOver_;
+    std::string  name_;
 
   protected:
     void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const;
@@ -52,7 +53,7 @@
     }
 
   public:
-    WidgetBase();
+    WidgetBase(const std::string& name);
 
     virtual void SetDefaultView()
     {
@@ -104,6 +105,12 @@
       return transmitMouseOver_;
     }
 
-    virtual void NotifyChange();
+    virtual void NotifyContentChanged();
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+
   };
 }
--- a/Framework/Widgets/WorldSceneWidget.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Thu Aug 30 16:56:08 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/>.
  **/
@@ -59,12 +59,16 @@
   };
 
 
+  // this is an adapter between a IWorldSceneMouseTracker
+  // that is tracking a mouse in scene coordinates/mm and
+  // an IMouseTracker that is tracking a mouse
+  // in screen coordinates/pixels.
   class WorldSceneWidget::SceneMouseTracker : public IMouseTracker
   {
   private:
     ViewportGeometry                       view_;
     std::auto_ptr<IWorldSceneMouseTracker>  tracker_;
-      
+
   public:
     SceneMouseTracker(const ViewportGeometry& view,
                       IWorldSceneMouseTracker* tracker) :
@@ -77,17 +81,17 @@
     virtual void Render(Orthanc::ImageAccessor& target)
     {
       CairoSurface surface(target);
-      CairoContext context(surface); 
+      CairoContext context(surface);
       view_.ApplyTransform(context);
       tracker_->Render(context, view_.GetZoom());
     }
 
-    virtual void MouseUp() 
+    virtual void MouseUp()
     {
       tracker_->MouseUp();
     }
 
-    virtual void MouseMove(int x, 
+    virtual void MouseMove(int x,
                            int y)
     {
       double sceneX, sceneY;
@@ -97,121 +101,80 @@
   };
 
 
-  class WorldSceneWidget::PanMouseTracker : public IMouseTracker
+  WorldSceneWidget::PanMouseTracker::PanMouseTracker(WorldSceneWidget& that,
+                                                     int x,
+                                                     int y) :
+    that_(that),
+    downX_(x),
+    downY_(y)
   {
-  private:
-    WorldSceneWidget&  that_;  
-    double             previousPanX_;
-    double             previousPanY_;
-    double             downX_;
-    double             downY_;
+    that_.view_.GetPan(previousPanX_, previousPanY_);
+  }
+
+  void WorldSceneWidget::PanMouseTracker::MouseMove(int x, int y)
+  {
+    that_.view_.SetPan(previousPanX_ + x - downX_,
+                       previousPanY_ + y - downY_);
+
+    that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
+  }
 
-  public:
-    PanMouseTracker(WorldSceneWidget& that,
-                    int x,
-                    int y) :
-      that_(that),
-      downX_(x),
-      downY_(y)
+  WorldSceneWidget::ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget&  that,
+                                                       int x,
+                                                       int y) :
+    that_(that),
+    downX_(x),
+    downY_(y)
+  {
+    oldZoom_ = that_.view_.GetZoom();
+    MapMouseToScene(centerX_, centerY_, that_.view_, downX_, downY_);
+  }
+
+  void WorldSceneWidget::ZoomMouseTracker::MouseMove(int x,
+                                                     int y)
+  {
+    static const double MIN_ZOOM = -4;
+    static const double MAX_ZOOM = 4;
+
+    if (that_.view_.GetDisplayHeight() <= 3)
     {
-      that_.view_.GetPan(previousPanX_, previousPanY_);
-    }
-
-    virtual void Render(Orthanc::ImageAccessor& surface)
-    {
-    }
-
-    virtual void MouseUp() 
-    {
+      return;   // Cannot zoom on such a small image
     }
 
-    virtual void MouseMove(int x, 
-                           int y)
-    {
-      that_.view_.SetPan(previousPanX_ + x - downX_,
-                         previousPanY_ + y - downY_);
-
-      that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
-    }
-  };
-
+    double dy = (static_cast<double>(y - downY_) /
+                 static_cast<double>(that_.view_.GetDisplayHeight() - 1)); // In the range [-1,1]
+    double z;
 
-  class WorldSceneWidget::ZoomMouseTracker : public IMouseTracker
-  {
-  private:
-    WorldSceneWidget&  that_;  
-    int                downX_;
-    int                downY_;
-    double             centerX_;
-    double             centerY_;
-    double             oldZoom_;
-
-  public:
-    ZoomMouseTracker(WorldSceneWidget&  that,
-                     int x,
-                     int y) :
-      that_(that),
-      downX_(x),
-      downY_(y)
+    // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+    if (dy < -1.0)
     {
-      oldZoom_ = that_.view_.GetZoom();
-      MapMouseToScene(centerX_, centerY_, that_.view_, downX_, downY_);
+      z = MIN_ZOOM;
     }
-
-    virtual void Render(Orthanc::ImageAccessor& surface)
+    else if (dy > 1.0)
     {
+      z = MAX_ZOOM;
     }
-
-    virtual void MouseUp() 
+    else
     {
+      z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
     }
 
-    virtual void MouseMove(int x, 
-                           int y)
-    {
-      static const double MIN_ZOOM = -4;
-      static const double MAX_ZOOM = 4;
+    z = pow(2.0, z);
 
-      if (that_.view_.GetDisplayHeight() <= 3)
-      {
-        return;   // Cannot zoom on such a small image
-      }
-
-      double dy = (static_cast<double>(y - downY_) / 
-                   static_cast<double>(that_.view_.GetDisplayHeight() - 1)); // In the range [-1,1]
-      double z;
+    that_.view_.SetZoom(oldZoom_ * z);
 
-      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
-      if (dy < -1.0)
-      {
-        z = MIN_ZOOM;
-      }
-      else if (dy > 1.0)
-      {
-        z = MAX_ZOOM;
-      }
-      else
-      {
-        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
-      }
-
-      z = pow(2.0, z);
+    // Correct the pan so that the original click point is kept at
+    // the same location on the display
+    double panX, panY;
+    that_.view_.GetPan(panX, panY);
 
-      that_.view_.SetZoom(oldZoom_ * z);
-
-      // Correct the pan so that the original click point is kept at
-      // the same location on the display
-      double panX, panY;
-      that_.view_.GetPan(panX, panY);
+    int tx, ty;
+    that_.view_.MapSceneToDisplay(tx, ty, centerX_, centerY_);
+    that_.view_.SetPan(panX + static_cast<double>(downX_ - tx),
+                       panY + static_cast<double>(downY_ - ty));
 
-      int tx, ty;
-      that_.view_.MapSceneToDisplay(tx, ty, centerX_, centerY_);
-      that_.view_.SetPan(panX + static_cast<double>(downX_ - tx),
-                               panY + static_cast<double>(downY_ - ty));
-
-      that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
-    }
-  };
+    that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
+  }
 
 
   bool WorldSceneWidget::RenderCairo(CairoContext& context)
@@ -272,7 +235,7 @@
     SetSceneExtent(view_);
     view_.SetDefaultView();
 
-    NotifyChange();
+    NotifyContentChanged();
 
     observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_);
   }
@@ -282,7 +245,7 @@
   {
     view_ = view;
 
-    NotifyChange();
+    NotifyContentChanged();
 
     observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_);
   }
@@ -302,24 +265,25 @@
     double sceneX, sceneY;
     MapMouseToScene(sceneX, sceneY, view_, x, y);
 
+    // asks the Widget Interactor to provide a mouse tracker
     std::auto_ptr<IWorldSceneMouseTracker> tracker
-      (CreateMouseSceneTracker(view_, button, sceneX, sceneY, modifiers));
+        (CreateMouseSceneTracker(view_, button, sceneX, sceneY, modifiers));
 
     if (tracker.get() != NULL)
     {
       return new SceneMouseTracker(view_, tracker.release());
     }
-
+    //TODO: allow Interactor to create Pan & Zoom
     switch (button)
     {
-      case MouseButton_Middle:
-        return new PanMouseTracker(*this, x, y);
+    case MouseButton_Middle:
+      return new PanMouseTracker(*this, x, y);
 
-      case MouseButton_Right:
-        return new ZoomMouseTracker(*this, x, y);
+    case MouseButton_Right:
+      return new ZoomMouseTracker(*this, x, y);
 
-      default:
-        return NULL;
+    default:
+      return NULL;
     }
   }
 
@@ -343,7 +307,7 @@
   {
     if (interactor_)
     {
-      return interactor_->CreateMouseTracker(*this, view, button, x, y, GetStatusBar());
+      return interactor_->CreateMouseTracker(*this, view, button, modifiers, x, y, GetStatusBar());
     }
     else
     {
@@ -355,7 +319,7 @@
   void WorldSceneWidget::MouseWheel(MouseWheelDirection direction,
                                     int x,
                                     int y,
-                                    KeyboardModifiers modifiers) 
+                                    KeyboardModifiers modifiers)
   {
     if (interactor_)
     {
--- a/Framework/Widgets/WorldSceneWidget.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/Widgets/WorldSceneWidget.h	Thu Aug 30 16:56:08 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/>.
  **/
@@ -46,12 +46,49 @@
                                     const ViewportGeometry& view) = 0;
     };
 
+    class PanMouseTracker : public IMouseTracker
+    {
+    private:
+      WorldSceneWidget&  that_;
+      double             previousPanX_;
+      double             previousPanY_;
+      double             downX_;
+      double             downY_;
+
+    public:
+      PanMouseTracker(WorldSceneWidget& that, int x, int y);
+
+      virtual void Render(Orthanc::ImageAccessor& surface) {}
+
+      virtual void MouseUp() {}
+
+      virtual void MouseMove(int x, int y);
+    };
+
+    class ZoomMouseTracker : public IMouseTracker
+    {
+    private:
+      WorldSceneWidget&  that_;
+      int                downX_;
+      int                downY_;
+      double             centerX_;
+      double             centerY_;
+      double             oldZoom_;
+
+    public:
+      ZoomMouseTracker(WorldSceneWidget&  that, int x, int y);
+
+      void Render(Orthanc::ImageAccessor& surface) {}
+
+      virtual void MouseUp() {}
+
+      virtual void MouseMove(int x, int y);
+    };
+
   private:
     struct SizeChangeFunctor;
 
     class SceneMouseTracker;
-    class PanMouseTracker;
-    class ZoomMouseTracker;
 
     typedef ObserversRegistry<WorldSceneWidget, IWorldObserver>  Observers;
 
@@ -75,7 +112,8 @@
     void SetSceneExtent(ViewportGeometry& geometry);
 
   public:
-    WorldSceneWidget() :
+    WorldSceneWidget(const std::string& name) :
+      CairoWidget(name),
       interactor_(NULL)
     {
     }
--- a/Framework/dev.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Framework/dev.h	Thu Aug 30 16:56:08 2018 +0200
@@ -43,7 +43,7 @@
   // TODO: Handle errors while loading
   class OrthancVolumeImage : 
     public SlicedVolumeBase,
-    private OrthancSlicesLoader::ICallback
+    public OrthancStone::IObserver
   { 
   private:
     OrthancSlicesLoader           loader_;
@@ -106,7 +106,7 @@
     }
 
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
+    void OnSliceGeometryReady(const OrthancSlicesLoader& loader)
     {
       if (loader.GetSliceCount() == 0)
       {
@@ -151,15 +151,15 @@
       unsigned int width = loader.GetSlice(0).GetWidth();
       unsigned int height = loader.GetSlice(0).GetHeight();
       Orthanc::PixelFormat format = loader.GetSlice(0).GetConverter().GetExpectedPixelFormat();
-      LOG(INFO) << "Creating a volume image of size " << width << "x" << height 
+      LOG(INFO) << "Creating a volume image of size " << width << "x" << height
                 << "x" << loader.GetSliceCount() << " in " << Orthanc::EnumerationToString(format);
 
       image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount(), computeRange_));
       image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry());
-      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), 
+      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(),
                                  loader.GetSlice(0).GetPixelSpacingY(), spacingZ);
       image_->Clear();
-      
+
       downloadStack_.reset(new DownloadStack(loader.GetSliceCount()));
       pendingSlices_ = loader.GetSliceCount();
 
@@ -173,13 +173,7 @@
       SlicedVolumeBase::NotifyGeometryReady();
     }
 
-    virtual void NotifyGeometryError(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,
@@ -190,7 +184,7 @@
         Orthanc::ImageProcessing::Copy(writer.GetAccessor(), *image);
       }
 
-      SlicedVolumeBase::NotifySliceChange(sliceIndex, slice);     
+      SlicedVolumeBase::NotifySliceChange(sliceIndex, slice);
 
       if (pendingSlices_ == 1)
       {
@@ -205,22 +199,47 @@
       ScheduleSliceDownload();
     }
 
-    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       SliceImageQuality quality)
+    virtual void HandleMessage(const IObservable& from, const IMessage& message)
     {
-      LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image";
-      ScheduleSliceDownload();
+      switch (message.GetType())
+      {
+      case MessageType_SliceLoader_GeometryReady:
+        OnSliceGeometryReady(dynamic_cast<const OrthancSlicesLoader&>(from));
+      case MessageType_SliceLoader_GeometryError:
+      {
+        LOG(ERROR) << "Unable to download a volume image";
+        SlicedVolumeBase::NotifyGeometryError();
+      }; break;
+      case MessageType_SliceLoader_ImageReady:
+      {
+        const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
+        OnSliceImageReady(dynamic_cast<const OrthancSlicesLoader&>(from),
+                          msg.sliceIndex_,
+                          msg.slice_,
+                          msg.image_,
+                          msg.effectiveQuality_);
+      }; break;
+      case MessageType_SliceLoader_ImageError:
+      {
+          const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
+          LOG(ERROR) << "Cannot download slice " << msg.sliceIndex_ << " in a volume image";
+          ScheduleSliceDownload();
+      }; break;
+      default:
+        VLOG("unhandled message type" << message.GetType());
+      }
     }
 
   public:
-    OrthancVolumeImage(IWebService& orthanc,
+    OrthancVolumeImage(MessageBroker& broker,
+                       IWebService& orthanc,
                        bool computeRange) : 
-      loader_(*this, orthanc),
+      OrthancStone::IObserver(broker),
+      loader_(broker, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)
     {
+        loader_.RegisterObserver(*this);
     }
 
     void ScheduleLoadSeries(const std::string& seriesId)
@@ -576,7 +595,8 @@
 
 
   public:
-    VolumeImageSource(OrthancVolumeImage&  volume) :
+    VolumeImageSource(MessageBroker& broker, OrthancVolumeImage&  volume) :
+      LayerSourceBase(broker),
       volume_(volume)
     {
       volume_.Register(*this);
@@ -814,7 +834,8 @@
     LayerWidget&  otherPlane_;
 
   public:
-    SliceLocationSource(LayerWidget&  otherPlane) :
+    SliceLocationSource(MessageBroker& broker, LayerWidget&  otherPlane) :
+      LayerSourceBase(broker),
       otherPlane_(otherPlane)
     {
       NotifyGeometryReady();
--- a/Platforms/Generic/CMakeLists.txt	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/CMakeLists.txt	Thu Aug 30 16:56:08 2018 +0200
@@ -29,6 +29,8 @@
 
 LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options)
 
+SET(ENABLE_SDL OFF)
+SET(ENABLE_QT ON)
 SET(ORTHANC_SANDBOXED OFF)
 SET(ENABLE_CRYPTO_OPTIONS ON)
 SET(ENABLE_GOOGLE_TEST ON)
@@ -45,9 +47,20 @@
 ## Build all the sample applications
 #####################################################################
 
-macro(BuildSample Target Sample)
+if (ENABLE_QT)
+  list(APPEND APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui
+    )
+endif()
+
+macro(BuildSingeFileSample Target Header Sample)
   add_executable(${Target}
-    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainSdl.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.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 +70,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)
+BuildSingeFileSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8)
 
 
 #####################################################################
@@ -72,6 +86,7 @@
 
 add_executable(UnitTests
   ${GOOGLE_TEST_SOURCES}
+  ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
   ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
   )
 
--- a/Platforms/Generic/Oracle.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/Oracle.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -27,6 +27,7 @@
 
 #include <vector>
 #include <stdio.h>
+#include <boost/thread/mutex.hpp>
 
 namespace OrthancStone
 {
@@ -40,7 +41,6 @@
       State_Stopped
     };
 
-    boost::mutex*                  globalMutex_;
     boost::mutex                   oracleMutex_;
     State                          state_;
     std::vector<boost::thread*>    threads_;
@@ -71,23 +71,13 @@
           // Random sleeping to test
           //boost::this_thread::sleep(boost::posix_time::milliseconds(50 * (1 + rand() % 10)));
 
-          if (that->globalMutex_ != NULL)
-          {
-            boost::mutex::scoped_lock lock(*that->globalMutex_);
-            command.Commit();
-          }
-          else
-          {
-            command.Commit();
-          }
+          command.Commit();
         }
       }
     }
     
   public:
-    PImpl(boost::mutex* globalMutex,
-          unsigned int threadCount) :
-      globalMutex_(globalMutex),
+    PImpl(unsigned int threadCount) :
       state_(State_Init),
       threads_(threadCount)
     {
@@ -182,19 +172,11 @@
   };
   
 
-  Oracle::Oracle(boost::mutex& globalMutex,
-                 unsigned int threadCount) :
-    pimpl_(new PImpl(&globalMutex, threadCount))
+  Oracle::Oracle(unsigned int threadCount) :
+    pimpl_(new PImpl(threadCount))
   {
   }
 
-
-  Oracle::Oracle(unsigned int threadCount) :
-    pimpl_(new PImpl(NULL, threadCount))
-  {
-  }
-
-
   void Oracle::Start()
   {
     pimpl_->Start();
--- a/Platforms/Generic/Oracle.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/Oracle.h	Thu Aug 30 16:56:08 2018 +0200
@@ -24,7 +24,6 @@
 #include "IOracleCommand.h"
 
 #include <boost/shared_ptr.hpp>
-#include <boost/thread/mutex.hpp>
 
 namespace OrthancStone
 {
@@ -36,9 +35,6 @@
     boost::shared_ptr<PImpl>  pimpl_;
 
   public:
-    Oracle(boost::mutex& globalMutex,
-           unsigned int threadCount);
-
     Oracle(unsigned int threadCount);
 
     void Start();
--- a/Platforms/Generic/OracleWebService.h	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Thu Aug 30 16:56:08 2018 +0200
@@ -25,36 +25,55 @@
 #include "Oracle.h"
 #include "WebServiceGetCommand.h"
 #include "WebServicePostCommand.h"
+#include "../../Applications/Generic/BasicNativeApplicationContext.h"
 
 namespace OrthancStone
 {
+  // The OracleWebService performs HTTP requests in a native environment.
   class OracleWebService : public IWebService
   {
   private:
     Oracle&                        oracle_;
+    BasicNativeApplicationContext& context_;
     Orthanc::WebServiceParameters  parameters_;
 
   public:
-    OracleWebService(Oracle& oracle,
-                     const Orthanc::WebServiceParameters& parameters) : 
+    OracleWebService(MessageBroker& broker,
+                     Oracle& oracle,
+                     const Orthanc::WebServiceParameters& parameters,
+                     BasicNativeApplicationContext& context) :
+      IWebService(broker),
       oracle_(oracle),
+      context_(context),
       parameters_(parameters)
     {
     }
 
     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, context_));
     }
 
     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, context_));
+    }
+
+    void Start()
+    {
+        oracle_.Start();
+    }
+
+    void Stop()
+    {
+        oracle_.Stop();
     }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,64 @@
+/**
+ * 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 */,
+                                               BasicNativeApplicationContext& context) :
+    IObservable(broker),
+    callback_(callback),
+    parameters_(parameters),
+    uri_(uri),
+    headers_(headers),
+    payload_(payload),
+    context_(context)
+  {
+    DeclareEmittableMessage(MessageType_HttpRequestError);
+    DeclareEmittableMessage(MessageType_HttpRequestSuccess);
+    RegisterObserver(callback);
+  }
+
+
+  void WebServiceCommandBase::Commit()
+  {
+    BasicNativeApplicationContext::GlobalMutexLocker lock(context_);  // we want to make sure that, i.e, the UpdateThread is not triggered while we are updating the "model" with the result of a WebServiceCommand
+
+    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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,61 @@
+/**
+ * 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 "../../Applications/Generic/BasicNativeApplicationContext.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_;
+    BasicNativeApplicationContext&          context_;
+
+  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 */,
+                          BasicNativeApplicationContext& context);
+
+    virtual void Execute() = 0;
+
+    virtual void Commit();
+  };
+}
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -25,14 +25,14 @@
 
 namespace OrthancStone
 {
-  WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback,
+  WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker,
+                                             IWebService::ICallback& callback,
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
-                                             Orthanc::IDynamicObject* payload /* takes ownership */) :
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    payload_(payload)
+                                             const IWebService::Headers& headers,
+                                             Orthanc::IDynamicObject* payload /* takes ownership */,
+                                             BasicNativeApplicationContext& context) :
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context)
   {
   }
 
@@ -42,19 +42,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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Thu Aug 30 16:56:08 2018 +0200
@@ -21,34 +21,21 @@
 
 #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,
-                         Orthanc::IDynamicObject* payload /* takes ownership */);
+                         const IWebService::Headers& headers,
+                         Orthanc::IDynamicObject* payload /* takes ownership */,
+                         BasicNativeApplicationContext& context);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- a/Platforms/Generic/WebServicePostCommand.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -25,16 +25,16 @@
 
 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)
+                                               Orthanc::IDynamicObject* payload /* takes ownership */,
+                                               BasicNativeApplicationContext& context) :
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context),
+    body_(body)
   {
   }
 
@@ -44,18 +44,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	Tue Aug 28 21:00:35 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Thu Aug 30 16:56:08 2018 +0200
@@ -21,36 +21,25 @@
 
 #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 */);
+                          Orthanc::IDynamicObject* payload /* takes ownership */,
+                          BasicNativeApplicationContext& context);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/CMakeLists.txt	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,88 @@
+# 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_QT OFF)
+SET(ENABLE_SDL OFF)
+add_definitions(-DORTHANC_ENABLE_WASM=1)
+
+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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,275 @@
+#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"
+#include "Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h"
+
+static unsigned int width_ = 0;
+static unsigned int height_ = 0;
+
+/**********************************/
+
+static std::unique_ptr<OrthancStone::IStoneApplication> application;
+static OrthancStone::IStoneApplicationToWebApplicationAdapter* applicationWebAdapter = NULL;
+static std::unique_ptr<OrthancStone::StoneApplicationContext> context;
+static OrthancStone::StartupParametersBuilder startupParametersBuilder;
+static OrthancStone::MessageBroker broker;
+
+static OrthancStone::ViewportContentChangedObserver viewportContentChangedObserver_;
+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", (int)viewport.get());
+
+    viewports_.push_back(viewport);
+
+    printf("There are now %d viewports in C++\n", viewports_.size());
+
+    viewport->SetStatusBar(statusBar_);
+    viewport->Register(viewportContentChangedObserver_);
+
+    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));
+    applicationWebAdapter = dynamic_cast<OrthancStone::IStoneApplicationToWebApplicationAdapter*>(application.get()); 
+    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::StoneApplicationContext());
+    context->SetWebService(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)
+  {
+    viewportContentChangedObserver_.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();
+  }
+
+  const char* EMSCRIPTEN_KEEPALIVE SendMessageToStoneApplication(const char* message) 
+  {
+    static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread)
+
+    if (applicationWebAdapter != NULL) {
+      printf("sending message to C++");
+      applicationWebAdapter->HandleMessageFromWeb(output, std::string(message));
+      return output.c_str();
+    }
+    return "This stone application does not have a Web Adapter";
+  }
+
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/Defaults.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,76 @@
+#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/IStoneApplication.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);
+  extern void UpdateStoneApplicationStatusFromCpp(const char* statusUpdateMessage);
+  
+  // 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::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker);
+
+namespace OrthancStone {
+
+  // default Observer to trigger Viewport redraw when something changes in the Viewport
+  class ViewportContentChangedObserver :
+    public OrthancStone::IViewport::IObserver
+  {
+  private:
+    // Flag to avoid flooding JavaScript with redundant Redraw requests
+    bool isScheduled_; 
+
+  public:
+    ViewportContentChangedObserver() :
+      isScheduled_(false)
+    {
+    }
+
+    void Reset()
+    {
+      isScheduled_ = false;
+    }
+
+    virtual void OnViewportContentChanged(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/IStoneApplicationToWebApplicationAdapter.h	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <string>
+
+namespace OrthancStone
+{
+  class IStoneApplicationToWebApplicationAdapter
+  {
+    public:
+      virtual void HandleMessageFromWeb(std::string& output, const std::string& input) = 0;
+      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) = 0;
+  };
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmViewport.cpp	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,16 @@
+// 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);
+  },
+  // each time the StoneApplication updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface
+  UpdateStoneApplicationStatusFromCpp: function(statusUpdateMessage) {
+    var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage);
+    UpdateWebApplication(statusUpdateMessage_);
+  }
+});
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/nginx.local.conf	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,44 @@
+# Local config to serve the WASM samples static files and reverse proxy Orthanc.
+# Uses port 9977 instead of 80.
+
+# `events` section is mandatory
+events {
+  worker_connections 1024; # Default: 1024
+}
+
+http {
+
+  # prevent nginx sync issues on OSX
+  proxy_buffering off;
+
+  server {
+    listen 9977 default_server;
+    client_max_body_size 4G;
+
+    # location may have to be adjusted depending on your OS and nginx install
+    include /etc/nginx/mime.types;
+    # if not in your system mime.types, add this line to support WASM:
+    # types {
+    #    application/wasm                      wasm; 
+    # }
+
+    # serve WASM static files
+    root build-web/;
+    location / {
+	}
+
+    # reverse proxy orthanc
+	location /orthanc/ {
+		rewrite /orthanc(.*) $1 break;
+		proxy_pass http://127.0.0.1:8042;
+		proxy_set_header Host $http_host;
+		proxy_set_header my-auth-header good-token;
+		proxy_request_buffering off;
+		proxy_max_temp_file_size 0;
+		client_max_body_size 0;
+	}
+
+
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/stone-framework-loader.ts	Thu Aug 30 16:56:08 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	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,7 @@
+{
+    "include" : [
+        "../../../Platforms/Wasm/stone-framework-loader.ts",
+        "../../../Platforms/Wasm/wasm-application-runner.ts",
+        "../../../Platforms/Wasm/wasm-viewport.ts"
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/wasm-application-runner.ts	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,112 @@
+///<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;
+var SendMessageToStoneApplication: 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 {};
+  }
+}
+
+// function UpdateWebApplication(statusUpdateMessage: string) {
+//   console.log(statusUpdateMessage);
+// }
+
+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, []);
+
+    SendMessageToStoneApplication = StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']);
+
+    console.log("Connecting C++ methods to JS methods - done");
+
+    // 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	Thu Aug 30 16:56:08 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	Tue Aug 28 21:00:35 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	Tue Aug 28 21:00:35 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-mergeInto(LibraryManager.library, {
-});
--- a/README	Tue Aug 28 21:00:35 2018 +0200
+++ b/README	Thu Aug 30 16:56:08 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
 ----------------------
@@ -83,7 +87,43 @@
 http://book.orthanc-server.com/developers/stone.html
 
 Stone of Orthanc comes with several sample applications in the
-"Samples" folder. These samples use SDL.
+"Samples" folder. These samples can be compiled into Web Assembly
+or into native SDL applications.
+
+to build the WASM samples:
+-------------------------
+```
+cd ~/orthanc-stone/Platforms/Wasm
+./build-wasm.sh
+```
+
+to serve the WASM samples:
+```
+# launch an Orthanc listening on 8042 port:
+Orthanc
+
+# launch an nginx that will serve the WASM static files and reverse proxy Orthanc
+sudo nginx -p $(pwd) -c nginx.local.conf
+```
+Now, you can open the samples in http://localhost:9977
+
+to build the SDL native samples (SimpleViewer only):
+-------------------------------
+```
+mkdir -p ~/builds/orthanc-stone-build
+cd ~/builds/orthanc-stone-build
+cmake -DALLOW_DOWNLOADS=ON ~/orthanc-stone/
+cmake --build . --target OrthancStoneSimpleViewer -- -j 5
+```
+
+to execute the native samples:
+```
+# launch an Orthanc listening on 8042 port:
+Orthanc
+
+# launch the sample
+./OrthancStoneSimpleViewer --instance1=XX --instance2=XX
+``` 
 
 
 Licensing
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Aug 28 21:00:35 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Aug 30 16:56:08 2018 +0200
@@ -39,6 +39,10 @@
     message(FATAL_ERROR "Cannot enable SDL in sandboxed environments")
   endif()
 
+  if (ENABLE_QT)
+    message(FATAL_ERROR "Cannot enable QT in sandboxed environments")
+  endif()
+
   if (ENABLE_SSL)
     message(FATAL_ERROR "Cannot enable SSL in sandboxed environments")
   endif()
@@ -69,12 +73,26 @@
 endif()
 
 
-if (ENABLE_SDL)
-  include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)  
+if (ENABLE_SDL AND ENABLE_QT)
+  message("SDL and QT may not be defined together")
+elseif(ENABLE_SDL)
+  message("SDL is enabled")
+  include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_NATIVE=1)
+  add_definitions(-DORTHANC_ENABLE_QT=0)
   add_definitions(-DORTHANC_ENABLE_SDL=1)
+elseif(ENABLE_QT)
+  message("QT is enabled")
+  include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_NATIVE=1)
+  add_definitions(-DORTHANC_ENABLE_QT=1)
+  add_definitions(-DORTHANC_ENABLE_SDL=0)
 else()
+  message("SDL and QT are both disabled")
   unset(USE_SYSTEM_SDL CACHE)
   add_definitions(-DORTHANC_ENABLE_SDL=0)
+  add_definitions(-DORTHANC_ENABLE_QT=0)
+  add_definitions(-DORTHANC_ENABLE_NATIVE=0)
 endif()
 
 
@@ -93,7 +111,9 @@
   -DORTHANC_ENABLE_LOGGING_PLUGIN=0
   )
 
-
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  add_definitions(-DCHECK_OBSERVERS_MESSAGES)
+endif()
 
 #####################################################################
 ## Embed the colormaps into the binaries
@@ -141,21 +161,46 @@
 ## All the source files required to build Stone of Orthanc
 #####################################################################
 
+set(APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h
+    ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.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
-    ${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
+  if (ENABLE_SDL OR ENABLE_QT)
+    list(APPEND APPLICATIONS_SOURCES
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/BasicNativeApplicationRunner.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/BasicNativeApplicationContext.cpp
+      )
+    if (ENABLE_SDL)
+      list(APPEND APPLICATIONS_SOURCES
+        ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplication.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
+        )
+    endif()
+  endif()
+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
+    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h
+  )
 endif()
 
 list(APPEND ORTHANC_STONE_SOURCES
@@ -163,11 +208,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
@@ -182,10 +229,12 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IWebService.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
@@ -195,6 +244,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
@@ -203,6 +253,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/CairoWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/EmptyWidget.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayerWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp
@@ -210,6 +261,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
@@ -227,5 +284,7 @@
 
   # Optional components
   ${SDL_SOURCES}
+  ${QT_SOURCES}
   ${BOOST_EXTENDED_SOURCES}
   )
+
--- a/Resources/CMake/OrthancStoneParameters.cmake	Tue Aug 28 21:00:35 2018 +0200
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Thu Aug 30 16:56:08 2018 +0200
@@ -50,3 +50,4 @@
 #####################################################################
 
 set(ENABLE_SDL ON CACHE INTERNAL "Include support for SDL")
+set(ENABLE_QT OFF CACHE INTERNAL "Include support for Qt")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/QtConfiguration.cmake	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,40 @@
+# 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/>.
+
+
+# Instruct CMake to run moc automatically when needed.
+set(CMAKE_AUTOMOC ON)
+SET(CMAKE_AUTOUIC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# Find the QtWidgets library
+find_package(Qt5Widgets)
+find_package(Qt5Core)
+
+list(APPEND QT_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Qt/BasicQtApplicationRunner.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.cpp
+)
+
+include_directories(${ORTHANC_STONE_ROOT}/Applications/Qt/)
+
+link_libraries(
+    Qt5::Widgets
+    Qt5::Core
+)
--- a/TODO	Tue Aug 28 21:00:35 2018 +0200
+++ b/TODO	Thu Aug 30 16:56:08 2018 +0200
@@ -26,7 +26,6 @@
 -------------
 
 * Tune number of loading threads in LayeredSceneWidget
-* Add cache over IOrthancServices (for SDL/Qt/...)
 * LayoutWidget: Do not update full background if only 1 widget has changed
 * LayoutWidget: Threads to refresh each child
 * Implement binary search to speed up search for closest slice
@@ -38,7 +37,6 @@
 Platform-specific
 -----------------
 
-* Qt widget example
 * Add precompiled headers for Microsoft Visual Studio
 * Investigate crash in CurlOrthancConnection if using MinGW32 in Release mode
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker.cpp	Thu Aug 30 16:56:08 2018 +0200
@@ -0,0 +1,158 @@
+/**
+ * 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 test1Counter = 0;
+static int test2Counter = 0;
+class MyFullObserver : public OrthancStone::IObserver
+{
+
+public:
+  MyFullObserver(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObserver(broker)
+  {
+    DeclareHandledMessage(OrthancStone::MessageType_Test1);
+    DeclareIgnoredMessage(OrthancStone::MessageType_Test2);
+  }
+
+
+  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+    switch (message.GetType())
+    {
+    case OrthancStone::MessageType_Test1:
+      test1Counter++;
+      break;
+    case OrthancStone::MessageType_Test2:
+      test2Counter++;
+      break;
+    default:
+      throw OrthancStone::MessageNotDeclaredException(message.GetType());
+    }
+  }
+
+};
+
+class MyPartialObserver : public OrthancStone::IObserver
+{
+
+public:
+  MyPartialObserver(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObserver(broker)
+  {
+    DeclareHandledMessage(OrthancStone::MessageType_Test1);
+    // don't declare Test2 on purpose
+  }
+
+
+  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+    switch (message.GetType())
+    {
+    case OrthancStone::MessageType_Test1:
+      test1Counter++;
+      break;
+    case OrthancStone::MessageType_Test2:
+      test2Counter++;
+      break;
+    default:
+      throw OrthancStone::MessageNotDeclaredException(message.GetType());
+    }
+  }
+
+};
+
+
+class MyObservable : public OrthancStone::IObservable
+{
+
+public:
+  MyObservable(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObservable(broker)
+  {
+    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
+    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
+  }
+
+};
+
+
+TEST(MessageBroker, NormalUsage)
+{
+  OrthancStone::MessageBroker broker;
+  MyObservable observable(broker);
+
+  test1Counter = 0;
+
+  // no observers have been registered -> nothing shall happen
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+
+  ASSERT_EQ(0, test1Counter);
+
+  // register an observer, check it is called
+  MyFullObserver fullObserver(broker);
+  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
+
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+
+  ASSERT_EQ(1, test1Counter);
+
+  // register an invalid observer, check it raises an exception
+  MyPartialObserver partialObserver(broker);
+  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
+
+  // check an exception is thrown when the observable emits an undeclared message
+  ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException);
+
+  // unregister the observer, make sure nothing happens afterwards
+  observable.UnregisterObserver(fullObserver);
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  ASSERT_EQ(1, test1Counter);
+}
+
+TEST(MessageBroker, DeleteObserverWhileRegistered)
+{
+  OrthancStone::MessageBroker broker;
+  MyObservable observable(broker);
+
+  test1Counter = 0;
+
+  {
+    // register an observer, check it is called
+    MyFullObserver observer(broker);
+    observable.RegisterObserver(observer);
+
+    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+
+    ASSERT_EQ(1, test1Counter);
+  }
+
+  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+
+  ASSERT_EQ(1, test1Counter);
+}
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Aug 28 21:00:35 2018 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Thu Aug 30 16:56:08 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);
       }
     }