changeset 880:9953f16c304d am-dev

Merge
author Alain Mazy <alain@mazy.be>
date Fri, 05 Jul 2019 15:33:02 +0200
parents 12b591d5d63c (current diff) 4bc8d9609447 (diff)
children a8cd3755db21
files Applications/Generic/GuiAdapter.cpp Applications/Generic/GuiAdapter.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 23 files changed, 1294 insertions(+), 249 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Fri Jul 05 14:52:43 2019 +0200
+++ b/.hgtags	Fri Jul 05 15:33:02 2019 +0200
@@ -1,2 +1,6 @@
 90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19
 6d15261f9c9965a2f4b64658e318b370933b175e toa2019061901
+ff3559c489c98fad1ed174f7be919df6c20d36a9 toa2019062401
+c71ef52602a00dbb35f2b6bd7bd5ed516f1014fa toa2019062501
+fe96057e97b94eb8a46c1a33ba350c354b5c4afc toa2019062502
+60a403f01c3112249f9d4a1a6149bef1de9766bf toa2019062503
--- a/Applications/Generic/GuiAdapter.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Applications/Generic/GuiAdapter.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -44,7 +44,7 @@
   std::ostream& operator<<(
     std::ostream& os, const GuiAdapterKeyboardEvent& event)
   {
-    os << "ctrl: " << event.ctrlKey << ", " <<
+    os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " <<
       "shift: " << event.shiftKey << ", " <<
       "alt: " << event.altKey;
     return os;
@@ -112,10 +112,7 @@
     //dest.padding = src.padding;
   }
 
-  void ConvertFromPlatform(
-    GuiAdapterWheelEvent& dest,
-    int                         eventType,
-    const EmscriptenWheelEvent& src)
+  void ConvertFromPlatform( GuiAdapterWheelEvent& dest, int eventType, const EmscriptenWheelEvent& src)
   {
     ConvertFromPlatform(dest.mouse, eventType, src.mouse);
     dest.deltaX = src.deltaX;
@@ -138,15 +135,15 @@
     dest.deltaMode = src.deltaMode;
   }
 
-  void ConvertFromPlatform(
-    GuiAdapterKeyboardEvent& dest,
-    const EmscriptenKeyboardEvent& src)
+  void ConvertFromPlatform(GuiAdapterKeyboardEvent& dest, const EmscriptenKeyboardEvent& src)
   {
+    dest.sym[0] = src.key[0];
+    dest.sym[1] = 0;
     dest.ctrlKey = src.ctrlKey;
     dest.shiftKey = src.shiftKey;
     dest.altKey = src.altKey;
   }
-   
+
   template<typename GenericFunc>
   struct FuncAdapterPayload
   {
@@ -156,10 +153,10 @@
   };
 
   template<typename GenericFunc,
-           typename GuiAdapterEvent,
-           typename EmscriptenEvent>
-  EM_BOOL OnEventAdapterFunc(
-    int eventType, const EmscriptenEvent* emEvent, void* userData)
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent>
+    EM_BOOL OnEventAdapterFunc(
+      int eventType, const EmscriptenEvent* emEvent, void* userData)
   {
 
     // userData is OnMouseWheelFuncAdapterPayload
@@ -170,7 +167,7 @@
     // LOG(INFO) << "eventType: " << eventType << " wheelEvent: " << 
     //   (int)wheelEvent << " userData: " << userData << 
     //   " payload->userData: " << payload->userData;
-    
+
     GuiAdapterEvent guiEvent;
     ConvertFromPlatform(guiEvent, eventType, *emEvent);
     bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData);
@@ -186,7 +183,7 @@
     // userData is OnMouseWheelFuncAdapterPayload
     FuncAdapterPayload<GenericFunc>* payload =
       reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
-    
+
     GuiAdapterEvent guiEvent;
     ConvertFromPlatform(guiEvent, *wheelEvent);
     bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData);
@@ -194,8 +191,8 @@
   }
 
   template<typename GenericFunc>
