# HG changeset patch # User Alain Mazy # Date 1562665603 -7200 # Node ID 31319fe867b9d764e9db15358f780647ea4fe833 # Parent a8cd3755db21cd943c5613ea378b5f65a88cb935# Parent 80829436ce0cee3eebbf56312a5a7b4da8aa93a6 Merge diff -r 80829436ce0c -r 31319fe867b9 .hgignore --- a/.hgignore Thu Jun 13 16:47:02 2019 +0200 +++ b/.hgignore Tue Jul 09 11:46:43 2019 +0200 @@ -33,6 +33,7 @@ Resources/CommandTool/protoc-tests/node_modules/ Samples/Sdl/ThirdPartyDownloads/ Samples/Sdl/CMakeLists.txt.orig +Samples/Qt/ThirdPartyDownloads/ Samples/WebAssembly/build/ Samples/WebAssembly/ThirdPartyDownloads/ diff -r 80829436ce0c -r 31319fe867b9 .hgtags --- a/.hgtags Thu Jun 13 16:47:02 2019 +0200 +++ b/.hgtags Tue Jul 09 11:46:43 2019 +0200 @@ -1,1 +1,6 @@ 90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19 +6d15261f9c9965a2f4b64658e318b370933b175e toa2019061901 +ff3559c489c98fad1ed174f7be919df6c20d36a9 toa2019062401 +c71ef52602a00dbb35f2b6bd7bd5ed516f1014fa toa2019062501 +fe96057e97b94eb8a46c1a33ba350c354b5c4afc toa2019062502 +60a403f01c3112249f9d4a1a6149bef1de9766bf toa2019062503 diff -r 80829436ce0c -r 31319fe867b9 Applications/Generic/GuiAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/GuiAdapter.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,863 @@ +/** + * 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 . + **/ + +#include "GuiAdapter.h" + +#if ORTHANC_ENABLE_OPENGL == 1 +# include "../../Framework/OpenGL/OpenGLIncludes.h" +#endif + +#if ORTHANC_ENABLE_SDL == 1 +# include +# include +# include +#endif + +#if ORTHANC_ENABLE_THREADS == 1 +# include "../../Framework/Messages/LockingEmitter.h" +#endif + +namespace OrthancStone +{ + void GuiAdapter::RegisterWidget(boost::shared_ptr widget) + { + widgets_.push_back(widget); + } + + std::ostream& operator<<( + std::ostream& os, const GuiAdapterKeyboardEvent& event) + { + os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " << + "shift: " << event.shiftKey << ", " << + "alt: " << event.altKey; + return os; + } + +#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.sym[0] = src.key[0]; + dest.sym[1] = 0; + dest.ctrlKey = src.ctrlKey; + dest.shiftKey = src.shiftKey; + dest.altKey = src.altKey; + } + + template + struct FuncAdapterPayload + { + std::string canvasId; + void* userData; + GenericFunc callback; + }; + + template + EM_BOOL OnEventAdapterFunc( + int eventType, const EmscriptenEvent* emEvent, void* userData) + { + + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload* payload = + reinterpret_cast*>(userData); + // LOG(INFO) << "OnEventAdapterFunc"; + // LOG(INFO) << "------------------"; + // 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); + return static_cast(ret); + } + + template + EM_BOOL OnEventAdapterFunc2( + int /*eventType*/, const EmscriptenEvent* wheelEvent, void* userData) + { + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload* payload = + reinterpret_cast*>(userData); + + GuiAdapterEvent guiEvent; + ConvertFromPlatform(guiEvent, *wheelEvent); + bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData); + return static_cast(ret); + } + + template + EM_BOOL OnEventAdapterFunc3( + double time, void* userData) + { + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload* payload = + reinterpret_cast*>(userData); + //std::auto_ptr< FuncAdapterPayload > deleter(payload); + bool ret = (*(payload->callback))(time, payload->userData); + return static_cast(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* payload = + new FuncAdapterPayload(); + std::auto_ptr > payloadP(payload); + payload->canvasId = canvasId; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast(payload); + // LOG(INFO) << "SetCallback -- userDataRaw: " << userDataRaw << + // " payload: " << payload << " payload->userData: " << payload->userData; + (*emFunc)( + canvasId.c_str(), + userDataRaw, + static_cast(capture), + &OnEventAdapterFunc, + 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 > payload( + new FuncAdapterPayload() + ); + payload->canvasId = canvasId; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast(payload.release()); + (*emFunc)( + canvasId.c_str(), + userDataRaw, + static_cast(capture), + &OnEventAdapterFunc2, + EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); + } + + template< + typename GenericFunc, + typename EmscriptenSetCallbackFunc> + static void SetAnimationFrameCallback( + EmscriptenSetCallbackFunc emFunc, + void* userData, GenericFunc func) + { + // LOG(ERROR) << "SetAnimationFrameCallback !!!!!! (RequestAnimationFrame)"; + std::auto_ptr > payload( + new FuncAdapterPayload() + ); + payload->canvasId = "UNDEFINED"; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast(payload.release()); + (*emFunc)( + &OnEventAdapterFunc3, + userDataRaw); + } + + void GuiAdapter::SetWheelCallback( + std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func) + { + SetCallback( + &emscripten_set_wheel_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetMouseDownCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + SetCallback( + &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( + &emscripten_set_mousemove_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetMouseUpCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + SetCallback( + &emscripten_set_mouseup_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetKeyDownCallback( + std::string canvasId, void* userData, bool capture, OnKeyDownFunc func) + { + SetCallback2( + &emscripten_set_keydown_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + SetCallback2( + &emscripten_set_keyup_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetResizeCallback( + std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + SetCallback( + &emscripten_set_resize_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::RequestAnimationFrame( + OnAnimationFrameFunc func, void* userData) + { + // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; + // LOG(ERROR) << "RequestAnimationFrame"; + // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; + SetAnimationFrameCallback( + &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(capture), func); + } + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + emscripten_set_keyup_callback(canvasId.c_str(), userData, static_cast(capture), func); + } + + void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast(capture), func); + } + + void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData) + { + emscripten_request_animation_frame_loop(func, userData); + } +#endif + + +#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) + { + 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; + + case SDL_BUTTON_RIGHT: + dest.button = GUIADAPTER_MOUSEBUTTON_RIGHT; + 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; + } + + 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; + } + + + + // SDL ONLY + void GuiAdapter::SetResizeCallback( + std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + resizeHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseDownCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseDownHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseMoveCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseMoveHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseUpCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseUpHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetWheelCallback( + std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func) + { + mouseWheelHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetKeyDownCallback( + std::string canvasId, void* userData, bool capture, OnKeyDownFunc func) + { + keyDownHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + keyUpHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::OnAnimationFrame() + { + for (size_t i = 0; i < animationFrameHandlers_.size(); i++) + { + // TODO: fix time + (*(animationFrameHandlers_[i].first))(0, animationFrameHandlers_[i].second); + } + } + + // SDL ONLY + void GuiAdapter::OnResize() + { + for (size_t i = 0; i < resizeHandlers_.size(); i++) + { + (*(resizeHandlers_[i].func))( + resizeHandlers_[i].canvasName, 0, resizeHandlers_[i].userData); + } + } + + // SDL ONLY + void GuiAdapter::OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event) + { + + // 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.mouse.type) + { + case GUIADAPTER_EVENT_WHEEL: + for (size_t i = 0; i < mouseWheelHandlers_.size(); i++) + { + if (mouseWheelHandlers_[i].canvasName == windowTitle) + (*(mouseWheelHandlers_[i].func))(windowTitle, &event, mouseWheelHandlers_[i].userData); + } + break; + default: + ORTHANC_ASSERT(false, "Wrong event.type: " << event.mouse.type << " in GuiAdapter::OnMouseWheelEvent(...)"); + break; + } + } + + + void GuiAdapter::OnKeyboardEvent(uint32_t windowID, const GuiAdapterKeyboardEvent& event) + { + // 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!"); + + std::string windowTitle(windowTitleSz); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + + switch (event.type) + { + case GUIADAPTER_EVENT_KEYDOWN: + for (size_t i = 0; i < keyDownHandlers_.size(); i++) + { + (*(keyDownHandlers_[i].func))(windowTitle, &event, keyDownHandlers_[i].userData); + } + break; + case GUIADAPTER_EVENT_KEYUP: + for (size_t i = 0; i < keyUpHandlers_.size(); i++) + { + (*(keyUpHandlers_[i].func))(windowTitle, &event, keyUpHandlers_[i].userData); + } + break; + default: + ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnKeyboardEvent(...)"); + break; + } + } + + // 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 GetWidgetFromWindowId(); + //boost::shared_ptr 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::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData) + { + animationFrameHandlers_.push_back(std::make_pair(func, userData)); + } + +# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__) /* OpenGL debug is not available on OS X */ + + // SDL ONLY + 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 + + // SDL ONLY + void GuiAdapter::Run() + { +# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__) + 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_MOUSEWHEEL) + { + + 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; + + GuiAdapterWheelEvent dest; + ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event); + OnMouseWheelEvent(event.window.windowID, dest); + + //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + //int x, y; + //SDL_GetMouseState(&x, &y); + + //if (event.wheel.y > 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + //} + //else if (event.wheel.y < 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + //} + } + 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_q: + stop = true; + break; + + default: + GuiAdapterKeyboardEvent dest; + ConvertFromPlatform(dest, event); + OnKeyboardEvent(event.window.windowID, dest); + break; + } + } + // HandleApplicationEvent(controller, compositor, event, tracker); + } + + SDL_Delay(1); + } + } +#endif +} + diff -r 80829436ce0c -r 31319fe867b9 Applications/Generic/GuiAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/GuiAdapter.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,371 @@ +/** + * 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 . + **/ +#pragma once + +#include + +#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 +#else +# if ORTHANC_ENABLE_SDL == 1 +# include +# endif +#endif + +#include "../../Framework/StoneException.h" + +#if ORTHANC_ENABLE_THREADS != 1 +# include "../../Framework/Messages/LockingEmitter.h" +#endif + +#include +#include +#include + +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 GuiAdapterMouseButtonType + { + GUIADAPTER_MOUSEBUTTON_LEFT = 0, + GUIADAPTER_MOUSEBUTTON_MIDDLE = 1, + GUIADAPTER_MOUSEBUTTON_RIGHT = 2 + }; + + + enum GuiAdapterHidEventType + { + GUIADAPTER_EVENT_MOUSEDOWN = 1973, + GUIADAPTER_EVENT_MOUSEMOVE = 1974, + GUIADAPTER_EVENT_MOUSEUP = 1975, + GUIADAPTER_EVENT_WHEEL = 1976, + GUIADAPTER_EVENT_KEYDOWN = 1977, + GUIADAPTER_EVENT_KEYUP = 1978, + }; + + 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)(std::string canvasId, const GuiAdapterMouseEvent* mouseEvent, void* userData); + typedef bool (*OnMouseWheelFunc)(std::string canvasId, const GuiAdapterWheelEvent* wheelEvent, void* userData); + typedef bool (*OnKeyDownFunc) (std::string canvasId, const GuiAdapterKeyboardEvent* keyEvent, void* userData); + typedef bool (*OnKeyUpFunc) (std::string canvasId, const GuiAdapterKeyboardEvent* keyEvent, void* userData); + + typedef bool (*OnAnimationFrameFunc)(double time, void* userData); + typedef bool (*OnWindowResizeFunc)(std::string canvasId, 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 + { + GuiAdapterHidEventType 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; + + public: + GuiAdapterMouseEvent() + : ctrlKey(false), + shiftKey(false), + altKey(false) + { + } + }; + + struct GuiAdapterWheelEvent { + GuiAdapterMouseEvent mouse; + double deltaX; + double deltaY; + unsigned long deltaMode; + }; + + // we don't use any data now + struct GuiAdapterUiEvent {}; + + // EmscriptenKeyboardEvent + struct GuiAdapterKeyboardEvent + { + GuiAdapterHidEventType type; + char sym[32]; + bool ctrlKey; + bool shiftKey; + bool altKey; + }; + + std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); + + /* + 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); + + void ConvertFromPlatform(GuiAdapterWheelEvent& dest, bool ctrlPressed, bool shiftPressed, bool altPressed, const SDL_Event& source); + + void ConvertFromPlatform(GuiAdapterKeyboardEvent& dest, const SDL_Event& source); + +# endif + +#endif + + class GuiAdapter + { + public: +#if ORTHANC_ENABLE_THREADS == 1 + GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter) +#else + GuiAdapter() +#endif + { + static int instanceCount = 0; + ORTHANC_ASSERT(instanceCount == 0); + instanceCount = 1; + } + + void RegisterWidget(boost::shared_ptr widget); + + /** + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + + emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnXXXMouseWheel); + emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnXXXMouseWheel); + emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnXXXMouseWheel); + + 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 > + animationFrameHandlers_; + + void OnResize(); + +#if ORTHANC_ENABLE_SDL == 1 + template + struct EventHandlerData + { + EventHandlerData(std::string canvasName, Func func, void* userData) + : canvasName(canvasName) + , func(func) + , userData(userData) + { + } + + std::string canvasName; + Func func; + void* userData; + }; + std::vector > resizeHandlers_; + std::vector > mouseDownHandlers_; + std::vector > mouseMoveHandlers_; + std::vector > mouseUpHandlers_; + std::vector > mouseWheelHandlers_; + std::vector > keyDownHandlers_; + std::vector > 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 + */ + void OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event); + + boost::shared_ptr GetWidgetFromWindowId(); + +#endif + + /** + This executes all the registered headers if needed (in wasm, the browser + deals with this) + */ + void ViewportsUpdateSize(); + + std::vector > widgets_; + + template void VisitWidgets(F func) + { + for (size_t i = 0; i < widgets_.size(); i++) + { + boost::shared_ptr widget = widgets_[i].lock(); + func(widget); + } + } + }; +} diff -r 80829436ce0c -r 31319fe867b9 Applications/Qt/QCairoWidget.h --- a/Applications/Qt/QCairoWidget.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Applications/Qt/QCairoWidget.h Tue Jul 09 11:46:43 2019 +0200 @@ -21,8 +21,8 @@ #pragma once #include "../../Applications/Generic/NativeStoneApplicationContext.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Widgets/IWidget.h" +#include "../../Framework/Wrappers/CairoSurface.h" +#include "../../Framework/Deprecated/Widgets/IWidget.h" #include #include diff -r 80829436ce0c -r 31319fe867b9 Applications/Qt/QtStoneApplicationRunner.cpp --- a/Applications/Qt/QtStoneApplicationRunner.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Applications/Qt/QtStoneApplicationRunner.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -27,7 +27,7 @@ #include #include -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include #include diff -r 80829436ce0c -r 31319fe867b9 Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Thu Jun 13 16:47:02 2019 +0200 +++ b/Applications/Samples/CMakeLists.txt Tue Jul 09 11:46:43 2019 +0200 @@ -82,6 +82,7 @@ endif() + ##################################################################### ## Configuration for Orthanc ##################################################################### @@ -100,6 +101,10 @@ set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build a static library containing the Orthanc Stone framework diff -r 80829436ce0c -r 31319fe867b9 Applications/Samples/rt-viewer-demo/CMakeLists.txt --- a/Applications/Samples/rt-viewer-demo/CMakeLists.txt Thu Jun 13 16:47:02 2019 +0200 +++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt Tue Jul 09 11:46:43 2019 +0200 @@ -81,6 +81,7 @@ endif() + ##################################################################### ## Configuration for Orthanc ##################################################################### @@ -97,6 +98,10 @@ set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build a static library containing the Orthanc Stone framework diff -r 80829436ce0c -r 31319fe867b9 Framework/Deprecated/Toolbox/BaseWebService.cpp --- a/Framework/Deprecated/Toolbox/BaseWebService.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -27,6 +27,8 @@ #include #include +#include +#include namespace Deprecated { @@ -89,7 +91,7 @@ OrthancStone::MessageHandler* failureCallback, unsigned int timeoutInSeconds) { - if (cache_.find(uri) == cache_.end()) + if (!cacheEnabled_ || cache_.find(uri) == cache_.end()) { GetAsyncInternal(uri, headers, new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered @@ -101,6 +103,15 @@ } else { + // put the uri on top of the most recently accessed list + std::deque::iterator it = std::find(orderedCacheKeys_.begin(), orderedCacheKeys_.end(), uri); + if (it != orderedCacheKeys_.end()) + { + std::string uri = *it; + orderedCacheKeys_.erase(it); + orderedCacheKeys_.push_front(uri); + } + // create a command and "post" it to the Oracle so it is executed and commited "later" NotifyHttpSuccessLater(cache_[uri], payload, successCallback); } @@ -123,7 +134,28 @@ void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) { - cache_[message.GetUri()] = boost::shared_ptr(new CachedHttpRequestSuccessMessage(message)); + if (cacheEnabled_) + { + while (cacheCurrentSize_ + message.GetAnswerSize() > cacheMaxSize_ && orderedCacheKeys_.size() > 0) + { + VLOG(1) << "BaseWebService: clearing cache: " << cacheCurrentSize_ << "/" << cacheMaxSize_ << "(" << message.GetAnswerSize() << ")"; + const std::string& oldestUri = orderedCacheKeys_.back(); + HttpCache::iterator it = cache_.find(oldestUri); + if (it != cache_.end()) + { + cacheCurrentSize_ -= it->second->GetAnswerSize(); + cache_.erase(it); + } + orderedCacheKeys_.pop_back(); + + } + + boost::shared_ptr cachedMessage(new CachedHttpRequestSuccessMessage(message)); + cache_[message.GetUri()] = cachedMessage; + orderedCacheKeys_.push_front(message.GetUri()); + cacheCurrentSize_ += message.GetAnswerSize(); + } + NotifyHttpSuccess(message); } diff -r 80829436ce0c -r 31319fe867b9 Framework/Deprecated/Toolbox/BaseWebService.h --- a/Framework/Deprecated/Toolbox/BaseWebService.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Tue Jul 09 11:46:43 2019 +0200 @@ -25,6 +25,7 @@ #include #include +#include namespace Deprecated { @@ -81,14 +82,21 @@ class BaseWebServicePayload; bool cacheEnabled_; - std::map > cache_; // TODO: this is currently an infinite cache ! + size_t cacheCurrentSize_; + size_t cacheMaxSize_; + + typedef std::map > HttpCache; + HttpCache cache_; + std::deque orderedCacheKeys_; public: BaseWebService(OrthancStone::MessageBroker& broker) : IWebService(broker), IObserver(broker), - cacheEnabled_(true) + cacheEnabled_(false), + cacheCurrentSize_(0), + cacheMaxSize_(100*1024*1024) { } diff -r 80829436ce0c -r 31319fe867b9 Framework/Deprecated/Toolbox/OrthancApiClient.cpp --- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -73,7 +73,7 @@ std::auto_ptr< OrthancStone::MessageHandler > binaryHandler_; std::auto_ptr< OrthancStone::MessageHandler > failureHandler_; std::auto_ptr< Orthanc::IDynamicObject > userPayload_; - + OrthancStone::MessageBroker& broker_; void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const { if (failureHandler_.get() != NULL) @@ -84,12 +84,15 @@ } public: - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : emptyHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) + { if (handler == NULL) { @@ -97,12 +100,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : binaryHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -110,12 +115,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : jsonHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -127,26 +134,35 @@ { if (emptyHandler_.get() != NULL) { - emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage - (message.GetUri(), userPayload_.get())); + if (broker_.IsActive(*(emptyHandler_->GetObserver()))) + { + emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage + (message.GetUri(), userPayload_.get())); + } } else if (binaryHandler_.get() != NULL) { - binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage - (message.GetUri(), message.GetAnswer(), - message.GetAnswerSize(), userPayload_.get())); + if (broker_.IsActive(*(binaryHandler_->GetObserver()))) + { + binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage + (message.GetUri(), message.GetAnswer(), + message.GetAnswerSize(), userPayload_.get())); + } } else if (jsonHandler_.get() != NULL) { - Json::Value response; - if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + if (broker_.IsActive(*(jsonHandler_->GetObserver()))) { - jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage - (message.GetUri(), response, userPayload_.get())); - } - else - { - NotifyConversionError(message); + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + { + jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage + (message.GetUri(), response, userPayload_.get())); + } + else + { + NotifyConversionError(message); + } } } else @@ -186,7 +202,7 @@ IWebService::HttpHeaders emptyHeaders; web_.GetAsync(baseUrl_ + uri, emptyHeaders, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -216,7 +232,7 @@ // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -232,7 +248,7 @@ Orthanc::IDynamicObject* payload) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -255,7 +271,7 @@ Orthanc::IDynamicObject* payload /* takes ownership */) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -302,7 +318,7 @@ Orthanc::IDynamicObject* payload) { web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable diff -r 80829436ce0c -r 31319fe867b9 Framework/Messages/IMessageEmitter.h --- a/Framework/Messages/IMessageEmitter.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Messages/IMessageEmitter.h Tue Jul 09 11:46:43 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: diff -r 80829436ce0c -r 31319fe867b9 Framework/Messages/LockingEmitter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/LockingEmitter.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,111 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 . + **/ + +#pragma once + +#include "IMessageEmitter.h" +#include "IObservable.h" + +#include + +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_) + { + } + + MessageBroker& GetBroker() + { + return broker_; + } + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock 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 lock_; + + public: + ReaderLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::unique_lock lock_; + + public: + WriterLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Oracle/GetOrthancImageCommand.cpp --- a/Framework/Oracle/GetOrthancImageCommand.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Oracle/GetOrthancImageCommand.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -45,7 +45,7 @@ GetOrthancImageCommand::GetOrthancImageCommand() : uri_("/"), - timeout_(10), + timeout_(60), hasExpectedFormat_(false) { } diff -r 80829436ce0c -r 31319fe867b9 Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp --- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -49,7 +49,7 @@ GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() : frame_(0), quality_(95), - timeout_(10), + timeout_(60), expectedFormat_(Orthanc::PixelFormat_Grayscale8) { } diff -r 80829436ce0c -r 31319fe867b9 Framework/Oracle/OrthancRestApiCommand.cpp --- a/Framework/Oracle/OrthancRestApiCommand.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Oracle/OrthancRestApiCommand.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -51,7 +51,7 @@ OrthancRestApiCommand::OrthancRestApiCommand() : method_(Orthanc::HttpMethod_Get), uri_("/"), - timeout_(10) + timeout_(60) { } diff -r 80829436ce0c -r 31319fe867b9 Framework/Oracle/WebAssemblyOracle.cpp --- a/Framework/Oracle/WebAssemblyOracle.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Oracle/WebAssemblyOracle.cpp Tue Jul 09 11:46:43 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) diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Tue Jul 09 11:46:43 2019 +0200 @@ -60,6 +60,22 @@ return frame_; } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (source_.get() != NULL) + { + size += source_->GetPitch() * source_->GetHeight(); + } + if (converted_.get() != NULL) + { + size += converted_->GetPitch() * converted_->GetHeight(); + } + + return size; + } + + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset); void SetSourceImage(Orthanc::ImageAccessor* image); // Takes ownership diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Tue Jul 09 11:46:43 2019 +0200 @@ -355,5 +355,10 @@ float& maxValue) const = 0; friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to + + virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs) + { + return 0; + } }; } diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographyMaskLayer.h --- a/Framework/Radiography/RadiographyMaskLayer.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.h Tue Jul 09 11:46:43 2019 +0200 @@ -49,6 +49,18 @@ { } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (mask_.get() != NULL) + { + size += mask_->GetPitch() * mask_->GetHeight(); + } + + return size; + } + + void SetCorners(const std::vector& corners); void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index); diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -147,6 +147,16 @@ return *layer; } + size_t RadiographyScene::GetApproximateMemoryUsage() const + { + size_t size = 0; + for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) + { + size += it->second->GetApproximateMemoryUsage(); + } + return size; + } + void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) { BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Tue Jul 09 11:46:43 2019 +0200 @@ -163,6 +163,8 @@ virtual ~RadiographyScene(); + virtual size_t GetApproximateMemoryUsage() const; + bool GetWindowing(float& center, float& width) const; diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -60,6 +60,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { @@ -143,6 +148,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { diff -r 80829436ce0c -r 31319fe867b9 Framework/Radiography/RadiographySceneWriter.cpp --- a/Framework/Radiography/RadiographySceneWriter.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -30,6 +30,14 @@ void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene) { output["version"] = 1; + float windowCenter, windowWidth; + bool hasWindowing = scene.GetWindowing(windowCenter, windowWidth); + output["hasWindowing"] = hasWindowing; + if (hasWindowing) + { + output["windowCenter"] = windowCenter; + output["windowWidth"] = windowWidth; + } output["layers"] = Json::arrayValue; std::vector layersIndexes; diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2D/ScenePoint2D.h --- a/Framework/Scene2D/ScenePoint2D.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2D/ScenePoint2D.h Tue Jul 09 11:46:43 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 . **/ @@ -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); + } + } }; } + diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/AngleMeasureTool.cpp --- a/Framework/Scene2DViewport/AngleMeasureTool.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -20,6 +20,7 @@ #include "AngleMeasureTool.h" #include "MeasureToolsToolbox.h" +#include "EditAngleMeasureTracker.h" #include "LayerHolder.h" #include @@ -43,6 +44,7 @@ MessageBroker& broker, boost::weak_ptr controllerW) : MeasureTool(broker, controllerW) , layerHolder_(boost::make_shared(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 AngleMeasureTool::GetMemento() const + { + boost::shared_ptr memento(new AngleMeasureToolMemento()); + memento->center_ = center_; + memento->side1End_ = side1End_; + memento->side2End_ = side2End_; + return memento; + } + + void AngleMeasureTool::SetMemento(boost::shared_ptr mementoBase) + { + boost::shared_ptr memento = boost::dynamic_pointer_cast(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 AngleMeasureTool::CreateEditionTracker(const PointerEvent& e) + { + ScenePoint2D scenePos = e.GetMainPosition().Apply( + GetScene()->GetCanvasToSceneTransform()); + + if (!HitTest(scenePos)) + return boost::shared_ptr(); + + /** + new EditLineMeasureTracker( + boost::shared_ptr measureTool; + MessageBroker & broker, + boost::weak_ptr controllerW, + const PointerEvent & e); + */ + boost::shared_ptr 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); } } { diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/AngleMeasureTool.h --- a/Framework/Scene2DViewport/AngleMeasureTool.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.h Tue Jul 09 11:46:43 2019 +0200 @@ -30,13 +30,14 @@ #include #include +#include #include #include namespace OrthancStone { - class AngleMeasureTool : public MeasureTool + class AngleMeasureTool : public MeasureTool, public boost::enable_shared_from_this { public: AngleMeasureTool(MessageBroker& broker, boost::weak_ptr 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 CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual boost::shared_ptr GetMemento() const ORTHANC_OVERRIDE; + virtual void SetMemento(boost::shared_ptr) 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_; + AngleHighlightArea angleHighlightArea_; + }; + + class AngleMeasureToolMemento : public MeasureToolMemento + { + public: + ScenePoint2D side1End_; + ScenePoint2D side2End_; + ScenePoint2D center_; }; } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditAngleMeasureCommand.cpp diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditAngleMeasureCommand.h diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditAngleMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Tue Jul 09 11:46:43 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 . + **/ + +#include "EditAngleMeasureTracker.h" + +namespace OrthancStone +{ + EditAngleMeasureTracker::EditAngleMeasureTracker( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr 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 memento = + boost::dynamic_pointer_cast(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 EditAngleMeasureTracker::GetCommand() + { + boost::shared_ptr ret = boost::dynamic_pointer_cast(command_); + ORTHANC_ASSERT(ret.get() != NULL, "Internal error in EditAngleMeasureTracker::GetCommand()"); + return ret; + } + +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditAngleMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h Tue Jul 09 11:46:43 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 . + **/ + +#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 measureTool, + MessageBroker& broker, + boost::weak_ptr 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 GetCommand(); + }; +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditLineMeasureCommand.cpp diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditLineMeasureCommand.h diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditLineMeasureTracker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Tue Jul 09 11:46:43 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 . + **/ + +#include "EditLineMeasureTracker.h" + +namespace OrthancStone +{ + EditLineMeasureTracker::EditLineMeasureTracker( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr 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 memento = + boost::dynamic_pointer_cast(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 EditLineMeasureTracker::GetCommand() + { + boost::shared_ptr ret = boost::dynamic_pointer_cast(command_); + ORTHANC_ASSERT(ret.get() != NULL, "Internal error in EditLineMeasureTracker::GetCommand()"); + return ret; + } + +} \ No newline at end of file diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/EditLineMeasureTracker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h Tue Jul 09 11:46:43 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 . + **/ + +#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 measureTool, + MessageBroker& broker, + boost::weak_ptr 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 GetCommand(); + }; +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/LineMeasureTool.cpp --- a/Framework/Scene2DViewport/LineMeasureTool.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -20,6 +20,7 @@ #include "LineMeasureTool.h" #include "MeasureToolsToolbox.h" +#include "EditLineMeasureTracker.h" #include "LayerHolder.h" #include @@ -33,6 +34,7 @@ MessageBroker& broker, boost::weak_ptr controllerW) : MeasureTool(broker, controllerW) , layerHolder_(boost::make_shared(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 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(); + + /** + new EditLineMeasureTracker( + boost::shared_ptr measureTool; + MessageBroker & broker, + boost::weak_ptr controllerW, + const PointerEvent & e); + */ + boost::shared_ptr 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 LineMeasureTool::GetMemento() const + { + boost::shared_ptr memento(new LineMeasureToolMemento()); + memento->start_ = start_; + memento->end_ = end_; + return memento; + } + + void LineMeasureTool::SetMemento(boost::shared_ptr mementoBase) + { + boost::shared_ptr memento = boost::dynamic_pointer_cast(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 diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/LineMeasureTool.h --- a/Framework/Scene2DViewport/LineMeasureTool.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.h Tue Jul 09 11:46:43 2019 +0200 @@ -28,13 +28,14 @@ #include #include +#include #include #include namespace OrthancStone { - class LineMeasureTool : public MeasureTool + class LineMeasureTool : public MeasureTool, public boost::enable_shared_from_this { public: LineMeasureTool(MessageBroker& broker, boost::weak_ptr 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 CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE; + virtual boost::shared_ptr GetMemento() const ORTHANC_OVERRIDE; + virtual void SetMemento(boost::shared_ptr) 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_; - int baseLayerIndex_; + ScenePoint2D start_; + ScenePoint2D end_; + boost::shared_ptr layerHolder_; + int baseLayerIndex_; + LineHighlightArea lineHighlightArea_; + }; + + class LineMeasureToolMemento : public MeasureToolMemento + { + public: + ScenePoint2D start_; + ScenePoint2D end_; }; } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureCommands.cpp --- a/Framework/Scene2DViewport/MeasureCommands.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -50,6 +50,30 @@ // we thus leave it as is } + EditMeasureCommand::EditMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr 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 controllerW, @@ -67,6 +91,29 @@ measureTool_->SetEnd(scenePos); } + EditLineMeasureCommand::EditLineMeasureCommand( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr 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 controllerW, @@ -99,4 +146,34 @@ assert(controller); // accessing dead object? return controller; } + + EditAngleMeasureCommand::EditAngleMeasureCommand( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr 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(); + } + } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureCommands.h --- a/Framework/Scene2DViewport/MeasureCommands.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.h Tue Jul 09 11:46:43 2019 +0200 @@ -42,6 +42,8 @@ } virtual void Undo() = 0; virtual void Redo() = 0; + + virtual ~TrackerCommand() {}; protected: boost::shared_ptr GetController(); @@ -52,7 +54,7 @@ { public: CreateMeasureCommand(boost::weak_ptr controllerW); - ~CreateMeasureCommand(); + virtual ~CreateMeasureCommand(); virtual void Undo() ORTHANC_OVERRIDE; virtual void Redo() ORTHANC_OVERRIDE; private: @@ -60,6 +62,27 @@ virtual boost::shared_ptr GetMeasureTool() = 0; }; + class EditMeasureCommand : public TrackerCommand + { + public: + EditMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr controllerW); + virtual ~EditMeasureCommand(); + virtual void Undo() ORTHANC_OVERRIDE; + virtual void Redo() ORTHANC_OVERRIDE; + + /** This memento is the original object state */ + boost::shared_ptr mementoOriginal_; + + private: + /** Must be implemented by the subclasses that edit the actual tool */ + virtual boost::shared_ptr GetMeasureTool() = 0; + + protected: + + /** This memento is updated by the subclasses upon modifications */ + boost::shared_ptr mementoModified_; + }; + class CreateLineMeasureCommand : public CreateMeasureCommand { public: @@ -80,6 +103,26 @@ }; + class EditLineMeasureCommand : public EditMeasureCommand + { + public: + EditLineMeasureCommand( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr controllerW); + + void SetStart(ScenePoint2D scenePos); + void SetEnd(ScenePoint2D scenePos); + + private: + virtual boost::shared_ptr GetMeasureTool() ORTHANC_OVERRIDE + { + return measureTool_; + } + boost::shared_ptr measureTool_; + }; + + class CreateAngleMeasureCommand : public CreateMeasureCommand { public: @@ -103,5 +146,31 @@ boost::shared_ptr measureTool_; }; + class EditAngleMeasureCommand : public EditMeasureCommand + { + public: + /** Ctor sets end of side 1*/ + EditAngleMeasureCommand( + boost::shared_ptr measureTool, + MessageBroker& broker, + boost::weak_ptr 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 GetMeasureTool() ORTHANC_OVERRIDE + { + return measureTool_; + } + boost::shared_ptr measureTool_; + }; + } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureTool.h --- a/Framework/Scene2DViewport/MeasureTool.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureTool.h Tue Jul 09 11:46:43 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 GetMemento() const = 0; + + /** + This method must apply the supplied memento (this requires RTTI to check + the type) + */ + virtual void SetMemento(boost::shared_ptr) = 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 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 controllerW); @@ -104,6 +138,13 @@ boost::weak_ptr controllerW_; bool enabled_; }; + + class MeasureToolMemento + { + public: + virtual ~MeasureToolMemento() {}; + }; + } extern void TrackerSample_SetInfoDisplayMessage( diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureToolsToolbox.cpp --- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Tue Jul 09 11:46:43 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; + } + } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureToolsToolbox.h --- a/Framework/Scene2DViewport/MeasureToolsToolbox.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h Tue Jul 09 11:46:43 2019 +0200 @@ -33,7 +33,7 @@ void AddSquare(PolylineSceneLayer::Chain& chain, boost::shared_ptr 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 scene, boost::shared_ptr layerHolder, const char* text, ScenePoint2D p); + + + std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p); } + diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureTrackers.cpp --- a/Framework/Scene2DViewport/MeasureTrackers.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.cpp Tue Jul 09 11:46:43 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 controllerW, const PointerEvent& e) + : controllerW_(controllerW) + , alive_(true) + , commitResult_(true) + { + boost::shared_ptr controller = controllerW.lock(); + originalClickPosition_ = e.GetMainPosition().Apply(controller->GetScene()->GetCanvasToSceneTransform()); + } + + boost::shared_ptr 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(); + } } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/MeasureTrackers.h --- a/Framework/Scene2DViewport/MeasureTrackers.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.h Tue Jul 09 11:46:43 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 controllerW, const PointerEvent& e); + + ~EditMeasureTracker(); + + protected: + boost::shared_ptr command_; + boost::weak_ptr controllerW_; + bool alive_; + boost::shared_ptr GetScene(); + + ScenePoint2D GetOriginalClickPosition() const + { + return originalClickPosition_; + } + private: + ScenePoint2D originalClickPosition_; + bool commitResult_; + }; } diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/UndoStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/UndoStack.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,68 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 . + **/ + +#include "UndoStack.h" + +#include "MeasureCommands.h" + +#include "../StoneException.h" + +namespace OrthancStone +{ + UndoStack::UndoStack() : numAppliedCommands_(0) + {} + + void UndoStack::PushCommand(boost::shared_ptr command) + { + commandStack_.erase( + commandStack_.begin() + numAppliedCommands_, + commandStack_.end()); + + ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command) + == commandStack_.end(), "Duplicate command"); + commandStack_.push_back(command); + numAppliedCommands_++; + } + + void UndoStack::Undo() + { + ORTHANC_ASSERT(CanUndo(), ""); + commandStack_[numAppliedCommands_ - 1]->Undo(); + numAppliedCommands_--; + } + + void UndoStack::Redo() + { + ORTHANC_ASSERT(CanRedo(), ""); + commandStack_[numAppliedCommands_]->Redo(); + numAppliedCommands_++; + } + + bool UndoStack::CanUndo() const + { + return numAppliedCommands_ > 0; + } + + bool UndoStack::CanRedo() const + { + return numAppliedCommands_ < commandStack_.size(); + } + +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/UndoStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/UndoStack.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,77 @@ +/** + * 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 . + **/ + +#pragma once + +#include + +#include + +namespace OrthancStone +{ + class TrackerCommand; + + class UndoStack + { + public: + UndoStack(); + + /** + Stores a command : + - this first trims the undo stack to keep the first numAppliedCommands_ + - then it adds the supplied command at the top of the undo stack + + In other words, when a new command is pushed, all the undone (and not + redone) commands are removed. + */ + void PushCommand(boost::shared_ptr command); + + /** + Undoes the command at the top of the undo stack, or throws if there is no + command to undo. + You can check "CanUndo" first to protect against extraneous redo. + */ + void Undo(); + + /** + Redoes the command that is just above the last applied command in the undo + stack or throws if there is no command to redo. + You can check "CanRedo" first to protect against extraneous redo. + */ + void Redo(); + + /** selfexpl */ + bool CanUndo() const; + + /** selfexpl */ + bool CanRedo() const; + + private: + std::vector > commandStack_; + + /** + This is always between >= 0 and <= undoStack_.size() and gives the + position where the controller is in the undo stack. + - If numAppliedCommands_ > 0, one can undo + - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo + */ + size_t numAppliedCommands_; + }; +} diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/ViewportController.cpp --- a/Framework/Scene2DViewport/ViewportController.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -19,6 +19,8 @@ **/ #include "ViewportController.h" + +#include "UndoStack.h" #include "MeasureCommands.h" #include "../StoneException.h" @@ -27,14 +29,49 @@ namespace OrthancStone { - ViewportController::ViewportController(MessageBroker& broker) + ViewportController::ViewportController(boost::weak_ptr undoStackW, MessageBroker& broker) : IObservable(broker) - , numAppliedCommands_(0) + , undoStackW_(undoStackW) , canvasToSceneFactor_(0.0) { scene_ = boost::make_shared(); } + boost::shared_ptr ViewportController::GetUndoStack() + { + return undoStackW_.lock(); + } + + boost::shared_ptr ViewportController::GetUndoStack() const + { + return undoStackW_.lock(); + } + + void ViewportController::PushCommand(boost::shared_ptr command) + { + GetUndoStack()->PushCommand(command); + } + + void ViewportController::Undo() + { + GetUndoStack()->Undo(); + } + + void ViewportController::Redo() + { + GetUndoStack()->Redo(); + } + + bool ViewportController::CanUndo() const + { + return GetUndoStack()->CanUndo(); + } + + bool ViewportController::CanRedo() const + { + return GetUndoStack()->CanRedo(); + } + boost::shared_ptr ViewportController::GetScene() const { return scene_; @@ -63,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(); @@ -91,42 +137,6 @@ BroadcastMessage(SceneTransformChanged(*this)); } - void ViewportController::PushCommand(boost::shared_ptr command) - { - commandStack_.erase( - commandStack_.begin() + numAppliedCommands_, - commandStack_.end()); - - ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command) - == commandStack_.end(), "Duplicate command"); - commandStack_.push_back(command); - numAppliedCommands_++; - } - - void ViewportController::Undo() - { - ORTHANC_ASSERT(CanUndo(), ""); - commandStack_[numAppliedCommands_-1]->Undo(); - numAppliedCommands_--; - } - - void ViewportController::Redo() - { - ORTHANC_ASSERT(CanRedo(), ""); - commandStack_[numAppliedCommands_]->Redo(); - numAppliedCommands_++; - } - - bool ViewportController::CanUndo() const - { - return numAppliedCommands_ > 0; - } - - bool ViewportController::CanRedo() const - { - return numAppliedCommands_ < commandStack_.size(); - } - void ViewportController::AddMeasureTool(boost::shared_ptr measureTool) { ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) diff -r 80829436ce0c -r 31319fe867b9 Framework/Scene2DViewport/ViewportController.h --- a/Framework/Scene2DViewport/ViewportController.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.h Tue Jul 09 11:46:43 2019 +0200 @@ -30,10 +30,8 @@ namespace OrthancStone { - /** - These constats are used - - */ + class UndoStack; + const double ARC_RADIUS_CANVAS_COORD = 30.0; const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90; @@ -44,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; @@ -71,7 +80,7 @@ ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \ SceneTransformChanged, ViewportController); - ViewportController(MessageBroker& broker); + ViewportController(boost::weak_ptr undoStackW, MessageBroker& broker); boost::shared_ptr GetScene() const; boost::shared_ptr GetScene(); @@ -90,6 +99,12 @@ std::vector > 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 */ @@ -107,36 +122,6 @@ /** Forwarded to the underlying scene, and broadcasted to the observers */ void FitContent(unsigned int canvasWidth, unsigned int canvasHeight); - /** - Stores a command : - - this first trims the undo stack to keep the first numAppliedCommands_ - - then it adds the supplied command at the top of the undo stack - - In other words, when a new command is pushed, all the undone (and not - redone) commands are removed. - */ - void PushCommand(boost::shared_ptr command); - - /** - Undoes the command at the top of the undo stack, or throws if there is no - command to undo. - You can check "CanUndo" first to protect against extraneous redo. - */ - void Undo(); - - /** - Redoes the command that is just above the last applied command in the undo - stack or throws if there is no command to redo. - You can check "CanRedo" first to protect against extraneous redo. - */ - void Redo(); - - /** selfexpl */ - bool CanUndo() const; - - /** selfexpl */ - bool CanRedo() const; - /** Adds a new measure tool */ void AddMeasureTool(boost::shared_ptr measureTool); @@ -169,18 +154,31 @@ */ double GetAngleTopTextLabelDistanceS() const; + + /** forwarded to the UndoStack */ + void PushCommand(boost::shared_ptr command); + + /** forwarded to the UndoStack */ + void Undo(); + + /** forwarded to the UndoStack */ + void Redo(); + + /** forwarded to the UndoStack */ + bool CanUndo() const; + + /** forwarded to the UndoStack */ + bool CanRedo() const; + + private: double GetCanvasToSceneFactor() const; - std::vector > commandStack_; - - /** - This is always between >= 0 and <= undoStack_.size() and gives the - position where the controller is in the undo stack. - - If numAppliedCommands_ > 0, one can undo - - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo - */ - size_t numAppliedCommands_; + boost::weak_ptr undoStackW_; + + boost::shared_ptr GetUndoStack(); + boost::shared_ptr GetUndoStack() const; + std::vector > measureTools_; boost::shared_ptr scene_; boost::shared_ptr tracker_; diff -r 80829436ce0c -r 31319fe867b9 Framework/StoneException.h --- a/Framework/StoneException.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Framework/StoneException.h Tue Jul 09 11:46:43 2019 +0200 @@ -128,11 +128,9 @@ std::stringstream sst; \ sst << "Assertion failed. Condition = \"" #cond "\""; \ std::string sstr = sst.str(); \ - throw OrthancException(ErrorCode_InternalError,sstr.c_str()); \ + throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \ } else (void)0 - - # define ORTHANC_EXPAND( x ) x # define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME # define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__)) diff -r 80829436ce0c -r 31319fe867b9 Platforms/Wasm/Defaults.cpp --- a/Platforms/Wasm/Defaults.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -41,6 +41,41 @@ extern "C" { #endif +#if 0 + // rewrite malloc/free in order to monitor allocations. We actually only monitor large allocations (like images ...) + + size_t bigChunksTotalSize = 0; + std::map allocatedBigChunks; + + extern void* emscripten_builtin_malloc(size_t bytes); + extern void emscripten_builtin_free(void* mem); + + void * __attribute__((noinline)) malloc(size_t size) + { + void *ptr = emscripten_builtin_malloc(size); + if (size > 100000) + { + bigChunksTotalSize += size; + printf("++ Allocated %zu bytes, got %p. (%zu MB consumed by big chunks)\n", size, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks[ptr] = size; + } + return ptr; + } + + void __attribute__((noinline)) free(void *ptr) + { + emscripten_builtin_free(ptr); + + std::map::iterator it = allocatedBigChunks.find(ptr); + if (it != allocatedBigChunks.end()) + { + bigChunksTotalSize -= it->second; + printf("-- Freed %zu bytes at %p. (%zu MB consumed by big chunks)\n", it->second, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks.erase(it); + } + } +#endif // 0 + using namespace OrthancStone; // when WASM needs a C++ viewport @@ -275,7 +310,7 @@ float x2, float y2) { - printf("touch start with %d touches\n", touchCount); + // printf("touch start with %d touches\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -291,7 +326,7 @@ float x2, float y2) { - printf("touch move with %d touches\n", touchCount); + // printf("touch move with %d touches\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -307,7 +342,7 @@ float x2, float y2) { - printf("touch end with %d touches remaining\n", touchCount); + // printf("touch end with %d touches remaining\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -362,14 +397,14 @@ { static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) - printf("SendSerializedMessageToStoneApplication\n"); - printf("%s", message); + //printf("SendSerializedMessageToStoneApplication\n"); + //printf("%s", message); if (applicationWasmAdapter.get() != NULL) { applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message)); return output.c_str(); } - printf("This Stone application does not have a Web Adapter"); + printf("This Stone application does not have a Web Adapter, unable to send messages"); return NULL; } diff -r 80829436ce0c -r 31319fe867b9 Platforms/Wasm/logger.ts --- a/Platforms/Wasm/logger.ts Thu Jun 13 16:47:02 2019 +0200 +++ b/Platforms/Wasm/logger.ts Tue Jul 09 11:46:43 2019 +0200 @@ -73,7 +73,7 @@ private getOutput(source: LogSource, args: any[]): any[] { var prefix = this.getPrefix(); - var prefixAndSource = []; + var prefixAndSource = Array(); if (prefix != null) { prefixAndSource = [prefix]; @@ -94,7 +94,7 @@ return [...prefixAndSource, ...args]; } - protected getPrefix(): string { + protected getPrefix(): string | null { return null; } } diff -r 80829436ce0c -r 31319fe867b9 Platforms/Wasm/wasm-application-runner.ts --- a/Platforms/Wasm/wasm-application-runner.ts Thu Jun 13 16:47:02 2019 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Tue Jul 09 11:46:43 2019 +0200 @@ -1,5 +1,5 @@ -import Stone = require('./stone-framework-loader'); -import StoneViewport = require('./wasm-viewport'); +import * as Stone from './stone-framework-loader' +import * as StoneViewport from './wasm-viewport' import * as Logger from './logger' if (!('WebAssembly' in window)) { @@ -130,11 +130,6 @@ Logger.defaultLogger.debug("Connecting C++ methods to JS methods - done"); - // Prevent scrolling - document.body.addEventListener('touchmove', function (event) { - event.preventDefault(); - }, { passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface - _InitializeWasmApplication(orthancBaseUrl); }); } diff -r 80829436ce0c -r 31319fe867b9 Platforms/Wasm/wasm-viewport.ts --- a/Platforms/Wasm/wasm-viewport.ts Thu Jun 13 16:47:02 2019 +0200 +++ b/Platforms/Wasm/wasm-viewport.ts Tue Jul 09 11:46:43 2019 +0200 @@ -1,4 +1,4 @@ -import wasmApplicationRunner = require('./wasm-application-runner'); +import * as wasmApplicationRunner from './wasm-application-runner' import * as Logger from './logger' var isPendingRedraw = false; @@ -10,14 +10,17 @@ Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; - WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + let viewport = WasmViewport.GetFromCppViewport(cppViewportHandle); + if (viewport) { + viewport.Redraw(); + } }); } } (window).ScheduleWebViewportRedraw = ScheduleWebViewportRedraw; -declare function UTF8ToString(any): string; +declare function UTF8ToString(v: any): string; function CreateWasmViewport(htmlCanvasId: string) : any { var cppViewportHandle = wasmApplicationRunner.CreateCppViewport(); @@ -38,7 +41,7 @@ private module_ : any; private canvasId_ : string; private htmlCanvas_ : HTMLCanvasElement; - private context_ : CanvasRenderingContext2D; + private context_ : CanvasRenderingContext2D | null; private imageData_ : any = null; private renderingBuffer_ : any = null; @@ -96,20 +99,20 @@ return this.pimpl_; } - public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport | null { if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) { return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } - public static GetFromCanvasId(canvasId: string) : WasmViewport { + public static GetFromCanvasId(canvasId: string) : WasmViewport | null { if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) { return WasmViewport.viewportsMapByCanvasId_[canvasId]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } public static ResizeAll() { @@ -135,7 +138,9 @@ this.renderingBuffer_, this.imageData_.width * this.imageData_.height * 4)); - this.context_.putImageData(this.imageData_, 0, 0); + if (this.context_) { + this.context_.putImageData(this.imageData_, 0, 0); + } } } @@ -147,25 +152,27 @@ } // width/height is defined by the parent width/height - this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; - this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; + if (this.htmlCanvas_.parentElement) { + this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; + this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; - Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); - if (this.imageData_ === null) { - this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); - - if (this.renderingBuffer_ != null) { - this.module_._free(this.renderingBuffer_); + if (this.imageData_ === null && this.context_) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } else { + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); } - this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); - } else { - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.Redraw(); } - - this.Redraw(); } public Initialize() { @@ -211,7 +218,7 @@ }); window.addEventListener('keydown', function(event) { - var keyChar = event.key; + var keyChar: string | null = event.key; var keyCode = event.keyCode if (keyChar.length == 1) { keyCode = 0; // maps to OrthancStone::KeyboardKeys_Generic @@ -328,7 +335,7 @@ this.touchZoom_ = false; } - public GetTouchTranslation(event) { + public GetTouchTranslation(event: any) { var touch = event.targetTouches[0]; return [ touch.pageX, @@ -336,7 +343,7 @@ ]; } - public GetTouchZoom(event) { + public GetTouchZoom(event: any) { var touch1 = event.targetTouches[0]; var touch2 = event.targetTouches[1]; var dx = (touch1.pageX - touch2.pageX); diff -r 80829436ce0c -r 31319fe867b9 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Thu Jun 13 16:47:02 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Tue Jul 09 11:46:43 2019 +0200 @@ -185,7 +185,6 @@ add_definitions( -DHAS_ORTHANC_EXCEPTION=1 - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) if (CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -315,6 +314,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 +373,7 @@ if (ENABLE_THREADS) list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp ) endif() @@ -468,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 @@ -484,6 +498,8 @@ ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PredeclaredTypes.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.h ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp diff -r 80829436ce0c -r 31319fe867b9 Resources/CodeGeneration/template.in.h.j2 --- a/Resources/CodeGeneration/template.in.h.j2 Thu Jun 13 16:47:02 2019 +0200 +++ b/Resources/CodeGeneration/template.in.h.j2 Tue Jul 09 11:46:43 2019 +0200 @@ -51,7 +51,7 @@ inline Json::Value _StoneSerializeValue(int64_t value) { - Json::Value result(value); + Json::Value result(static_cast(value)); return result; } @@ -79,7 +79,7 @@ inline Json::Value _StoneSerializeValue(uint64_t value) { - Json::Value result(value); + Json::Value result(static_cast(value)); return result; } diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicScene.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,463 @@ +/** + * 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 . + **/ + +#define GLEW_STATIC 1 +// From Stone +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" + +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + +#include +#include +#include "EmbeddedResources.h" + +//#include +#include +#include +#include + +static const unsigned int FONT_SIZE = 32; +static const int LAYER_POSITION = 150; + +using namespace OrthancStone; + +void PrepareScene(boost::shared_ptr controller) +{ + Scene2D& scene(*controller->GetScene()); + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene.SetLayer(12, new ColorTextureSceneLayer(i)); + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::auto_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true, 255, 0, 0); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false, 0, 0, 255); + +// layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::auto_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + + +//void TakeScreenshot(const std::string& target, +// const Scene2D& scene, +// unsigned int canvasWidth, +// unsigned int canvasHeight) +//{ +// // Take a screenshot, then save it as PNG file +// CairoCompositor compositor(scene, canvasWidth, canvasHeight); +// compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1); +// compositor.Refresh(); + +// Orthanc::ImageAccessor canvas; +// compositor.GetCanvas().GetReadOnlyAccessor(canvas); + +// Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); +// Orthanc::ImageProcessing::Convert(png, canvas); + +// Orthanc::PngWriter writer; +// writer.WriteToFile(target, png); +//} + + +//void HandleApplicationEvent(ViewportControllerPtr controller, +// const OpenGLCompositor& compositor, +// const SDL_Event& event, +// FlexiblePointerTrackerPtr& activeTracker) +//{ +// Scene2D& scene(*controller->GetScene()); +// if (event.type == SDL_MOUSEMOTION) +// { +// int scancodeCount = 0; +// const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + +// if (activeTracker.get() == NULL && +// SDL_SCANCODE_LCTRL < scancodeCount && +// keyboardState[SDL_SCANCODE_LCTRL]) +// { +// // The "left-ctrl" key is down, while no tracker is present + +// PointerEvent e; +// e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + +// ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + +// char buf[64]; +// sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + +// if (scene.HasLayer(LAYER_POSITION)) +// { +// TextSceneLayer& layer = +// dynamic_cast(scene.GetLayer(LAYER_POSITION)); +// layer.SetText(buf); +// layer.SetPosition(p.GetX(), p.GetY()); +// } +// else +// { +// std::auto_ptr +// layer(new TextSceneLayer); +// layer->SetColor(0, 255, 0); +// layer->SetText(buf); +// layer->SetBorder(20); +// layer->SetAnchor(BitmapAnchor_BottomCenter); +// layer->SetPosition(p.GetX(), p.GetY()); +// scene.SetLayer(LAYER_POSITION, layer.release()); +// } +// } +// else +// { +// scene.DeleteLayer(LAYER_POSITION); +// } +// } +// else if (event.type == SDL_MOUSEBUTTONDOWN) +// { +// PointerEvent e; +// e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + +// switch (event.button.button) +// { +// case SDL_BUTTON_MIDDLE: +// activeTracker = boost::make_shared(controller, e); +// break; + +// case SDL_BUTTON_RIGHT: +// activeTracker = boost::make_shared(controller, +// e, compositor.GetCanvasHeight()); +// break; + +// case SDL_BUTTON_LEFT: +// activeTracker = boost::make_shared(controller, e); +// break; + +// default: +// break; +// } +// } +// else if (event.type == SDL_KEYDOWN && +// event.key.repeat == 0 /* Ignore key bounce */) +// { +// switch (event.key.keysym.sym) +// { +// case SDLK_s: +// controller->FitContent(compositor.GetCanvasWidth(), +// compositor.GetCanvasHeight()); +// break; + +// case SDLK_c: +// TakeScreenshot("screenshot.png", scene, +// compositor.GetCanvasWidth(), +// compositor.GetCanvasHeight()); +// break; + +// default: +// break; +// } +// } +//} + + +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 ); + } +} + + +//void Run(ViewportControllerPtr controller) +//{ +// SdlOpenGLWindow window("Hello", 1024, 768); + +// controller->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + +// glEnable(GL_DEBUG_OUTPUT); +// glDebugMessageCallback(OpenGLMessageCallback, 0); + +// OpenGLCompositor compositor(window, *controller->GetScene()); +// compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, +// FONT_SIZE, Orthanc::Encoding_Latin1); + +// FlexiblePointerTrackerPtr tracker; + +// bool stop = false; +// while (!stop) +// { +// compositor.Refresh(); + +// SDL_Event event; +// while (!stop && +// SDL_PollEvent(&event)) +// { +// if (event.type == SDL_QUIT) +// { +// stop = true; +// break; +// } +// else if (event.type == SDL_MOUSEMOTION) +// { +// if (tracker) +// { +// PointerEvent e; +// e.AddPosition(compositor.GetPixelCenterCoordinates( +// event.button.x, event.button.y)); +// tracker->PointerMove(e); +// } +// } +// else if (event.type == SDL_MOUSEBUTTONUP) +// { +// if (tracker) +// { +// PointerEvent e; +// e.AddPosition(compositor.GetPixelCenterCoordinates( +// event.button.x, event.button.y)); +// tracker->PointerUp(e); +// if(!tracker->IsAlive()) +// tracker.reset(); +// } +// } +// else if (event.type == SDL_WINDOWEVENT && +// event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) +// { +// tracker.reset(); +// compositor.UpdateSize(); +// } +// else if (event.type == SDL_KEYDOWN && +// event.key.repeat == 0 /* Ignore key bounce */) +// { +// switch (event.key.keysym.sym) +// { +// case SDLK_f: +// window.GetWindow().ToggleMaximize(); +// break; + +// case SDLK_q: +// stop = true; +// break; + +// default: +// break; +// } +// } + +// HandleApplicationEvent(controller, compositor, event, tracker); +// } + +// SDL_Delay(1); +// } +//} + +extern void InitGL(); + +#include +#include "BasicSceneWindow.h" +#include "Scene2DInteractor.h" + +int main(int argc, char* argv[]) +{ + { + QApplication a(argc, argv); + + QSurfaceFormat requestedFormat; + requestedFormat.setVersion( 2, 0 ); + + OrthancStone::Samples::BasicSceneWindow window; + window.show(); + + MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); + boost::shared_ptr controller = boost::make_shared( + undoStack, boost::ref(broker)); + PrepareScene(controller); + + boost::shared_ptr interactor(new BasicScene2DInteractor(controller)); + window.GetOpenGlWidget().SetInteractor(interactor); + + QOpenGLContext * context = new QOpenGLContext; + context->setFormat( requestedFormat ); + context->create(); + context->makeCurrent(window.GetOpenGlWidget().context()->surface()); + + boost::shared_ptr compositor = boost::make_shared(window.GetOpenGlWidget(), *controller->GetScene()); + compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + + window.GetOpenGlWidget().SetCompositor(compositor); + + return a.exec(); + } + + + + + + + + + + + + +// StoneInitialize(); +// Orthanc::Logging::EnableInfoLevel(true); + +// QApplication app(argc, argv); + +// OrthancStone::Samples::BasicSceneWindow window; + +// QSurfaceFormat requestedFormat; +// requestedFormat.setVersion( 3, 3 ); + +// window.show(); + +// QOpenGLContext * context = new QOpenGLContext; +// context->setFormat( requestedFormat ); +// context->create(); + +// GLenum err = glewInit(); +// if( GLEW_OK != err ){ +// qDebug() << "[Error] GLEW failed to initialize. " << (const char*)glewGetErrorString(err); +// } + +// try +// { +// MessageBroker broker; +// ViewportControllerPtr controller = boost::make_shared( +// boost::ref(broker)); +// PrepareScene(controller); + +// boost::shared_ptr compositor(new OpenGLCompositor(window.GetOpenGlWidget(), *controller->GetScene())); + +// compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, +// FONT_SIZE, Orthanc::Encoding_Latin1); + +// window.SetCompositor(compositor); + +// app.exec(); +// } +// catch (Orthanc::OrthancException& e) +// { +// LOG(ERROR) << "EXCEPTION: " << e.What(); +// } + + + +// StoneFinalize(); + +// return 0; +} diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/BasicSceneWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,61 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 . + **/ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "BasicSceneWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + BasicSceneWindow::BasicSceneWindow( + QWidget *parent) : +// QStoneMainWindow(context, parent), + ui_(new Ui::BasicSceneWindow) + //stoneSampleApplication_(stoneSampleApplication) + { + ui_->setupUi(this); + //SetCentralStoneWidget(*ui_->cairoCentralWidget); + } + + BasicSceneWindow::~BasicSceneWindow() + { + delete ui_; + } + + QStoneOpenGlWidget& BasicSceneWindow::GetOpenGlWidget() + { + return *(ui_->centralWidget); + } + + void BasicSceneWindow::SetCompositor(boost::shared_ptr compositor) + { + ui_->centralWidget->SetCompositor(compositor); + } + + } +} diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/BasicSceneWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,55 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 . + **/ +#pragma once +#include +#include +// #include "../../Qt/QCairoWidget.h" +// #include "../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class BasicSceneWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + //class SampleSingleCanvasApplicationBase; + + class BasicSceneWindow : public QMainWindow + { + Q_OBJECT + + private: + Ui::BasicSceneWindow* ui_; + //SampleSingleCanvasApplicationBase& stoneSampleApplication_; + + public: + explicit BasicSceneWindow(QWidget *parent = 0); + ~BasicSceneWindow(); + + QStoneOpenGlWidget& GetOpenGlWidget(); + + void SetCompositor(boost::shared_ptr compositor); + }; + } +} diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/BasicSceneWindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.ui Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,84 @@ + + + BasicSceneWindow + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + + 0 + 0 + 903 + 21 + + + + + Test + + + + + + + + + QStoneOpenGlWidget + QWidget +
QStoneOpenGlWidget.h
+
+
+ + +
diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/CMakeLists.txt Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 2.8.3) + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_QT ON) +SET(ENABLE_SDL OFF) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +list(APPEND BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.cpp + ) + +ORTHANC_QT_WRAP_UI(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.ui + ) + +ORTHANC_QT_WRAP_CPP(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.h + QStoneOpenGlWidget.h + ) + +add_executable(BasicScene + BasicScene.cpp + QStoneOpenGlWidget.cpp + Scene2DInteractor.cpp + ${BASIC_SCENE_APPLICATIONS_SOURCES} + ) + +target_include_directories(BasicScene PUBLIC ${CMAKE_SOURCE_DIR} ${STONE_SOURCES_DIR}) + +target_link_libraries(BasicScene OrthancStone) diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/QStoneOpenGlWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,95 @@ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "QStoneOpenGlWidget.h" + +#include + +using namespace OrthancStone; + +void QStoneOpenGlWidget::initializeGL() +{ + glewInit(); +} + +void QStoneOpenGlWidget::MakeCurrent() +{ + this->makeCurrent(); +} + +void QStoneOpenGlWidget::resizeGL(int w, int h) +{ + +} + +void QStoneOpenGlWidget::paintGL() +{ + if (compositor_) + { + compositor_->Refresh(); + } + doneCurrent(); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterMouseEvent& guiEvent, + PointerEvent& pointerEvent, + const QMouseEvent& qtEvent, + const OrthancStone::OpenGLCompositor& compositor) +{ + guiEvent.targetX = qtEvent.x(); + guiEvent.targetY = qtEvent.y(); + pointerEvent.AddPosition(compositor.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY)); + + switch (qtEvent.button()) + { + case Qt::LeftButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; break; + case Qt::MiddleButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_MIDDLE; break; + case Qt::RightButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_RIGHT; break; + default: + guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; + } + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } + +} + +void QStoneOpenGlWidget::mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + OrthancStone::GuiAdapterMouseEvent guiEvent; + PointerEvent pointerEvent; + ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *compositor_); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + sceneInteractor_->OnMouseEvent(guiEvent, pointerEvent); + } + + // force redraw of the OpenGL widget + update(); +} + +void QStoneOpenGlWidget::mousePressEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); +} + +void QStoneOpenGlWidget::mouseMoveEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); +} + +void QStoneOpenGlWidget::mouseReleaseEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEUP); +} diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/QStoneOpenGlWidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,65 @@ +#pragma once +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include +#include + +#include +#include "../../Framework/OpenGL/IOpenGLContext.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "Scene2DInteractor.h" + +namespace OrthancStone +{ + class QStoneOpenGlWidget : public QOpenGLWidget, public OrthancStone::OpenGL::IOpenGLContext + { + boost::shared_ptr compositor_; + boost::shared_ptr sceneInteractor_; + + public: + QStoneOpenGlWidget(QWidget *parent) : + QOpenGLWidget(parent) + { + } + + protected: + + //**** QWidget overrides + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + + //**** IOpenGLContext overrides + + virtual void MakeCurrent() override; + virtual void SwapBuffer() override {} + + virtual unsigned int GetCanvasWidth() const override + { + return this->width(); + } + + virtual unsigned int GetCanvasHeight() const override + { + return this->height(); + } + + public: + void SetInteractor(boost::shared_ptr sceneInteractor) + { + sceneInteractor_ = sceneInteractor; + } + + void SetCompositor(boost::shared_ptr compositor) + { + compositor_ = compositor; + } + + protected: + void mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + + }; +} diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/Scene2DInteractor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,38 @@ +#include "Scene2DInteractor.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" + + +namespace OrthancStone +{ + +} + +using namespace OrthancStone; + + +void BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + currentTracker_->PointerMove(pointerEvent); + } + else + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT) + { + // TODO: need a pointer to compositor currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, viewportController_->)); + } + } + +} + + diff -r 80829436ce0c -r 31319fe867b9 Samples/Qt/Scene2DInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.h Tue Jul 09 11:46:43 2019 +0200 @@ -0,0 +1,37 @@ +#pragma once + +#include "../../Framework/Scene2D/PointerEvent.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Applications/Generic/GuiAdapter.h" + + +namespace OrthancStone +{ + + class Scene2DInteractor + { + protected: + boost::shared_ptr viewportController_; + boost::shared_ptr currentTracker_; + + public: + Scene2DInteractor(boost::shared_ptr viewportController) : + viewportController_(viewportController) + {} + + virtual void OnMouseEvent(const GuiAdapterMouseEvent& guiEvent, const PointerEvent& pointerEvent) = 0; + + }; +} + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ +public: + BasicScene2DInteractor(boost::shared_ptr viewportController) : + Scene2DInteractor(viewportController) + {} + + virtual void OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; +}; + diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/BasicScene.cpp --- a/Samples/Sdl/BasicScene.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/BasicScene.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -29,6 +29,7 @@ #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/Scene2D/ZoomSceneTracker.h" #include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Messages/MessageBroker.h" @@ -376,8 +377,9 @@ try { MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); boost::shared_ptr controller = boost::make_shared( - boost::ref(broker)); + undoStack, boost::ref(broker)); PrepareScene(controller); Run(controller); } diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/CMakeLists.txt --- a/Samples/Sdl/CMakeLists.txt Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Tue Jul 09 11:46:43 2019 +0200 @@ -47,6 +47,11 @@ include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + ##################################################################### ## Build the samples ##################################################################### diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/FusionMprSdl.cpp --- a/Samples/Sdl/FusionMprSdl.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/FusionMprSdl.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -31,6 +31,7 @@ #include "../../Framework/Scene2D/ZoomSceneTracker.h" #include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" #include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" #include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" #include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" @@ -407,6 +408,7 @@ , oracleObservable_(broker) , oracle_(*this) , currentTool_(FusionMprGuiTool_Rotate) + , undoStack_(new UndoStack) { //oracleObservable.RegisterObserverCallback //(new Callable @@ -425,7 +427,7 @@ (*this, &FusionMprSdlApp::Handle)); controller_ = boost::shared_ptr( - new ViewportController(broker_)); + new ViewportController(undoStack_, broker_)); controller_->RegisterObserverCallback( new Callable diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/FusionMprSdl.h --- a/Samples/Sdl/FusionMprSdl.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/FusionMprSdl.h Tue Jul 09 11:46:43 2019 +0200 @@ -61,6 +61,7 @@ static const unsigned int FONT_SIZE_1 = 24; class Scene2D; + class UndoStack; /** This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that @@ -194,6 +195,8 @@ int FIXED_INFOTEXT_LAYER_ZINDEX; FusionMprGuiTool currentTool_; + boost::shared_ptr undoStack_; + }; } diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/TrackerSampleApp.cpp --- a/Samples/Sdl/TrackerSampleApp.cpp Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/TrackerSampleApp.cpp Tue Jul 09 11:46:43 2019 +0200 @@ -29,6 +29,7 @@ #include "../../Framework/Scene2D/RotateSceneTracker.h" #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" #include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" #include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" #include "../../Framework/StoneInitialization.h" @@ -246,7 +247,7 @@ DisplayFloatingCtrlInfoText(e); } - else + else if (activeTracker_.get() != NULL) { HideInfoText(); //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; @@ -267,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 > 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) { @@ -458,8 +485,10 @@ TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker) , currentTool_(GuiTool_Rotate) + , undoStack_(new UndoStack) { - controller_ = boost::shared_ptr(new ViewportController(broker)); + controller_ = boost::shared_ptr( + new ViewportController(undoStack_, broker)); controller_->RegisterObserverCallback( new Callable @@ -593,6 +622,15 @@ boost::shared_ptr TrackerSampleApp::TrackerHitTest(const PointerEvent & e) { // std::vector> measureTools_; + ScenePoint2D scenePos = e.GetMainPosition().Apply( + controller_->GetScene()->GetCanvasToSceneTransform()); + + std::vector > measureTools = controller_->HitTestMeasureTools(scenePos); + + if (measureTools.size() > 0) + { + return measureTools[0]->CreateEditionTracker(e); + } return boost::shared_ptr(); } diff -r 80829436ce0c -r 31319fe867b9 Samples/Sdl/TrackerSampleApp.h --- a/Samples/Sdl/TrackerSampleApp.h Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/Sdl/TrackerSampleApp.h Tue Jul 09 11:46:43 2019 +0200 @@ -52,6 +52,7 @@ static const unsigned int FONT_SIZE_1 = 24; class Scene2D; + class UndoStack; class TrackerSampleApp : public IObserver , public boost::enable_shared_from_this @@ -131,6 +132,7 @@ int FIXED_INFOTEXT_LAYER_ZINDEX; GuiTool currentTool_; + boost::shared_ptr undoStack_; }; } diff -r 80829436ce0c -r 31319fe867b9 Samples/WebAssembly/CMakeLists.txt --- a/Samples/WebAssembly/CMakeLists.txt Thu Jun 13 16:47:02 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Tue Jul 09 11:46:43 2019 +0200 @@ -60,6 +60,10 @@ include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build the samples