changeset 843:67f9c27214c5

Removed extra logging + doc + added GuiAdapter and LockingEmitter
author Benjamin Golinvaux <bgo@osimis.io>
date Fri, 14 Jun 2019 12:14:16 +0200
parents 266e2b0b9abc
children 84cd55245e2d
files Applications/Generic/GuiAdapter.cpp Applications/Generic/GuiAdapter.h Framework/Messages/IMessageEmitter.h Framework/Messages/LockingEmitter.h Framework/Oracle/WebAssemblyOracle.cpp Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 6 files changed, 1078 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/GuiAdapter.cpp	Fri Jun 14 12:14:16 2019 +0200
@@ -0,0 +1,636 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "GuiAdapter.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+# if ORTHANC_ENABLE_OPENGL == 1
+#   include "../../Framework/OpenGL/OpenGLIncludes.h"
+#   include <SDL_video.h>
+#   include <SDL_render.h>
+#   include <SDL.h>
+# endif
+#endif
+
+#if ORTHANC_ENABLE_THREADS == 1
+# include "../../Framework/Messages/LockingEmitter.h"
+#endif
+
+namespace OrthancStone
+{
+  void GuiAdapter::RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget)
+  {
+    widgets_.push_back(widget);
+  }
+
+#if ORTHANC_ENABLE_WASM == 1
+  void GuiAdapter::Run()
+  {
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterUiEvent& dest,
+    int                      eventType,
+    const EmscriptenUiEvent& src)
+  {
+    // no data for now
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent& dest,
+    int                         eventType,
+    const EmscriptenMouseEvent& src)
+  {
+    memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
+    switch (eventType)
+    {
+    case EMSCRIPTEN_EVENT_CLICK:
+      LOG(ERROR) << "Emscripten EMSCRIPTEN_EVENT_CLICK is not supported";
+      ORTHANC_ASSERT(false, "Not supported");
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEDOWN:
+      dest.type = GUIADAPTER_EVENT_MOUSEDOWN;
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEMOVE:
+      dest.type = GUIADAPTER_EVENT_MOUSEMOVE;
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEUP:
+      dest.type = GUIADAPTER_EVENT_MOUSEUP;
+      break;
+    case EMSCRIPTEN_EVENT_WHEEL:
+      dest.type = GUIADAPTER_EVENT_WHEEL;
+      break;
+
+    default:
+      LOG(ERROR) << "Emscripten event: " << eventType << " is not supported";
+      ORTHANC_ASSERT(false, "Not supported");
+    }
+    //dest.timestamp = src.timestamp;
+    //dest.screenX = src.screenX;
+    //dest.screenY = src.screenY;
+    //dest.clientX = src.clientX;
+    //dest.clientY = src.clientY;
+    dest.ctrlKey = src.ctrlKey;
+    dest.shiftKey = src.shiftKey;
+    dest.altKey = src.altKey;
+    //dest.metaKey = src.metaKey;
+    dest.button = src.button;
+    //dest.buttons = src.buttons;
+    //dest.movementX = src.movementX;
+    //dest.movementY = src.movementY;
+    dest.targetX = src.targetX;
+    dest.targetY = src.targetY;
+    //dest.canvasX = src.canvasX;
+    //dest.canvasY = src.canvasY;
+    //dest.padding = src.padding;
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent& dest,
+    int                         eventType,
+    const EmscriptenWheelEvent& src)
+  {
+    ConvertFromPlatform(dest.mouse, eventType, src.mouse);
+    dest.deltaX = src.deltaX;
+    dest.deltaY = src.deltaY;
+    switch (src.deltaMode)
+    {
+    case DOM_DELTA_PIXEL:
+      dest.deltaMode = GUIADAPTER_DELTA_PIXEL;
+      break;
+    case DOM_DELTA_LINE:
+      dest.deltaMode = GUIADAPTER_DELTA_LINE;
+      break;
+    case DOM_DELTA_PAGE:
+      dest.deltaMode = GUIADAPTER_DELTA_PAGE;
+      break;
+    default:
+      ORTHANC_ASSERT(false, "Unknown deltaMode: " << src.deltaMode <<
+        " in wheel event...");
+    }
+    dest.deltaMode = src.deltaMode;
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterKeyboardEvent& dest,
+    const EmscriptenKeyboardEvent& src)
+  {
+    dest.ctrlKey = src.ctrlKey;
+    dest.shiftKey = src.shiftKey;
+    dest.altKey = src.altKey;
+  }
+   
+  template<typename GenericFunc>
+  struct FuncAdapterPayload
+  {
+    void*       userData;
+    GenericFunc callback;
+  };
+
+  template<typename GenericFunc,
+           typename GuiAdapterEvent,
+           typename EmscriptenEvent>
+  EM_BOOL OnEventAdapterFunc(
+    int eventType, const EmscriptenEvent* wheelEvent, void* userData)
+  {
+
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    // LOG(INFO) << "OnEventAdapterFunc";
+    // LOG(INFO) << "------------------";
+    // LOG(INFO) << "eventType: " << eventType << " wheelEvent: " << 
+    //   (int)wheelEvent << " userData: " << userData << 
+    //   " payload->userData: " << payload->userData;
+    
+    GuiAdapterEvent guiEvent;
+    ConvertFromPlatform(guiEvent, eventType, *wheelEvent);
+    bool ret = (*(payload->callback))(&guiEvent, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  template<typename GenericFunc,
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent>
+    EM_BOOL OnEventAdapterFunc2(
+      int /*eventType*/, const EmscriptenEvent* wheelEvent, void* userData)
+  {
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    
+    GuiAdapterEvent guiEvent;
+    ConvertFromPlatform(guiEvent, *wheelEvent);
+    bool ret = (*(payload->callback))(&guiEvent, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  template<typename GenericFunc>
+    EM_BOOL OnEventAdapterFunc3(
+      double time, void* userData)
+  {
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    //std::auto_ptr< FuncAdapterPayload<GenericFunc> > deleter(payload);
+    bool ret = (*(payload->callback))(time, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  // resize: (const char* target, void* userData, EM_BOOL useCapture, em_ui_callback_func callback)
+  template<
+    typename GenericFunc, 
+    typename GuiAdapterEvent, 
+    typename EmscriptenEvent, 
+    typename EmscriptenSetCallbackFunc>
+  static void SetCallback(
+    EmscriptenSetCallbackFunc emFunc, 
+    std::string canvasId, void* userData, bool capture, GenericFunc func)
+  {
+    // TODO: write RemoveCallback with an int id that gets returned from
+    // here
+    FuncAdapterPayload<GenericFunc>* payload = 
+      new FuncAdapterPayload<GenericFunc>();
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payloadP(payload);
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload);
+    // LOG(INFO) << "SetCallback -- userDataRaw: " << userDataRaw <<
+    //   " payload: " << payload << " payload->userData: " << payload->userData;
+    (*emFunc)(
+      canvasId.c_str(),
+      userDataRaw,
+      static_cast<EM_BOOL>(capture),
+      &OnEventAdapterFunc<GenericFunc, GuiAdapterEvent, EmscriptenEvent>,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+    payloadP.release();
+  }
+
+  template<
+    typename GenericFunc,
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent,
+    typename EmscriptenSetCallbackFunc>
+    static void SetCallback2(
+      EmscriptenSetCallbackFunc emFunc,
+      std::string canvasId, void* userData, bool capture, GenericFunc func)
+  {
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload(
+      new FuncAdapterPayload<GenericFunc>()
+    );
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload.release());
+    (*emFunc)(
+      canvasId.c_str(),
+      userDataRaw,
+      static_cast<EM_BOOL>(capture),
+      &OnEventAdapterFunc2<GenericFunc, GuiAdapterEvent, EmscriptenEvent>,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+  }
+
+  template<
+    typename GenericFunc,
+    typename EmscriptenSetCallbackFunc>
+    static void SetCallback3(
+      EmscriptenSetCallbackFunc emFunc,
+      void* userData, GenericFunc func)
+  {
+    // LOG(ERROR) << "SetCallback3 !!!!!! (RequestAnimationFrame)";
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload(
+      new FuncAdapterPayload<GenericFunc>()
+    );
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload.release());
+    (*emFunc)(
+      &OnEventAdapterFunc3<GenericFunc>,
+      userDataRaw);
+  }
+
+  void GuiAdapter::SetWheelCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func)
+  {
+    SetCallback<OnMouseWheelFunc, GuiAdapterWheelEvent, EmscriptenWheelEvent>(
+      &emscripten_set_wheel_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseDownCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mousedown_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseMoveCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    // LOG(INFO) << "SetMouseMoveCallback -- " << "supplied userData: " << 
+    //   userData;
+
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mousemove_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseUpCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mouseup_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetKeyDownCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyDownFunc func)
+  {
+    SetCallback2<OnKeyDownFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>(
+      &emscripten_set_keydown_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetKeyUpCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyUpFunc func)
+  {
+    SetCallback2<OnKeyUpFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>(
+      &emscripten_set_keyup_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetResizeCallback(
+    std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    SetCallback<OnWindowResizeFunc, GuiAdapterUiEvent, EmscriptenUiEvent>(
+      &emscripten_set_resize_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::RequestAnimationFrame(
+    OnAnimationFrameFunc func, void* userData)
+  {
+    // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+";
+    // LOG(ERROR) << "RequestAnimationFrame";
+    // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+";
+    SetCallback3<OnAnimationFrameFunc>(
+      &emscripten_request_animation_frame_loop,
+      userData,
+      func);
+  }
+
+#if 0
+  void GuiAdapter::SetKeyDownCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyDownFunc func)
+  {
+    emscripten_set_keydown_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+  void GuiAdapter::SetKeyUpCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyUpFunc func)
+  {
+    emscripten_set_keyup_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+
+  void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+
+  void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData)
+  {
+    emscripten_request_animation_frame_loop(func, userData);
+  }
+#endif
+
+
+#else
+
+void ConvertFromPlatform(
+  GuiAdapterMouseEvent& dest,
+  bool ctrlPressed, bool shiftPressed, bool altPressed,
+  const SDL_Event& source)
+{
+  memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
+  switch (source.type)
+  {
+  case SDL_MOUSEBUTTONDOWN:
+    dest.type = GUIADAPTER_EVENT_MOUSEDOWN;
+    break;
+  case SDL_MOUSEMOTION:
+    dest.type = GUIADAPTER_EVENT_MOUSEMOVE;
+    break;
+  case SDL_MOUSEBUTTONUP:
+    dest.type = GUIADAPTER_EVENT_MOUSEUP;
+    break;
+  default:
+    LOG(ERROR) << "SDL event: " << source.type << " is not supported";
+    ORTHANC_ASSERT(false, "Not supported");
+    }
+  //dest.timestamp = src.timestamp;
+  //dest.screenX = src.screenX;
+  //dest.screenY = src.screenY;
+  //dest.clientX = src.clientX;
+  //dest.clientY = src.clientY;
+  dest.ctrlKey = ctrlPressed;
+  dest.shiftKey = shiftPressed;
+  dest.altKey = altPressed;
+  //dest.metaKey = src.metaKey;
+  switch (source.button.button)
+  {
+  case SDL_BUTTON_MIDDLE:
+    dest.button = 1;
+    break;
+
+  case SDL_BUTTON_RIGHT:
+    dest.button = 2;
+    break;
+
+  case SDL_BUTTON_LEFT:
+    dest.button = 0;
+    break;
+
+  default:
+    break;
+  }
+  //dest.buttons = src.buttons;
+  //dest.movementX = src.movementX;
+  //dest.movementY = src.movementY;
+  dest.targetX = source.button.x;
+  dest.targetY = source.button.y;
+  //dest.canvasX = src.canvasX;
+  //dest.canvasY = src.canvasY;
+  //dest.padding = src.padding;
+  }
+
+  void GuiAdapter::SetResizeCallback(
+    std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    resizeHandlers_.push_back(std::make_pair(func, userData));
+  }
+
+  void GuiAdapter::SetMouseDownCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+ {
+ }
+
+  void GuiAdapter::SetMouseMoveCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
+ {
+ }
+
+  void GuiAdapter::SetMouseUpCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
+ {
+ }
+
+ void GuiAdapter::SetWheelCallback(
+   std::string canvasId, void* userData, bool capture, OnMouseWheelFunc  func)
+ {
+ }
+
+ void GuiAdapter::SetKeyDownCallback(
+   std::string canvasId, void* userData, bool capture, OnKeyDownFunc   func)
+ {
+ }
+
+ void GuiAdapter::SetKeyUpCallback(
+   std::string canvasId, void* userData, bool capture, OnKeyUpFunc    func)
+ {
+ }
+
+
+  void GuiAdapter::OnAnimationFrame()
+  {
+    for (const auto& handler : animationFrameHandlers_)
+    {
+      // TODO: fix time 
+      (*(handler.first))(0,handler.second);
+    }
+  }
+
+  void GuiAdapter::OnResize()
+  {
+    for (const auto& handler : resizeHandlers_)
+    {
+      // TODO: fix time 
+      (*(handler.first))(0, handler.second);
+    }
+  }
+   
+  void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event)
+  {
+  }
+
+
+  void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData)
+  {
+    animationFrameHandlers_.push_back(std::make_pair(func, userData));
+  }
+
+# if ORTHANC_ENABLE_OPENGL == 1
+
+  static void GLAPIENTRY
+    OpenGLMessageCallback(GLenum source,
+      GLenum type,
+      GLuint id,
+      GLenum severity,
+      GLsizei length,
+      const GLchar* message,
+      const void* userParam)
+  {
+    if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
+    {
+      fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+        (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+        type, severity, message);
+    }
+}
+# endif
+
+  void GuiAdapter::Run()
+  {
+# if ORTHANC_ENABLE_OPENGL == 1
+    glEnable(GL_DEBUG_OUTPUT);
+    glDebugMessageCallback(OpenGLMessageCallback, 0);
+# endif
+
+    // Uint32 SDL_GetWindowID(SDL_Window* window)
+    // SDL_Window* SDL_GetWindowFromID(Uint32 id)   // may return NULL
+
+    bool stop = false;
+    while (!stop)
+    {
+      {
+        LockingEmitter::WriterLock lock(lockingEmitter_);
+        OnAnimationFrame(); // in SDL we must call it
+      }
+
+      SDL_Event event;
+
+      while (!stop && SDL_PollEvent(&event))
+      {
+        LockingEmitter::WriterLock lock(lockingEmitter_);
+
+        if (event.type == SDL_QUIT)
+        {
+          // TODO: call exit callbacks here
+          stop = true;
+          break;
+        }
+        else if ( (event.type == SDL_MOUSEMOTION) ||
+                  (event.type == SDL_MOUSEBUTTONDOWN) ||
+                  (event.type == SDL_MOUSEBUTTONUP) )
+        {
+          int scancodeCount = 0;
+          const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+          bool ctrlPressed(false);
+          bool shiftPressed(false);
+          bool altPressed(false);
+            
+          if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT])
+            altPressed = true;
+          
+          GuiAdapterMouseEvent dest;
+          ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event);
+          OnMouseEvent(event.window.windowID, dest);
+#if 0
+          // for reference, how to create trackers
+          if (tracker)
+          {
+            PointerEvent e;
+            e.AddPosition(compositor.GetPixelCenterCoordinates(
+              event.button.x, event.button.y));
+            tracker->PointerMove(e);
+          }
+#endif
+        }
+        else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+        {
+#if 0
+          tracker.reset();
+#endif
+          OnResize();
+        }
+        else if (event.type == SDL_KEYDOWN && event.key.repeat == 0 /* Ignore key bounce */)
+        {
+          switch (event.key.keysym.sym)
+          {
+          case SDLK_f:
+            // window.GetWindow().ToggleMaximize(); //TODO: move to particular handler
+            break;
+
+          case SDLK_s:
+#if 0
+            // TODO: re-enable at application-level!!!!
+            VisitWidgets(
+              [](auto value)
+              {
+                auto widget = boost::dynamic_pointer_cast<VolumeSlicerWidget()
+                value->FitContent();
+              });
+#endif
+            break;
+
+          case SDLK_q:
+            stop = true;
+            break;
+
+          default:
+            break;
+          }
+        }
+        //        HandleApplicationEvent(controller, compositor, event, tracker);
+      }
+
+      SDL_Delay(1);
+    }
+  }
+#endif
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/GuiAdapter.h	Fri Jun 14 12:14:16 2019 +0200
@@ -0,0 +1,323 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 <string>
+
+#if ORTHANC_ENABLE_WASM != 1
+# ifdef __EMSCRIPTEN__
+#   error __EMSCRIPTEN__ is defined and ORTHANC_ENABLE_WASM != 1
+# endif
+#endif
+
+#if ORTHANC_ENABLE_WASM == 1
+# ifndef __EMSCRIPTEN__
+#   error __EMSCRIPTEN__ is not defined and ORTHANC_ENABLE_WASM == 1
+# endif
+#endif
+
+#if ORTHANC_ENABLE_WASM == 1
+# include <emscripten/html5.h>
+#else
+# if ORTHANC_ENABLE_SDL == 1
+#   include <SDL.h>
+# endif
+#endif
+
+#include "../../Framework/StoneException.h"
+
+#if ORTHANC_ENABLE_THREADS != 1
+# include "../../Framework/Messages/LockingEmitter.h"
+#endif
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+namespace OrthancStone
+{
+
+  /**
+  This interface is used to store the widgets that are controlled by the 
+  GuiAdapter and receive event callbacks.
+  The callbacks may possibly be downcast (using dynamic_cast, for safety) \
+  to the actual widget type
+  */
+  class IGuiAdapterWidget
+  {
+  public:
+    virtual ~IGuiAdapterWidget() {}
+  };
+
+  enum GuiAdapterMouseEventType
+  {
+    GUIADAPTER_EVENT_MOUSEDOWN = 1973,
+    GUIADAPTER_EVENT_MOUSEMOVE = 1974,
+    GUIADAPTER_EVENT_MOUSEUP   = 1975,
+    GUIADAPTER_EVENT_WHEEL     = 1976
+  };
+
+  const unsigned int GUIADAPTER_DELTA_PIXEL = 2973;
+  const unsigned int GUIADAPTER_DELTA_LINE  = 2974;
+  const unsigned int GUIADAPTER_DELTA_PAGE  = 2975;
+
+  struct GuiAdapterUiEvent;
+  struct GuiAdapterMouseEvent;
+  struct GuiAdapterWheelEvent;
+  struct GuiAdapterKeyboardEvent;
+
+  class LockingEmitter;
+    
+#if 1
+  typedef bool (*OnMouseEventFunc)(const GuiAdapterMouseEvent* mouseEvent, void* userData);
+  typedef bool (*OnMouseWheelFunc)(const GuiAdapterWheelEvent* wheelEvent, void* userData);
+  typedef bool (*OnKeyDownFunc)   (const GuiAdapterKeyboardEvent*   keyEvent,   void* userData);
+  typedef bool (*OnKeyUpFunc)     (const GuiAdapterKeyboardEvent*   keyEvent,   void* userData);
+
+  typedef bool (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef bool (*OnWindowResizeFunc)(const GuiAdapterUiEvent* uiEvent, void* userData);
+
+#else
+
+#if ORTHANC_ENABLE_WASM == 1
+  typedef EM_BOOL (*OnMouseEventFunc)(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+  typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData);
+  typedef EM_BOOL (*OnKeyDownFunc)   (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData);
+  typedef EM_BOOL (*OnKeyUpFunc)     (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData);
+
+  typedef EM_BOOL (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef EM_BOOL (*OnWindowResizeFunc)(int eventType, const EmscriptenUiEvent* uiEvent, void* userData);
+#else
+  typedef bool    (*OnMouseEventFunc)(int eventType, const SDL_Event* mouseEvent, void* userData);
+  typedef bool    (*OnMouseWheelFunc)(int eventType, const SDL_Event* wheelEvent, void* userData);
+  typedef bool    (*OnKeyDownFunc)   (int eventType, const SDL_Event* keyEvent,   void* userData);
+  typedef bool    (*OnKeyUpFunc)     (int eventType, const SDL_Event* keyEvent,   void* userData);
+
+  typedef bool    (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef bool    (*OnWindowResizeFunc)(int eventType, const GuiAdapterUiEvent* uiEvent, void* userData);
+#endif
+
+#endif
+  struct GuiAdapterMouseEvent {
+    GuiAdapterMouseEventType type;
+    //double                   timestamp;
+    //long                     screenX;
+    //long                     screenY;
+    //long                     clientX;
+    //long                     clientY;
+    bool                     ctrlKey;
+    bool                     shiftKey;
+    bool                     altKey;
+    //bool                     metaKey;
+    unsigned short           button;
+    //unsigned short           buttons;
+    //long                     movementX;
+    //long                     movementY;
+    long                     targetX;
+    long                     targetY;
+    // canvasX and canvasY are deprecated - there no longer exists a Module['canvas'] object, so canvasX/Y are no longer reported (register a listener on canvas directly to get canvas coordinates, or translate manually)
+    //long                     canvasX;
+    //long                     canvasY;
+    //long                     padding;
+  };
+
+  struct GuiAdapterWheelEvent {
+    GuiAdapterMouseEvent mouse;
+    double deltaX;
+    double deltaY;
+    unsigned long deltaMode;
+  };
+
+  // we don't use any data now
+  struct GuiAdapterUiEvent {};
+
+  // EmscriptenKeyboardEvent
+  struct GuiAdapterKeyboardEvent
+  {
+    bool ctrlKey;
+    bool shiftKey;
+    bool altKey;
+  };
+
+  /*
+    Mousedown event trigger when either the left or right (or middle) mouse is pressed 
+    on the object;
+
+    Mouseup event trigger when either the left or right (or middle) mouse is released
+    above the object after triggered mousedown event and held.
+
+    Click event trigger when the only left mouse button is pressed and released on the
+    same object, requires the Mousedown and Mouseup event happened before Click event.
+
+    The normal expect trigger order: onMousedown >> onMouseup >> onClick
+
+    Testing in Chrome v58, the time between onMouseup and onClick events are around 
+    7ms to 15ms
+
+    FROM: https://codingrepo.com/javascript/2017/05/19/javascript-difference-mousedown-mouseup-click-events/
+  */
+#if ORTHANC_ENABLE_WASM == 1
+  void ConvertFromPlatform(
+    GuiAdapterUiEvent&       dest,
+    int                      eventType,
+    const EmscriptenUiEvent& src);
+
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent&       dest,
+    int                         eventType,
+    const EmscriptenMouseEvent& src);
+  
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent&       dest,
+    int                         eventType,
+    const EmscriptenWheelEvent& src);
+
+  void ConvertFromPlatform(
+    GuiAdapterKeyboardEvent&       dest,
+    const EmscriptenKeyboardEvent& src);
+
+#else
+
+# if ORTHANC_ENABLE_SDL == 1
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent& dest,
+    bool ctrlPressed, bool shiftPressed, bool altPressed,
+    const SDL_Event& source);
+# endif
+
+#endif
+
+  class GuiAdapter
+  {
+  public:
+#if ORTHANC_ENABLE_THREADS == 1
+    GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter)
+    {}
+#else
+    GuiAdapter() {}
+#endif
+
+    void RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget);
+    
+    /**
+      emscripten_set_resize_callback("#window", NULL, false, OnWindowResize);
+
+      emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnMouseWheel);
+
+      emscripten_set_keydown_callback("#window", NULL, false, OnKeyDown);
+      emscripten_set_keyup_callback("#window", NULL, false, OnKeyUp);
+
+      emscripten_request_animation_frame_loop(OnAnimationFrame, NULL);
+    
+      SDL:
+      see https://wiki.libsdl.org/SDL_CaptureMouse
+
+    */
+
+    void SetMouseDownCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetMouseMoveCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetMouseUpCallback  (std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetWheelCallback    (std::string canvasId, void* userData, bool capture, OnMouseWheelFunc   func);
+    void SetKeyDownCallback  (std::string canvasId, void* userData, bool capture, OnKeyDownFunc      func);
+    void SetKeyUpCallback    (std::string canvasId, void* userData, bool capture, OnKeyUpFunc        func);
+    
+    // if you pass "#window", under SDL, then any Window resize will trigger the callback
+    void SetResizeCallback (std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func);
+
+    void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData);
+
+    // TODO: implement and call to remove canvases [in SDL, although code should be generic]
+    void SetOnExitCallback();
+
+    // void 
+    // OnWindowResize
+    // oracle
+    // broker
+
+    /**
+    Under SDL, this function does NOT return until all windows have been closed.
+    Under wasm, it returns without doing anything, since the event loop is managed
+    by the browser.
+    */
+    void Run();
+
+#if ORTHANC_ENABLE_WASM != 1
+    /**
+    This method must be called in order for callback handler to be allowed to 
+    be registered.
+
+    We'll retrieve its name and use it as the canvas name in all subsequent 
+    calls
+    */
+    //void RegisterSdlWindow(SDL_Window* window);
+    //void UnregisterSdlWindow(SDL_Window* window);
+#endif
+
+  private:
+
+#if ORTHANC_ENABLE_THREADS == 1
+    /**
+    This object is used by the multithreaded Oracle to serialize access to
+    shared data. We need to use it as soon as we access the state.
+    */
+    LockingEmitter& lockingEmitter_;
+#endif
+
+    /**
+    In SDL, this executes all the registered headers
+    */
+    void OnAnimationFrame();
+
+    //void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData);
+    std::vector<std::pair<OnAnimationFrameFunc, void*> >  
+      animationFrameHandlers_;
+    
+    void OnResize();
+
+    std::vector<std::pair<OnWindowResizeFunc, void*> >
+      resizeHandlers_;
+    
+
+#if ORTHANC_ENABLE_SDL == 1
+
+    /**
+    This executes all the registered headers if needed (in wasm, the browser
+    deals with this)
+    */
+    void OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event);
+#endif
+
+    /**
+    This executes all the registered headers if needed (in wasm, the browser
+    deals with this)
+    */
+    void ViewportsUpdateSize();
+
+    std::vector<boost::weak_ptr<IGuiAdapterWidget>> widgets_;
+
+    template<typename F> void VisitWidgets(F func)
+    {
+      for (auto w : widgets_)
+      {
+        boost::shared_ptr<IGuiAdapterWidget> widget = w.lock();
+        func(widget);
+      }
+    }
+  };
+}
--- a/Framework/Messages/IMessageEmitter.h	Tue Jun 11 15:41:21 2019 +0200
+++ b/Framework/Messages/IMessageEmitter.h	Fri Jun 14 12:14:16 2019 +0200
@@ -26,6 +26,12 @@
 
 namespace OrthancStone
 {
+  /**
+   This class may be used to customize the way the messages are sent between
+   a source and a destination, for instance by the ThreadedOracle.
+
+   See the concrete class LockingEmitter for an example of when it is useful.
+   */
   class IMessageEmitter : public boost::noncopyable
   {
   public:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/LockingEmitter.h	Fri Jun 14 12:14:16 2019 +0200
@@ -0,0 +1,106 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "IMessageEmitter.h"
+#include "IObservable.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  /**
+   * This class is used when using the ThreadedOracle : since messages
+   * can be sent from multiple Oracle threads, this IMessageEmitter
+   * implementation serializes the callbacks.
+   * 
+   * The internal mutex used in Oracle messaging can also be used to 
+   * protect the application data. Thus, this class can be used as a single
+   * application-wide mutex.
+   */
+  class LockingEmitter : public IMessageEmitter
+  {
+  private:
+    boost::shared_mutex                mutex_;
+    MessageBroker        broker_;
+    IObservable          oracleObservable_;
+
+  public:
+    LockingEmitter() :
+      oracleObservable_(broker_)
+    {
+    }
+
+    virtual void EmitMessage(const IObserver& observer,
+      const IMessage& message) ORTHANC_OVERRIDE
+    {
+      try
+      {
+        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+        oracleObservable_.EmitMessage(observer, message);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while emitting a message: " << e.What();
+      }
+    }
+
+
+    class ReaderLock : public boost::noncopyable
+    {
+    private:
+      LockingEmitter& that_;
+      boost::shared_lock<boost::shared_mutex>  lock_;
+
+    public:
+      ReaderLock(LockingEmitter& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+    };
+
+
+    class WriterLock : public boost::noncopyable
+    {
+    private:
+      LockingEmitter& that_;
+      boost::unique_lock<boost::shared_mutex>  lock_;
+
+    public:
+      WriterLock(LockingEmitter& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+
+      MessageBroker& GetBroker()
+      {
+        return that_.broker_;
+      }
+
+      IObservable& GetOracleObservable()
+      {
+        return that_.oracleObservable_;
+      }
+    };
+  };
+}
--- a/Framework/Oracle/WebAssemblyOracle.cpp	Tue Jun 11 15:41:21 2019 +0200
+++ b/Framework/Oracle/WebAssemblyOracle.cpp	Fri Jun 14 12:14:16 2019 +0200
@@ -284,12 +284,7 @@
 
     void SetBody(std::string& body /* will be swapped */)
     {
-      if (body != "")
-      {
-        LOG(ERROR) << "Setting non-empty body. body size = " << body.size() << " body = " << body;
-      }
       body_.swap(body);
-      LOG(ERROR) << "After setting non-empty body. body_ size = " << body_.size() << " body_ = " << body_;
     }
 
     void SetHttpHeaders(const HttpHeaders& headers)
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Jun 11 15:41:21 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Jun 14 12:14:16 2019 +0200
@@ -315,6 +315,12 @@
     DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js")
 endif()
 
+if (ENABLE_SDL OR ENABLE_WASM)
+  list(APPEND APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.h
+    )
+endif()
 
 if (ENABLE_STONE_DEPRECATED)
   list(APPEND ORTHANC_STONE_SOURCES
@@ -368,6 +374,7 @@
 
 if (ENABLE_THREADS)
   list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h
     ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp
     )
 endif()