-    EM_BOOL OnEventAdapterFunc3(
-      double time, void* userData)
+  EM_BOOL OnEventAdapterFunc3(
+    double time, void* userData)
   {
     // userData is OnMouseWheelFuncAdapterPayload
     FuncAdapterPayload<GenericFunc>* payload =
@@ -207,17 +204,17 @@
 
   // resize: (const char* target, void* userData, EM_BOOL useCapture, em_ui_callback_func callback)
   template<
-    typename GenericFunc, 
-    typename GuiAdapterEvent, 
-    typename EmscriptenEvent, 
+    typename GenericFunc,
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent,
     typename EmscriptenSetCallbackFunc>
-  static void SetCallback(
-    EmscriptenSetCallbackFunc emFunc, 
-    std::string canvasId, void* userData, bool capture, GenericFunc func)
+    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 = 
+    FuncAdapterPayload<GenericFunc>* payload =
       new FuncAdapterPayload<GenericFunc>();
     std::auto_ptr<FuncAdapterPayload<GenericFunc> > payloadP(payload);
     payload->canvasId = canvasId;
@@ -397,76 +394,107 @@
 
 #else
 
-// SDL ONLY
-void ConvertFromPlatform(
-  GuiAdapterMouseEvent& dest,
-  bool ctrlPressed, bool shiftPressed, bool altPressed,
-  const SDL_Event& source)
-{
-  memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
-  switch (source.type)
+  // SDL ONLY
+  void ConvertFromPlatform(GuiAdapterMouseEvent& dest, bool ctrlPressed, bool shiftPressed, bool altPressed, const SDL_Event& source)
   {
-  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;
-  case SDL_MOUSEWHEEL:
-    dest.type = GUIADAPTER_EVENT_WHEEL;
-    break;
-  default:
-    LOG(ERROR) << "SDL event: " << source.type << " is not supported";
-    ORTHANC_ASSERT(false, "Not supported");
+    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;
+    case SDL_MOUSEWHEEL:
+      dest.type = GUIADAPTER_EVENT_WHEEL;
+      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 = GUIADAPTER_MOUSEBUTTON_MIDDLE;
-    break;
+    //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 =GUIADAPTER_MOUSEBUTTON_MIDDLE;
+      break;
 
-  case SDL_BUTTON_RIGHT:
-    dest.button = GUIADAPTER_MOUSEBUTTON_RIGHT;
-    break;
+    case SDL_BUTTON_RIGHT:
+      dest.button = GUIADAPTER_MOUSEBUTTON_RIGHT;
+      break;
 
-  case SDL_BUTTON_LEFT:
-    dest.button = GUIADAPTER_MOUSEBUTTON_LEFT;
-    break;
+    case SDL_BUTTON_LEFT:
+      dest.button = GUIADAPTER_MOUSEBUTTON_LEFT;
+      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;
+    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 ConvertFromPlatform(
-  GuiAdapterWheelEvent& dest,
-  bool ctrlPressed, bool shiftPressed, bool altPressed,
-  const SDL_Event& source)
-{
-  ConvertFromPlatform(dest.mouse, ctrlPressed, shiftPressed, altPressed, source);
-  dest.deltaX = source.wheel.x;
-  dest.deltaY = source.wheel.y;
-}
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent& dest,
+    bool ctrlPressed, bool shiftPressed, bool altPressed,
+    const SDL_Event& source)
+  {
+    ConvertFromPlatform(dest.mouse, ctrlPressed, shiftPressed, altPressed, source);
+    dest.deltaX = source.wheel.x;
+    dest.deltaY = source.wheel.y;
+  }
+
+  void ConvertFromPlatform(GuiAdapterKeyboardEvent& dest, const SDL_Event& src)
+  {
+    memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
+    switch (src.type)
+    {
+    case SDL_KEYDOWN:
+      dest.type = GUIADAPTER_EVENT_KEYDOWN;
+      break;
+    case SDL_KEYUP:
+      dest.type = GUIADAPTER_EVENT_KEYUP;
+      break;
+    default:
+      LOG(ERROR) << "SDL event: " << src.type << " is not supported";
+      ORTHANC_ASSERT(false, "Not supported");
+    }
+    dest.sym[0] = src.key.keysym.sym;
+    dest.sym[1] = 0;
+
+    if (src.key.keysym.mod & KMOD_CTRL)
+      dest.ctrlKey = true;
+    else
+      dest.ctrlKey = false;
+
+    if (src.key.keysym.mod & KMOD_SHIFT)
+      dest.shiftKey = true;
+    else
+      dest.shiftKey = false;
+
+    if (src.key.keysym.mod & KMOD_ALT)
+      dest.altKey = true;
+    else
+      dest.altKey = false;
+  }
 
 
 
@@ -480,43 +508,44 @@
   // SDL ONLY
   void GuiAdapter::SetMouseDownCallback(
     std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
- {
+  {
     mouseDownHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
- }
+  }
 
   // SDL ONLY
   void GuiAdapter::SetMouseMoveCallback(
     std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
- {
+  {
     mouseMoveHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
   }
 
   // SDL ONLY
   void GuiAdapter::SetMouseUpCallback(
     std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
- {
+  {
     mouseUpHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
   }
 
   // SDL ONLY
   void GuiAdapter::SetWheelCallback(
-   std::string canvasId, void* userData, bool capture, OnMouseWheelFunc  func)
+    std::string canvasId, void* userData, bool capture, OnMouseWheelFunc  func)
   {
     mouseWheelHandlers_.push_back(EventHandlerData<OnMouseWheelFunc>(canvasId, func, userData));
   }
 
   // SDL ONLY
   void GuiAdapter::SetKeyDownCallback(
-   std::string canvasId, void* userData, bool capture, OnKeyDownFunc   func)
- {
- }
+    std::string canvasId, void* userData, bool capture, OnKeyDownFunc   func)
+  {
+    keyDownHandlers_.push_back(EventHandlerData<OnKeyDownFunc>(canvasId, func, userData));
+  }
 
   // SDL ONLY
   void GuiAdapter::SetKeyUpCallback(
-   std::string canvasId, void* userData, bool capture, OnKeyUpFunc    func)
- {
- }
-
+    std::string canvasId, void* userData, bool capture, OnKeyUpFunc    func)
+  {
+    keyUpHandlers_.push_back(EventHandlerData<OnKeyUpFunc>(canvasId, func, userData));
+  }
 
   // SDL ONLY
   void GuiAdapter::OnAnimationFrame()
@@ -558,7 +587,7 @@
     case GUIADAPTER_EVENT_WHEEL:
       for (size_t i = 0; i < mouseWheelHandlers_.size(); i++)
       {
-        if(mouseWheelHandlers_[i].canvasName == windowTitle)
+        if (mouseWheelHandlers_[i].canvasName == windowTitle)
           (*(mouseWheelHandlers_[i].func))(windowTitle, &event, mouseWheelHandlers_[i].userData);
       }
       break;
@@ -568,14 +597,16 @@
     }
   }
 
-  // SDL ONLY
-  void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event)
+
+  void GuiAdapter::OnKeyboardEvent(uint32_t windowID, const GuiAdapterKeyboardEvent& event)
   {
-    // the SDL window name IS the canvas name ("canvas" is used because this lib
-    // is designed for Wasm
+    // only one-letter (ascii) keyboard events supported for now
+    ORTHANC_ASSERT(event.sym[0] != 0);
+    ORTHANC_ASSERT(event.sym[1] == 0);
+
     SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
     ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!");
-     
+
     const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow);
     ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!");
 
@@ -584,42 +615,84 @@
 
     switch (event.type)
     {
-    case GUIADAPTER_EVENT_MOUSEDOWN:
-      for (size_t i = 0; i < mouseDownHandlers_.size(); i++)
+    case GUIADAPTER_EVENT_KEYDOWN:
+      for (size_t i = 0; i < keyDownHandlers_.size(); i++)
       {
-        if (mouseDownHandlers_[i].canvasName == windowTitle)
-          (*(mouseDownHandlers_[i].func))(windowTitle, &event, mouseDownHandlers_[i].userData);
+        (*(keyDownHandlers_[i].func))(windowTitle, &event, keyDownHandlers_[i].userData);
       }
       break;
-    case GUIADAPTER_EVENT_MOUSEMOVE:
-      for (size_t i = 0; i < mouseMoveHandlers_.size(); i++)
+    case GUIADAPTER_EVENT_KEYUP:
+      for (size_t i = 0; i < keyUpHandlers_.size(); i++)
       {
-        if (mouseMoveHandlers_[i].canvasName == windowTitle)
-          (*(mouseMoveHandlers_[i].func))(windowTitle, &event, mouseMoveHandlers_[i].userData);
-      }
-      break;
-    case GUIADAPTER_EVENT_MOUSEUP:
-      for (size_t i = 0; i < mouseUpHandlers_.size(); i++)
-      {
-        if (mouseUpHandlers_[i].canvasName == windowTitle)
-          (*(mouseUpHandlers_[i].func))(windowTitle, &event, mouseUpHandlers_[i].userData);
+        (*(keyUpHandlers_[i].func))(windowTitle, &event, keyUpHandlers_[i].userData);
       }
       break;
     default:
-      ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnMouseEvent(...)");
+      ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnKeyboardEvent(...)");
       break;
     }
+  }
 
-    ////boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId();
-    //boost::shared_ptr<IGuiAdapterWidget> foundWidget;
-    //VisitWidgets([foundWidget, windowID](auto widget)
-    //  {
-    //    if (widget->GetSdlWindowID() == windowID)
-    //      foundWidget = widget;
-    //  });
-    //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!");
-    //if(foundWidget)
-    //  foundWidget->
+  // SDL ONLY
+  void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event)
+  {
+    if (windowID == 0)
+    {
+      LOG(WARNING) << "GuiAdapter::OnMouseEvent -- windowID == 0 and event won't be routed!";
+    }
+    else
+    {
+      // the SDL window name IS the canvas name ("canvas" is used because this lib
+      // is designed for Wasm
+      SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
+
+      ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!");
+
+      const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow);
+      ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!");
+
+      std::string windowTitle(windowTitleSz);
+      ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!");
+
+      switch (event.type)
+      {
+      case GUIADAPTER_EVENT_MOUSEDOWN:
+        for (size_t i = 0; i < mouseDownHandlers_.size(); i++)
+        {
+          if (mouseDownHandlers_[i].canvasName == windowTitle)
+            (*(mouseDownHandlers_[i].func))(windowTitle, &event, mouseDownHandlers_[i].userData);
+        }
+        break;
+      case GUIADAPTER_EVENT_MOUSEMOVE:
+        for (size_t i = 0; i < mouseMoveHandlers_.size(); i++)
+        {
+          if (mouseMoveHandlers_[i].canvasName == windowTitle)
+            (*(mouseMoveHandlers_[i].func))(windowTitle, &event, mouseMoveHandlers_[i].userData);
+        }
+        break;
+      case GUIADAPTER_EVENT_MOUSEUP:
+        for (size_t i = 0; i < mouseUpHandlers_.size(); i++)
+        {
+          if (mouseUpHandlers_[i].canvasName == windowTitle)
+            (*(mouseUpHandlers_[i].func))(windowTitle, &event, mouseUpHandlers_[i].userData);
+        }
+        break;
+      default:
+        ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnMouseEvent(...)");
+        break;
+      }
+
+      ////boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId();
+      //boost::shared_ptr<IGuiAdapterWidget> foundWidget;
+      //VisitWidgets([foundWidget, windowID](auto widget)
+      //  {
+      //    if (widget->GetSdlWindowID() == windowID)
+      //      foundWidget = widget;
+      //  });
+      //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!");
+      //if(foundWidget)
+      //  foundWidget->
+    }
   }
 
   // SDL ONLY
@@ -637,7 +710,7 @@
       GLuint id,
       GLenum severity,
       GLsizei length,
-      const GLchar* message,
+      const GLchar * message,
       const void* userParam)
   {
     if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
@@ -646,7 +719,7 @@
         (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
         type, severity, message);
     }
-}
+  }
 # endif
 
   // SDL ONLY
@@ -680,16 +753,16 @@
           stop = true;
           break;
         }
-        else if ( (event.type == SDL_MOUSEMOTION) ||
-                  (event.type == SDL_MOUSEBUTTONDOWN) ||
-                  (event.type == SDL_MOUSEBUTTONUP) )
+        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])
@@ -700,7 +773,7 @@
             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);
@@ -768,23 +841,14 @@
             // 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:
+            GuiAdapterKeyboardEvent dest;
+            ConvertFromPlatform(dest, event);
+            OnKeyboardEvent(event.window.windowID, dest);
             break;
           }
         }
--- a/Applications/Generic/GuiAdapter.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Applications/Generic/GuiAdapter.h	Fri Jul 05 15:33:02 2019 +0200
@@ -75,12 +75,14 @@
   };
 
 
-  enum GuiAdapterMouseEventType
+  enum GuiAdapterHidEventType
   {
     GUIADAPTER_EVENT_MOUSEDOWN = 1973,
     GUIADAPTER_EVENT_MOUSEMOVE = 1974,
     GUIADAPTER_EVENT_MOUSEUP   = 1975,
-    GUIADAPTER_EVENT_WHEEL     = 1976
+    GUIADAPTER_EVENT_WHEEL     = 1976,
+    GUIADAPTER_EVENT_KEYDOWN   = 1977,
+    GUIADAPTER_EVENT_KEYUP     = 1978,
   };
 
   const unsigned int GUIADAPTER_DELTA_PIXEL = 2973;
@@ -124,8 +126,9 @@
 #endif
 
 #endif
-  struct GuiAdapterMouseEvent {
-    GuiAdapterMouseEventType type;
+  struct GuiAdapterMouseEvent
+  {
+    GuiAdapterHidEventType type;
     //double                   timestamp;
     //long                     screenX;
     //long                     screenY;
@@ -168,6 +171,8 @@
   // EmscriptenKeyboardEvent
   struct GuiAdapterKeyboardEvent
   {
+    GuiAdapterHidEventType type;
+    char sym[32];
     bool ctrlKey;
     bool shiftKey;
     bool altKey;
@@ -193,37 +198,21 @@
     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(GuiAdapterUiEvent& dest, int eventType, const EmscriptenUiEvent& src);
 
-  void ConvertFromPlatform(
-    GuiAdapterMouseEvent&       dest,
-    int                         eventType,
-    const EmscriptenMouseEvent& src);
+  void ConvertFromPlatform(GuiAdapterMouseEvent& dest, int eventType, const EmscriptenMouseEvent& src);
   
-  void ConvertFromPlatform(
-    GuiAdapterWheelEvent&       dest,
-    int                         eventType,
-    const EmscriptenWheelEvent& src);
+  void ConvertFromPlatform(GuiAdapterWheelEvent& dest, int eventType, const EmscriptenWheelEvent& src);
 
-  void ConvertFromPlatform(
-    GuiAdapterKeyboardEvent&       dest,
-    const EmscriptenKeyboardEvent& 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);
+  void ConvertFromPlatform(GuiAdapterMouseEvent& dest, bool ctrlPressed, bool shiftPressed, bool altPressed, const SDL_Event& source);
 
-  void ConvertFromPlatform(
-    GuiAdapterWheelEvent& dest,
-    bool ctrlPressed, bool shiftPressed, bool altPressed,
-    const SDL_Event& source);
+  void ConvertFromPlatform(GuiAdapterWheelEvent& dest, bool ctrlPressed, bool shiftPressed, bool altPressed, const SDL_Event& source);
+
+  void ConvertFromPlatform(GuiAdapterKeyboardEvent& dest, const SDL_Event& source);
 
 # endif
 
@@ -342,14 +331,17 @@
     std::vector<EventHandlerData<OnMouseEventFunc  > > mouseMoveHandlers_;
     std::vector<EventHandlerData<OnMouseEventFunc  > > mouseUpHandlers_;
     std::vector<EventHandlerData<OnMouseWheelFunc  > > mouseWheelHandlers_;
-    
+    std::vector<EventHandlerData<OnKeyDownFunc > > keyDownHandlers_;
+    std::vector<EventHandlerData<OnKeyUpFunc > > keyUpHandlers_;
 
     /**
     This executes all the registered headers if needed (in wasm, the browser
     deals with this)
     */
     void OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event);
-    
+
+    void OnKeyboardEvent(uint32_t windowID, const GuiAdapterKeyboardEvent& event);
+
     /**
     Same remark as OnMouseEvent
     */
--- a/Framework/Scene2D/ScenePoint2D.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2D/ScenePoint2D.h	Fri Jul 05 15:33:02 2019 +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,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/AffineTransform2D.h"
-
+#include "../Toolbox/LinearAlgebra.h"
 
 namespace OrthancStone
 {
@@ -40,7 +40,7 @@
     }
 
     ScenePoint2D(double x,
-                 double y) :
+      double y) :
       x_(x),
       y_(y)
     {
@@ -63,5 +63,83 @@
       t.Apply(x, y);
       return ScenePoint2D(x, y);
     }
+
+    const ScenePoint2D operator-(const ScenePoint2D& a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ - a.x_;
+      v.y_ = y_ - a.y_;
+
+      return v;
+    }
+
+    const ScenePoint2D operator+(const ScenePoint2D& a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ + a.x_;
+      v.y_ = y_ + a.y_;
+
+      return v;
+    }
+
+    const ScenePoint2D operator*(double a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ * a;
+      v.y_ = y_ * a;
+
+      return v;
+    }
+
+    static double Dot(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      return a.x_ * b.x_ + a.y_ * b.y_;
+    }
+
+    static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      ScenePoint2D n = b - a;
+      return Dot(n, n);
+    }
+
+    /**
+    Distance from point p to [a,b] segment
+
+    Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
+    */
+    static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
+    {
+      ScenePoint2D n = b - a;
+      ScenePoint2D pa = a - p;
+
+      double c = Dot(n, pa);
+
+      // Closest point is a
+      if (c > 0.0)
+        return Dot(pa, pa);
+
+      ScenePoint2D bp = p - b;
+
+      // Closest point is b
+      if (Dot(n, bp) > 0.0)
+        return Dot(bp, bp);
+
+      // if segment length is very short, we approximate distance to the
+      // distance with a
+      double nq = Dot(n, n);
+      if (LinearAlgebra::IsCloseToZero(nq))
+      {
+        // segment is very small: approximate distance from point to segment
+        // with distance from p to a
+        return Dot(pa, pa);
+      }
+      else
+      {
+        // Closest point is between a and b
+        ScenePoint2D e = pa - n * (c / nq);
+        return Dot(e, e);
+      }
+    }
   };
 }
+
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -20,6 +20,7 @@
 
 #include "AngleMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "EditAngleMeasureTracker.h"
 #include "LayerHolder.h"
 
 #include <Core/Logging.h>
@@ -43,6 +44,7 @@
     MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
     : MeasureTool(broker, controllerW)
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5))
+    , angleHighlightArea_(AngleHighlightArea_None)
   {
 
   }
@@ -75,10 +77,109 @@
     RefreshScene();
   }
 
+  void AngleMeasureTool::SetAngleHighlightArea(AngleHighlightArea area)
+  {
+    if (angleHighlightArea_ != area)
+    {
+      angleHighlightArea_ = area;
+      RefreshScene();
+    }
+  }
+
+  void AngleMeasureTool::ResetHighlightState()
+  {
+    SetAngleHighlightArea(AngleHighlightArea_None);
+  }
+
+
+  boost::shared_ptr<OrthancStone::MeasureToolMemento> AngleMeasureTool::GetMemento() const
+  {
+    boost::shared_ptr<AngleMeasureToolMemento> memento(new AngleMeasureToolMemento());
+    memento->center_ = center_;
+    memento->side1End_ = side1End_;
+    memento->side2End_ = side2End_;
+    return memento;
+  }
+  
+  void AngleMeasureTool::SetMemento(boost::shared_ptr<MeasureToolMemento> mementoBase)
+  {
+    boost::shared_ptr<AngleMeasureToolMemento> memento = boost::dynamic_pointer_cast<AngleMeasureToolMemento>(mementoBase);
+    ORTHANC_ASSERT(memento.get() != NULL, "Internal error: wrong (or bad) memento");
+    center_   = memento->center_;
+    side1End_ = memento->side1End_;
+    side2End_ = memento->side2End_;
+    RefreshScene();
+  }
+
+  void AngleMeasureTool::Highlight(ScenePoint2D p)
+  {
+    AngleHighlightArea angleHighlightArea = AngleHitTest(p);
+    SetAngleHighlightArea(angleHighlightArea);
+  }
+
+  AngleMeasureTool::AngleHighlightArea AngleMeasureTool::AngleHitTest(ScenePoint2D p) const
+  {
+    const double pixelToScene =
+      GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+    const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
+
+    {
+      const double sqDistanceFromSide1End = ScenePoint2D::SquaredDistancePtPt(p, side1End_);
+      if (sqDistanceFromSide1End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side1End;
+    }
+
+    {
+      const double sqDistanceFromSide2End = ScenePoint2D::SquaredDistancePtPt(p, side2End_);
+      if (sqDistanceFromSide2End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side2End;
+    }
+
+    {
+      const double sqDistanceFromCenter = ScenePoint2D::SquaredDistancePtPt(p, center_);
+      if (sqDistanceFromCenter <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Center;
+    }
+
+    {
+      const double sqDistanceFromSide1 = ScenePoint2D::SquaredDistancePtSegment(center_, side1End_, p);
+      if (sqDistanceFromSide1 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side1;
+    }
+
+    {
+      const double sqDistanceFromSide2 = ScenePoint2D::SquaredDistancePtSegment(center_, side2End_, p);
+      if (sqDistanceFromSide2 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side2;
+    }
+
+    return AngleHighlightArea_None;
+  }
 
   bool AngleMeasureTool::HitTest(ScenePoint2D p) const
   {
-    throw std::logic_error("The method or operation is not implemented.");
+    return AngleHitTest(p) != AngleHighlightArea_None;
+  }
+
+
+  boost::shared_ptr<IFlexiblePointerTracker> AngleMeasureTool::CreateEditionTracker(const PointerEvent& e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    if (!HitTest(scenePos))
+      return boost::shared_ptr<IFlexiblePointerTracker>();
+
+    /**
+      new EditLineMeasureTracker(
+        boost::shared_ptr<LineMeasureTool> measureTool;
+        MessageBroker & broker,
+        boost::weak_ptr<ViewportController>          controllerW,
+        const PointerEvent & e);
+    */
+    boost::shared_ptr<EditAngleMeasureTracker> editAngleMeasureTracker(
+      new EditAngleMeasureTracker(shared_from_this(), GetBroker(), GetController(), e));
+    return editAngleMeasureTracker;
   }
 
   void AngleMeasureTool::SetCenter(ScenePoint2D pt)
@@ -101,7 +202,8 @@
           PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
           polylineLayer->ClearAllChains();
 
-          const Color color(0, 183, 17);
+          const Color color(TOOL_ANGLE_LINES_COLOR_RED, TOOL_ANGLE_LINES_COLOR_GREEN, TOOL_ANGLE_LINES_COLOR_BLUE);
+          const Color highlightColor(TOOL_ANGLE_LINES_HL_COLOR_RED, TOOL_ANGLE_LINES_HL_COLOR_GREEN, TOOL_ANGLE_LINES_HL_COLOR_BLUE);
 
           // sides
           {
@@ -109,13 +211,20 @@
               PolylineSceneLayer::Chain chain;
               chain.push_back(side1End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false, color);
+
+              if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2))
+                polylineLayer->AddChain(chain, false, highlightColor);
+              else
+                polylineLayer->AddChain(chain, false, color);
             }
             {
               PolylineSceneLayer::Chain chain;
               chain.push_back(side2End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false, color);
+              if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2))
+                polylineLayer->AddChain(chain, false, highlightColor);
+              else
+                polylineLayer->AddChain(chain, false, color);
             }
           }
 
@@ -126,14 +235,23 @@
               //TODO: take DPI into account
               AddSquare(chain, GetScene(), side1End_, 
                 GetController()->GetHandleSideLengthS());
-              polylineLayer->AddChain(chain, true, color);
+              
+              if (angleHighlightArea_ == AngleHighlightArea_Side1End)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
+              
             }
             {
               PolylineSceneLayer::Chain chain;
               //TODO: take DPI into account
               AddSquare(chain, GetScene(), side2End_, 
                 GetController()->GetHandleSideLengthS());
-              polylineLayer->AddChain(chain, true, color);
+
+              if (angleHighlightArea_ == AngleHighlightArea_Side2End)
+                  polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
           }
 
@@ -143,7 +261,10 @@
 
             AddShortestArc(chain, side1End_, center_, side2End_,
                            controller->GetAngleToolArcRadiusS());
-            polylineLayer->AddChain(chain, false, color);
+            if (angleHighlightArea_ == AngleHighlightArea_Center)
+              polylineLayer->AddChain(chain, false, highlightColor);
+            else
+              polylineLayer->AddChain(chain, false, color);
           }
         }
         {
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Fri Jul 05 15:33:02 2019 +0200
@@ -30,13 +30,14 @@
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
 
 #include <vector>
 #include <cmath>
 
 namespace OrthancStone
 {
-  class AngleMeasureTool : public MeasureTool
+  class AngleMeasureTool : public MeasureTool, public boost::enable_shared_from_this<AngleMeasureTool>
   {
   public:
     AngleMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
@@ -47,18 +48,45 @@
     void SetCenter(ScenePoint2D start);
     void SetSide2End(ScenePoint2D start);
 
+    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE;
+    virtual void ResetHighlightState() ORTHANC_OVERRIDE;
+    virtual boost::shared_ptr<IFlexiblePointerTracker> CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual boost::shared_ptr<MeasureToolMemento> GetMemento() const ORTHANC_OVERRIDE;
+    virtual void SetMemento(boost::shared_ptr<MeasureToolMemento>) ORTHANC_OVERRIDE;
 
-    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    enum AngleHighlightArea
+    {
+      AngleHighlightArea_None,
+      AngleHighlightArea_Side1End,
+      AngleHighlightArea_Side1,
+      AngleHighlightArea_Side2End,
+      AngleHighlightArea_Side2,
+      AngleHighlightArea_Center
+    };
+
+
+    AngleHighlightArea AngleHitTest(ScenePoint2D p) const;
 
   private:
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
+    void                SetAngleHighlightArea(AngleHighlightArea area);
 
   private:
-    ScenePoint2D    side1End_;
-    ScenePoint2D    side2End_;
-    ScenePoint2D    center_;
+    ScenePoint2D                    side1End_;
+    ScenePoint2D                    side2End_;
+    ScenePoint2D                    center_;
     boost::shared_ptr<LayerHolder>  layerHolder_;
+    AngleHighlightArea              angleHighlightArea_;
+  };
+
+  class AngleMeasureToolMemento : public MeasureToolMemento
+  {
+  public:
+    ScenePoint2D                    side1End_;
+    ScenePoint2D                    side2End_;
+    ScenePoint2D                    center_;
   };
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -0,0 +1,112 @@
+/**
+ * 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 "EditAngleMeasureTracker.h"
+
+namespace OrthancStone
+{
+  EditAngleMeasureTracker::EditAngleMeasureTracker(
+    boost::shared_ptr<AngleMeasureTool>  measureTool,
+    MessageBroker& broker,
+    boost::weak_ptr<ViewportController> controllerW,
+    const PointerEvent& e)
+    : EditMeasureTracker(controllerW, e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    modifiedZone_ = measureTool->AngleHitTest(scenePos);
+
+    command_.reset(new EditAngleMeasureCommand(measureTool, broker, controllerW));
+  }
+
+  EditAngleMeasureTracker::~EditAngleMeasureTracker()
+  {
+
+  }
+
+  void EditAngleMeasureTracker::PointerMove(const PointerEvent& e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    ScenePoint2D delta = scenePos - GetOriginalClickPosition();
+
+    boost::shared_ptr<AngleMeasureToolMemento> memento =
+      boost::dynamic_pointer_cast<AngleMeasureToolMemento>(command_->mementoOriginal_);
+
+    ORTHANC_ASSERT(memento.get() != NULL);
+
+    switch (modifiedZone_)
+    {
+    case AngleMeasureTool::AngleHighlightArea_Center:
+    {
+      ScenePoint2D newCenter = memento->center_ + delta;
+      GetCommand()->SetCenter(newCenter);
+    }
+    break;
+    case AngleMeasureTool::AngleHighlightArea_Side1:
+    case AngleMeasureTool::AngleHighlightArea_Side2:
+    {
+      ScenePoint2D newCenter = memento->center_ + delta;
+      ScenePoint2D newSide1End = memento->side1End_ + delta;
+      ScenePoint2D newSide2End = memento->side2End_ + delta;
+      GetCommand()->SetCenter(newCenter);
+      GetCommand()->SetSide1End(newSide1End);
+      GetCommand()->SetSide2End(newSide2End);
+    }
+    break;
+    case AngleMeasureTool::AngleHighlightArea_Side1End:
+    {
+      ScenePoint2D newSide1End = memento->side1End_ + delta;
+      GetCommand()->SetSide1End(newSide1End);
+    }
+    break;
+    case AngleMeasureTool::AngleHighlightArea_Side2End:
+    {
+      ScenePoint2D newSide2End = memento->side2End_ + delta;
+      GetCommand()->SetSide2End(newSide2End);
+    }
+    break;
+    default:
+      LOG(WARNING) << "Warning: please retry the measuring tool editing operation!";
+      break;
+    }
+  }
+
+  void EditAngleMeasureTracker::PointerUp(const PointerEvent& e)
+  {
+    alive_ = false;
+  }
+
+  void EditAngleMeasureTracker::PointerDown(const PointerEvent& e)
+  {
+    LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) "
+      "are ignored when the edit angle tracker is active";
+  }
+
+  boost::shared_ptr<EditAngleMeasureCommand> EditAngleMeasureTracker::GetCommand()
+  {
+    boost::shared_ptr<EditAngleMeasureCommand> ret = boost::dynamic_pointer_cast<EditAngleMeasureCommand>(command_);
+    ORTHANC_ASSERT(ret.get() != NULL, "Internal error in EditAngleMeasureTracker::GetCommand()");
+    return ret;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h	Fri Jul 05 15:33:02 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * 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 "MeasureTrackers.h"
+
+namespace OrthancStone
+{
+  class EditAngleMeasureTracker : public EditMeasureTracker
+  {
+  public:
+    /**
+    When you create this tracker, you need to supply it with the undo stack
+    where it will store the commands that perform the actual measure tool
+    creation and modification.
+    In turn, a container for these commands to store the actual measuring
+    must be supplied, too
+    */
+    EditAngleMeasureTracker(
+      boost::shared_ptr<AngleMeasureTool>  measureTool,
+      MessageBroker& broker,
+      boost::weak_ptr<ViewportController> controllerW,
+      const PointerEvent& e);
+
+    ~EditAngleMeasureTracker();
+
+    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    AngleMeasureTool::AngleHighlightArea modifiedZone_;
+
+    boost::shared_ptr<EditAngleMeasureCommand> GetCommand();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -0,0 +1,107 @@
+/**
+ * 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 "EditLineMeasureTracker.h"
+
+namespace OrthancStone
+{
+  EditLineMeasureTracker::EditLineMeasureTracker(
+    boost::shared_ptr<LineMeasureTool>  measureTool,
+    MessageBroker& broker,
+    boost::weak_ptr<ViewportController> controllerW,
+    const PointerEvent& e) 
+    : EditMeasureTracker(controllerW, e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    modifiedZone_ = measureTool->LineHitTest(scenePos);
+
+    command_.reset(
+      new EditLineMeasureCommand(
+        measureTool,
+        broker,
+        controllerW));
+  }
+
+  EditLineMeasureTracker::~EditLineMeasureTracker()
+  {
+
+  }
+
+  void EditLineMeasureTracker::PointerMove(const PointerEvent& e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    ScenePoint2D delta = scenePos - GetOriginalClickPosition();
+
+    boost::shared_ptr<LineMeasureToolMemento> memento =
+      boost::dynamic_pointer_cast<LineMeasureToolMemento>(command_->mementoOriginal_);
+
+    ORTHANC_ASSERT(memento.get() != NULL);
+
+    switch (modifiedZone_)
+    {
+    case LineMeasureTool::LineHighlightArea_Start:
+    {
+      ScenePoint2D newStart = memento->start_ + delta;
+      GetCommand()->SetStart(newStart);
+    }
+    break;
+    case LineMeasureTool::LineHighlightArea_End:
+    {
+      ScenePoint2D newEnd = memento->end_ + delta;
+      GetCommand()->SetEnd(newEnd);
+    }
+    break;
+    case LineMeasureTool::LineHighlightArea_Segment:
+    {
+      ScenePoint2D newStart = memento->start_ + delta;
+      ScenePoint2D newEnd = memento->end_ + delta;
+      GetCommand()->SetStart(newStart);
+      GetCommand()->SetEnd(newEnd);
+    }
+    break;
+    default:
+      LOG(WARNING) << "Warning: please retry the measuring tool editing operation!";
+        break;
+    }
+  }
+
+  void EditLineMeasureTracker::PointerUp(const PointerEvent& e)
+  {
+    alive_ = false;
+  }
+
+  void EditLineMeasureTracker::PointerDown(const PointerEvent& e)
+  {
+    LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) "
+      "are ignored when the edit line tracker is active";
+  }
+
+  boost::shared_ptr<EditLineMeasureCommand> EditLineMeasureTracker::GetCommand()
+  {
+    boost::shared_ptr<EditLineMeasureCommand> ret = boost::dynamic_pointer_cast<EditLineMeasureCommand>(command_);
+    ORTHANC_ASSERT(ret.get() != NULL, "Internal error in EditLineMeasureTracker::GetCommand()");
+    return ret;
+  }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h	Fri Jul 05 15:33:02 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * 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 "MeasureTrackers.h"
+
+namespace OrthancStone
+{
+  class EditLineMeasureTracker : public EditMeasureTracker
+  {
+  public:
+    /**
+    When you create this tracker, you need to supply it with the undo stack
+    where it will store the commands that perform the actual measure tool
+    creation and modification.
+    In turn, a container for these commands to store the actual measuring
+    must be supplied, too
+    */
+    EditLineMeasureTracker(
+      boost::shared_ptr<LineMeasureTool>  measureTool,
+      MessageBroker&                      broker,
+      boost::weak_ptr<ViewportController> controllerW,
+      const PointerEvent&                 e);
+
+    ~EditLineMeasureTracker();
+
+    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    LineMeasureTool::LineHighlightArea modifiedZone_;
+
+    boost::shared_ptr<EditLineMeasureCommand> GetCommand();
+  };
+}
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -20,6 +20,7 @@
 
 #include "LineMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "EditLineMeasureTracker.h"
 #include "LayerHolder.h"
 
 #include <Core/Logging.h>
@@ -33,6 +34,7 @@
     MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
     : MeasureTool(broker, controllerW)
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5))
+    , lineHighlightArea_(LineHighlightArea_None)
   {
 
   }
@@ -72,26 +74,88 @@
     RefreshScene();
   }
 
-  
+  void LineMeasureTool::SetLineHighlightArea(LineHighlightArea area)
+  {
+    if (lineHighlightArea_ != area)
+    {
+      lineHighlightArea_ = area;
+      RefreshScene();
+    }
+  }
 
-  bool LineMeasureTool::HitTest(ScenePoint2D p) const
+  void LineMeasureTool::ResetHighlightState()
+  {
+    SetLineHighlightArea(LineHighlightArea_None);
+  }
+ 
+  void LineMeasureTool::Highlight(ScenePoint2D p)
+  {
+    LineHighlightArea lineHighlightArea = LineHitTest(p);
+    SetLineHighlightArea(lineHighlightArea);
+  }
+
+  LineMeasureTool::LineHighlightArea LineMeasureTool::LineHitTest(ScenePoint2D p) const
   {
     const double pixelToScene =
       GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+    const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
 
-    // the hit test will return true if the supplied point (in scene coords)
-    // is close to the handle or to the line.
+    const double sqDistanceFromStart = ScenePoint2D::SquaredDistancePtPt(p, start_);
+    if (sqDistanceFromStart <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_Start;
+    
+    const double sqDistanceFromEnd = ScenePoint2D::SquaredDistancePtPt(p, end_);
+    if (sqDistanceFromEnd <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_End;
+
+    const double sqDistanceFromPtSegment = ScenePoint2D::SquaredDistancePtSegment(start_, end_, p);
+    if (sqDistanceFromPtSegment <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_Segment;
+
+    return LineHighlightArea_None;
+  }
 
-    // since the handle is small, a nice approximation is to defined this
-    // as a threshold on the distance between the point and the handle center.
+  bool LineMeasureTool::HitTest(ScenePoint2D p) const
+  {
+    return LineHitTest(p) != LineHighlightArea_None;
+  }
+
+  boost::shared_ptr<IFlexiblePointerTracker> LineMeasureTool::CreateEditionTracker(const PointerEvent& e)
+  {
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
 
-    // this threshold is defined as a constant value in CANVAS units.
+    if (!HitTest(scenePos))
+      return boost::shared_ptr<IFlexiblePointerTracker>();
+
+    /**
+      new EditLineMeasureTracker(
+        boost::shared_ptr<LineMeasureTool> measureTool;
+        MessageBroker & broker,
+        boost::weak_ptr<ViewportController>          controllerW,
+        const PointerEvent & e);
+    */
+    boost::shared_ptr<EditLineMeasureTracker> editLineMeasureTracker(
+      new EditLineMeasureTracker(shared_from_this(), GetBroker(), GetController(), e));
+    return editLineMeasureTracker;
+  }
 
 
-    // line equation from two points (non-normalized)
-    // (y0-y1)*x + (x1-x0)*xy + (x0*y1 - x1*y0) = 0
-    // 
-    return false;
+  boost::shared_ptr<MeasureToolMemento> LineMeasureTool::GetMemento() const
+  {
+    boost::shared_ptr<LineMeasureToolMemento> memento(new LineMeasureToolMemento());
+    memento->start_ = start_;
+    memento->end_ = end_;
+    return memento;
+  }
+
+  void LineMeasureTool::SetMemento(boost::shared_ptr<MeasureToolMemento> mementoBase)
+  {
+    boost::shared_ptr<LineMeasureToolMemento> memento = boost::dynamic_pointer_cast<LineMeasureToolMemento>(mementoBase);
+    ORTHANC_ASSERT(memento.get() != NULL, "Internal error: wrong (or bad) memento");
+    start_ = memento->start_;
+    end_ = memento->end_;
+    RefreshScene();
   }
 
   void LineMeasureTool::RefreshScene()
@@ -112,11 +176,18 @@
                             TOOL_LINES_COLOR_GREEN, 
                             TOOL_LINES_COLOR_BLUE);
 
+          const Color highlightColor(TOOL_LINES_HL_COLOR_RED,
+                                     TOOL_LINES_HL_COLOR_GREEN,
+                                     TOOL_LINES_HL_COLOR_BLUE);
+
           {
             PolylineSceneLayer::Chain chain;
             chain.push_back(start_);
             chain.push_back(end_);
-            polylineLayer->AddChain(chain, false, color);
+            if(lineHighlightArea_ == LineHighlightArea_Segment)
+              polylineLayer->AddChain(chain, false, highlightColor);
+            else
+              polylineLayer->AddChain(chain, false, color);
           }
 
           // handles
@@ -128,7 +199,10 @@
               AddSquare(chain, GetScene(), start_, 
                 GetController()->GetHandleSideLengthS());
               
-              polylineLayer->AddChain(chain, true, color);
+              if (lineHighlightArea_ == LineHighlightArea_Start)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
 
             {
@@ -138,7 +212,10 @@
               AddSquare(chain, GetScene(), end_, 
                 GetController()->GetHandleSideLengthS());
               
-              polylineLayer->AddChain(chain, true, color);
+              if (lineHighlightArea_ == LineHighlightArea_End)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
           }
 
@@ -150,7 +227,7 @@
           double squareDist = deltaX * deltaX + deltaY * deltaY;
           double dist = sqrt(squareDist);
           char buf[64];
-          sprintf(buf, "%0.02f units", dist);
+          sprintf(buf, "%0.02f mm", dist);
 
           // TODO: for now we simply position the text overlay at the middle
           // of the measuring segment
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Fri Jul 05 15:33:02 2019 +0200
@@ -28,13 +28,14 @@
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
 
 #include <vector>
 #include <cmath>
 
 namespace OrthancStone
 {
-  class LineMeasureTool : public MeasureTool
+  class LineMeasureTool : public MeasureTool, public boost::enable_shared_from_this<LineMeasureTool>
   {
   public:
     LineMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
@@ -47,16 +48,43 @@
 
 
     virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE;
+    virtual void ResetHighlightState() ORTHANC_OVERRIDE;
+    virtual boost::shared_ptr<IFlexiblePointerTracker> CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual boost::shared_ptr<MeasureToolMemento> GetMemento() const ORTHANC_OVERRIDE;
+    virtual void SetMemento(boost::shared_ptr<MeasureToolMemento>) ORTHANC_OVERRIDE;
+
+    enum LineHighlightArea
+    {
+      LineHighlightArea_None,
+      LineHighlightArea_Start,
+      LineHighlightArea_End,
+      LineHighlightArea_Segment
+    };
+
+
+    LineHighlightArea LineHitTest(ScenePoint2D p) const;
 
   private:
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
+    void                SetLineHighlightArea(LineHighlightArea area);
+
+  private:
 
   private:
-    ScenePoint2D   start_;
-    ScenePoint2D   end_;
-    boost::shared_ptr<LayerHolder> layerHolder_;
-    int            baseLayerIndex_;
+    ScenePoint2D                    start_;
+    ScenePoint2D                    end_;
+    boost::shared_ptr<LayerHolder>  layerHolder_;
+    int                             baseLayerIndex_;
+    LineHighlightArea               lineHighlightArea_;
+  };
+
+  class LineMeasureToolMemento : public MeasureToolMemento
+  {
+  public:
+    ScenePoint2D                    start_;
+    ScenePoint2D                    end_;
   };
 
 }
--- a/Framework/Scene2DViewport/MeasureCommands.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -50,6 +50,30 @@
     // we thus leave it as is
   }
 
+  EditMeasureCommand::EditMeasureCommand(boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW)
+    : TrackerCommand(controllerW)
+    , mementoOriginal_(measureTool->GetMemento())
+    , mementoModified_(measureTool->GetMemento())
+  {
+
+  }
+
+  EditMeasureCommand::~EditMeasureCommand()
+  {
+
+  }
+
+  void EditMeasureCommand::Undo()
+  {
+    // simply disable the measure tool upon undo
+    GetMeasureTool()->SetMemento(mementoOriginal_);
+  }
+
+  void EditMeasureCommand::Redo()
+  {
+    GetMeasureTool()->SetMemento(mementoModified_);
+  }
+
   CreateLineMeasureCommand::CreateLineMeasureCommand(
     MessageBroker&         broker, 
     boost::weak_ptr<ViewportController> controllerW,
@@ -67,6 +91,29 @@
     measureTool_->SetEnd(scenePos);
   }
 
+  EditLineMeasureCommand::EditLineMeasureCommand(
+    boost::shared_ptr<LineMeasureTool>  measureTool,
+    MessageBroker& broker,
+    boost::weak_ptr<ViewportController> controllerW)
+    : EditMeasureCommand(measureTool,controllerW)
+    , measureTool_(measureTool)
+  {
+  }
+
+
+  void EditLineMeasureCommand::SetStart(ScenePoint2D scenePos)
+  {
+    measureTool_->SetStart(scenePos);
+    mementoModified_ = measureTool_->GetMemento();
+  }
+
+
+  void EditLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
+  {
+    measureTool_->SetEnd(scenePos);
+    mementoModified_ = measureTool_->GetMemento();
+  }
+    
   CreateAngleMeasureCommand::CreateAngleMeasureCommand(
     MessageBroker&         broker, 
     boost::weak_ptr<ViewportController> controllerW,
@@ -99,4 +146,34 @@
     assert(controller); // accessing dead object?
     return controller;
   }
+
+  EditAngleMeasureCommand::EditAngleMeasureCommand(
+    boost::shared_ptr<AngleMeasureTool>  measureTool,
+    MessageBroker& broker,
+    boost::weak_ptr<ViewportController> controllerW)
+    : EditMeasureCommand(measureTool, controllerW)
+    , measureTool_(measureTool)
+  {
+  }
+
+  void EditAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
+  {
+    measureTool_->SetCenter(scenePos);
+    mementoModified_ = measureTool_->GetMemento();
+  }
+
+
+  void EditAngleMeasureCommand::SetSide1End(ScenePoint2D scenePos)
+  {
+    measureTool_->SetSide1End(scenePos);
+    mementoModified_ = measureTool_->GetMemento();
+  }
+
+
+  void EditAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
+  {
+    measureTool_->SetSide2End(scenePos);
+    mementoModified_ = measureTool_->GetMemento();
+  }
+
 }
--- a/Framework/Scene2DViewport/MeasureCommands.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.h	Fri Jul 05 15:33:02 2019 +0200
@@ -42,6 +42,8 @@
     }
     virtual void Undo() = 0;
     virtual void Redo() = 0;
+    
+    virtual ~TrackerCommand() {};
 
   protected:
     boost::shared_ptr<ViewportController>  GetController();
@@ -52,7 +54,7 @@
   {
   public:
     CreateMeasureCommand(boost::weak_ptr<ViewportController> controllerW);
-    ~CreateMeasureCommand();
+    virtual ~CreateMeasureCommand();
     virtual void Undo() ORTHANC_OVERRIDE;
     virtual void Redo() ORTHANC_OVERRIDE;
   private:
@@ -60,6 +62,27 @@
     virtual boost::shared_ptr<MeasureTool> GetMeasureTool() = 0;
   };
   
+  class EditMeasureCommand : public TrackerCommand
+  {
+  public:
+    EditMeasureCommand(boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW);
+    virtual ~EditMeasureCommand();
+    virtual void Undo() ORTHANC_OVERRIDE;
+    virtual void Redo() ORTHANC_OVERRIDE;
+
+    /** This memento is the original object state */
+    boost::shared_ptr<MeasureToolMemento> mementoOriginal_;
+
+  private:
+    /** Must be implemented by the subclasses that edit the actual tool */
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() = 0;
+
+  protected:
+
+    /** This memento is updated by the subclasses upon modifications */
+    boost::shared_ptr<MeasureToolMemento> mementoModified_;
+  };
+
   class CreateLineMeasureCommand : public CreateMeasureCommand
   {
   public:
@@ -80,6 +103,26 @@
   };
 
 
+  class EditLineMeasureCommand : public EditMeasureCommand
+  {
+  public:
+    EditLineMeasureCommand(
+      boost::shared_ptr<LineMeasureTool>  measureTool,
+      MessageBroker&                      broker,
+      boost::weak_ptr<ViewportController> controllerW);
+
+    void SetStart(ScenePoint2D scenePos);
+    void SetEnd(ScenePoint2D scenePos);
+
+  private:
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    boost::shared_ptr<LineMeasureTool> measureTool_;
+  };
+
+
   class CreateAngleMeasureCommand : public CreateMeasureCommand
   {
   public:
@@ -103,5 +146,31 @@
     boost::shared_ptr<AngleMeasureTool> measureTool_;
   };
 
+  class EditAngleMeasureCommand : public EditMeasureCommand
+  {
+  public:
+    /** Ctor sets end of side 1*/
+    EditAngleMeasureCommand(
+      boost::shared_ptr<AngleMeasureTool>  measureTool,
+      MessageBroker& broker,
+      boost::weak_ptr<ViewportController> controllerW);
+
+    /** This method sets center*/
+    void SetCenter(ScenePoint2D scenePos);
+
+    /** This method sets end of side 1*/
+    void SetSide1End(ScenePoint2D scenePos);
+
+    /** This method sets end of side 2*/
+    void SetSide2End(ScenePoint2D scenePos);
+
+  private:
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    boost::shared_ptr<AngleMeasureTool> measureTool_;
+  };
+
 }
 
--- a/Framework/Scene2DViewport/MeasureTool.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.h	Fri Jul 05 15:33:02 2019 +0200
@@ -35,6 +35,9 @@
 
 namespace OrthancStone
 {
+  class IFlexiblePointerTracker;
+  class MeasureToolMemento;
+
   class MeasureTool : public IObserver
   {
   public:
@@ -71,6 +74,37 @@
     measuring tool
     */
     virtual bool HitTest(ScenePoint2D p) const = 0;
+
+    /**
+    This method must return a memento the captures the tool state (not including
+    the highlighting state
+    */
+    virtual boost::shared_ptr<MeasureToolMemento> GetMemento() const = 0;
+
+    /**
+    This method must apply the supplied memento (this requires RTTI to check
+    the type)
+    */
+    virtual void SetMemento(boost::shared_ptr<MeasureToolMemento>) = 0;
+
+    /**
+    This must create an edition tracker suitable for the supplied click position,
+    or an empty pointer if no hit test (although this should have been checked
+    first)
+    */
+    virtual boost::shared_ptr<IFlexiblePointerTracker> CreateEditionTracker(const PointerEvent& e) = 0;
+
+    /**
+    Will change the measuring tool to provide visual feedback on the GUI 
+    element that is in the pointer hit zone
+    */
+    virtual void Highlight(ScenePoint2D p) = 0;
+
+    /**
+    This function must reset the visual highlighted hot zone feedback
+    */
+    virtual void ResetHighlightState() = 0;
+
   protected:
     MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
 
@@ -104,6 +138,13 @@
     boost::weak_ptr<ViewportController> controllerW_;
     bool     enabled_;
   };
+
+  class MeasureToolMemento
+  {
+    public:
+      virtual ~MeasureToolMemento() {};
+  };
+
 }
 
 extern void TrackerSample_SetInfoDisplayMessage(
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -322,4 +322,11 @@
         p.GetY() + yoffsets[i] * pixelToScene);
     }
   }
+
+  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p)
+  {
+    os << "x = " << p.GetX() << " , y = " << p.GetY();
+    return os;
+  }
+
 }
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Fri Jul 05 15:33:02 2019 +0200
@@ -33,7 +33,7 @@
   void AddSquare(PolylineSceneLayer::Chain& chain,
     boost::shared_ptr<const Scene2D>     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLengthS);
+    const double& sideLengthS);
 
   /**
     Creates an arc centered on c that goes
@@ -48,23 +48,23 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const ScenePoint2D&         p1
-    , const ScenePoint2D&         c
-    , const ScenePoint2D&         p2
-    , const double&               radiusS
+    PolylineSceneLayer::Chain& chain
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double& radiusS
     , const int                   subdivisionsCount = 63);
 
   /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from
     start angle to end angle, by following the shortest arc.
 
     Warning: the existing chain content will be wiped out.
   */
   void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const ScenePoint2D&         centerS
-    , const double&               radiusS
+    PolylineSceneLayer::Chain& chain
+    , const ScenePoint2D& centerS
+    , const double& radiusS
     , const double                startAngleRad
     , const double                endAngleRad
     , const int                   subdivisionsCount = 63);
@@ -79,24 +79,24 @@
       - so that r2 belongs to the p2,c line
       - so that the distance from c to r2 equals radius
 
-    if clockwise is true, the arc is drawn from r1 to r2 with increasing 
+    if clockwise is true, the arc is drawn from r1 to r2 with increasing
     angle values. Otherwise, the angle values decrease.
 
     Warning: the existing chain content will be wiped out.
   */
 
   void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
-    , const ScenePoint2D&        p1
-    , const ScenePoint2D&        c
-    , const ScenePoint2D&        p2
-    , const double&              radiusS
+    PolylineSceneLayer::Chain & chain
+    , const Scene2D & scene
+    , const ScenePoint2D & p1
+    , const ScenePoint2D & c
+    , const ScenePoint2D & p2
+    , const double& radiusS
     , const bool                 clockwise
     , const int                  subdivisionsCount = 63);
- 
+
   /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from
     start angle to end angle with the supplied radius.
 
     if clockwise is true, the arc is drawn from start to end by increasing the
@@ -107,10 +107,10 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&      scene
+    PolylineSceneLayer::Chain& chain
+    , const Scene2D& scene
     , const ScenePoint2D& centerS
-    , const double&       radiusS
+    , const double& radiusS
     , const double        startAngleRad
     , const double        endAngleRad
     , const bool          clockwise
@@ -123,9 +123,9 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    const Scene2D& scene,
     const ScenePoint2D& centerS,
-    const double&       radiusS,
+    const double& radiusS,
     const int           numSubdivisions = 63);
 
   /**
@@ -135,10 +135,10 @@
   double NormalizeAngle(double angle);
 
   /**
-    Returns the angle magnitude between the p1,c and p2,c lines. 
+    Returns the angle magnitude between the p1,c and p2,c lines.
     The returned angle is between 0 and 2*pi
 
-    If the angle is between 0 and pi, this means that the shortest arc 
+    If the angle is between 0 and pi, this means that the shortest arc
     from p1 to p2 is clockwise.
 
     If the angle is between pi and 2*pi, this means that the shortest arc
@@ -146,7 +146,7 @@
 
   */
   double MeasureAngle(
-      const ScenePoint2D& p1
+    const ScenePoint2D& p1
     , const ScenePoint2D& c
     , const ScenePoint2D& p2);
 
@@ -163,7 +163,7 @@
   to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
   */
   void GetPositionOnBisectingLine(
-      ScenePoint2D&       result
+    ScenePoint2D& result
     , const ScenePoint2D& p1
     , const ScenePoint2D& c
     , const ScenePoint2D& p2
@@ -172,14 +172,18 @@
 
   /**
   This helper is used when drawing text with an outline.
-  It set the properties for several text layers at once : first the 
-  four outline layers, with a position shift and then the actual main text 
+  It set the properties for several text layers at once : first the
+  four outline layers, with a position shift and then the actual main text
   layer.
 
   The five text layers are supposed to already exist in the scene, starting
-  from layerIndex, up to (and not including) layerIndex+5. 
+  from layerIndex, up to (and not including) layerIndex+5.
   */
   void SetTextLayerOutlineProperties(
     boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder,
     const char* text, ScenePoint2D p);
+
+
+  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p);
 }
+
--- a/Framework/Scene2DViewport/MeasureTrackers.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -46,7 +46,6 @@
   {
     // if the tracker completes successfully, we add the command
     // to the undo stack
-
     // otherwise, we simply undo it
     if (commitResult_)
       controllerW_.lock()->PushCommand(command_);
@@ -59,6 +58,41 @@
     return controllerW_.lock()->GetScene();
   }
 
+  EditMeasureTracker::EditMeasureTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e)
+    : controllerW_(controllerW)
+    , alive_(true)
+    , commitResult_(true)
+  {
+    boost::shared_ptr<ViewportController> controller = controllerW.lock();
+    originalClickPosition_ = e.GetMainPosition().Apply(controller->GetScene()->GetCanvasToSceneTransform());
+  }
+
+  boost::shared_ptr<Scene2D> EditMeasureTracker::GetScene()
+  {
+    return controllerW_.lock()->GetScene();
+  }
+
+  void EditMeasureTracker::Cancel()
+  {
+    commitResult_ = false;
+    alive_ = false;
+  }
+
+  bool EditMeasureTracker::IsAlive() const
+  {
+    return alive_;
+  }
+
+  EditMeasureTracker::~EditMeasureTracker()
+  {
+    // if the tracker completes successfully, we add the command
+    // to the undo stack
+    // otherwise, we simply undo it
+    if (commitResult_)
+      controllerW_.lock()->PushCommand(command_);
+    else
+      command_->Undo();
+  }
 }
 
 
--- a/Framework/Scene2DViewport/MeasureTrackers.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Fri Jul 05 15:33:02 2019 +0200
@@ -50,5 +50,30 @@
   private:
     bool                            commitResult_;
   };
+
+  class EditMeasureTracker : public IFlexiblePointerTracker
+  {
+  public:
+    virtual void Cancel() ORTHANC_OVERRIDE;
+    virtual bool IsAlive() const ORTHANC_OVERRIDE;
+  protected:
+    EditMeasureTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e);
+
+    ~EditMeasureTracker();
+
+  protected:
+    boost::shared_ptr<EditMeasureCommand> command_;
+    boost::weak_ptr<ViewportController>   controllerW_;
+    bool                                  alive_;
+    boost::shared_ptr<Scene2D>            GetScene();
+
+    ScenePoint2D                          GetOriginalClickPosition() const
+    {
+      return originalClickPosition_;
+    }
+  private:
+    ScenePoint2D                          originalClickPosition_;
+    bool                                  commitResult_;
+  };
 }
 
--- a/Framework/Scene2DViewport/ViewportController.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -100,6 +100,15 @@
     return ret;
   }
 
+
+  void ViewportController::ResetMeasuringToolsHighlight()
+  {
+    for (size_t i = 0; i < measureTools_.size(); ++i)
+    {
+      measureTools_[i]->ResetHighlightState();
+    }
+  }
+
   const OrthancStone::AffineTransform2D& ViewportController::GetCanvasToSceneTransform() const
   {
     return scene_->GetCanvasToSceneTransform();
--- a/Framework/Scene2DViewport/ViewportController.h	Fri Jul 05 14:52:43 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Fri Jul 05 15:33:02 2019 +0200
@@ -42,10 +42,21 @@
   const uint8_t TEXT_COLOR_GREEN = 223;
   const uint8_t TEXT_COLOR_BLUE = 81;
 
+  const uint8_t TOOL_ANGLE_LINES_COLOR_RED = 0;
+  const uint8_t TOOL_ANGLE_LINES_COLOR_GREEN = 183;
+  const uint8_t TOOL_ANGLE_LINES_COLOR_BLUE = 17;
+                     
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_RED = 0;
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_GREEN = 17;
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_BLUE = 183;
+
   const uint8_t TOOL_LINES_COLOR_RED = 0;
   const uint8_t TOOL_LINES_COLOR_GREEN = 223;
   const uint8_t TOOL_LINES_COLOR_BLUE = 21;
 
+  const uint8_t TOOL_LINES_HL_COLOR_RED = 0;
+  const uint8_t TOOL_LINES_HL_COLOR_GREEN = 21;
+  const uint8_t TOOL_LINES_HL_COLOR_BLUE = 223;
 
   const uint8_t TEXT_OUTLINE_COLOR_RED = 0;
   const uint8_t TEXT_OUTLINE_COLOR_GREEN = 56;
@@ -88,6 +99,12 @@
     std::vector<boost::shared_ptr<MeasureTool> > HitTestMeasureTools(ScenePoint2D p);
 
     /**
+    This function will traverse the measuring tools and will clear their 
+    highlighted state
+    */
+    void ResetMeasuringToolsHighlight();
+
+    /**
     With this method, the object takes ownership of the supplied tracker and
     updates it according to user interaction
     */
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Jul 05 14:52:43 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Jul 05 15:33:02 2019 +0200
@@ -474,6 +474,14 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureCommand.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureCommand.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.h
--- a/Samples/Sdl/TrackerSampleApp.cpp	Fri Jul 05 14:52:43 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Fri Jul 05 15:33:02 2019 +0200
@@ -247,7 +247,7 @@
 
         DisplayFloatingCtrlInfoText(e);
       }
-      else
+      else if (activeTracker_.get() != NULL)
       {
         HideInfoText();
         //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
@@ -268,6 +268,32 @@
             activeTracker_.reset();
         }
       }
+      else
+      {
+        HideInfoText();
+
+        PointerEvent e;
+        e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y));
+
+        ScenePoint2D scenePos = e.GetMainPosition().Apply(
+          controller_->GetScene()->GetCanvasToSceneTransform());
+        //auto measureTools = GetController()->HitTestMeasureTools(scenePos);
+        //LOG(TRACE) << "# of hit tests: " << measureTools.size();
+        
+        // this returns the collection of measuring tools where hit test is true
+        std::vector<boost::shared_ptr<MeasureTool> > measureTools = controller_->HitTestMeasureTools(scenePos);
+
+        // let's refresh the measuring tools highlighted state
+        // first let's tag them as "unhighlighted"
+        controller_->ResetMeasuringToolsHighlight();
+
+        // then immediately take the first one and ask it to highlight the 
+        // measuring tool UI part that is hot
+        if (measureTools.size() > 0)
+        {
+          measureTools[0]->Highlight(scenePos);
+        }
+      }
     }
     else if (event.type == SDL_MOUSEBUTTONUP)
     {
@@ -596,6 +622,15 @@
   boost::shared_ptr<IFlexiblePointerTracker> TrackerSampleApp::TrackerHitTest(const PointerEvent & e)
   {
     // std::vector<boost::shared_ptr<MeasureTool>> measureTools_;
+    ScenePoint2D scenePos = e.GetMainPosition().Apply(
+      controller_->GetScene()->GetCanvasToSceneTransform());
+
+    std::vector<boost::shared_ptr<MeasureTool> > measureTools = controller_->HitTestMeasureTools(scenePos);
+
+    if (measureTools.size() > 0)
+    {
+      return measureTools[0]->CreateEditionTracker(e);
+    }
     return boost::shared_ptr<IFlexiblePointerTracker>();
   }