Mercurial > hg > orthanc-stone
changeset 289:36ebe6ec8fe8 am-2
merged default into am-2
author | am@osimis.io |
---|---|
date | Thu, 30 Aug 2018 16:56:08 +0200 |
parents | 8c8da145fefa (diff) 3a8bac805352 (current diff) |
children | 87376a645ee1 |
files | |
diffstat | 128 files changed, 6152 insertions(+), 1673 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,7 @@ +Platforms/Generic/CMakeLists.txt.user +Platforms/Generic/ThirdPartyDownloads/ +Platforms/Wasm/CMakeLists.txt.user +Platforms/Wasm/build/ +Platforms/Wasm/build-web/ +Platforms/Wasm/ThirdPartyDownloads/ +Applications/Qt/archive/
--- a/Applications/BasicApplicationContext.cpp Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "BasicApplicationContext.h" - -namespace OrthancStone -{ - void BasicApplicationContext::UpdateThread(BasicApplicationContext* that) - { - while (!that->stopped_) - { - { - ViewportLocker locker(*that); - locker.GetViewport().UpdateContent(); - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_)); - } - } - - - BasicApplicationContext::BasicApplicationContext(Orthanc::WebServiceParameters& orthanc) : - oracle_(viewportMutex_, 4), // Use 4 threads to download - //oracle_(viewportMutex_, 1), // Disable threading to be reproducible - webService_(oracle_, orthanc), - stopped_(true), - updateDelay_(100) // By default, 100ms between each refresh of the content - { - srand(time(NULL)); - } - - - BasicApplicationContext::~BasicApplicationContext() - { - for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - for (SlicedVolumes::iterator it = slicedVolumes_.begin(); it != slicedVolumes_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - for (VolumeLoaders::iterator it = volumeLoaders_.begin(); it != volumeLoaders_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - } - - - IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget) // Takes ownership - { - viewport_.SetCentralWidget(widget); - return *widget; - } - - - ISlicedVolume& BasicApplicationContext::AddSlicedVolume(ISlicedVolume* volume) - { - if (volume == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - slicedVolumes_.push_back(volume); - return *volume; - } - } - - - IVolumeLoader& BasicApplicationContext::AddVolumeLoader(IVolumeLoader* loader) - { - if (loader == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - volumeLoaders_.push_back(loader); - return *loader; - } - } - - - IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor) - { - if (interactor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - interactors_.push_back(interactor); - - return *interactor; - } - - - void BasicApplicationContext::Start() - { - oracle_.Start(); - - if (viewport_.HasUpdateContent()) - { - stopped_ = false; - updateThread_ = boost::thread(UpdateThread, this); - } - } - - - void BasicApplicationContext::Stop() - { - stopped_ = true; - - if (updateThread_.joinable()) - { - updateThread_.join(); - } - - oracle_.Stop(); - } -}
--- a/Applications/BasicApplicationContext.h Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Framework/Viewport/WidgetViewport.h" -#include "../Framework/Volumes/ISlicedVolume.h" -#include "../Framework/Volumes/IVolumeLoader.h" -#include "../Framework/Widgets/IWorldSceneInteractor.h" -#include "../Platforms/Generic/OracleWebService.h" - -#include <list> -#include <boost/thread.hpp> - -namespace OrthancStone -{ - class BasicApplicationContext : public boost::noncopyable - { - private: - typedef std::list<ISlicedVolume*> SlicedVolumes; - typedef std::list<IVolumeLoader*> VolumeLoaders; - typedef std::list<IWorldSceneInteractor*> Interactors; - - static void UpdateThread(BasicApplicationContext* that); - - Oracle oracle_; - OracleWebService webService_; - boost::mutex viewportMutex_; - WidgetViewport viewport_; - SlicedVolumes slicedVolumes_; - VolumeLoaders volumeLoaders_; - Interactors interactors_; - boost::thread updateThread_; - bool stopped_; - unsigned int updateDelay_; - - public: - class ViewportLocker : public boost::noncopyable - { - private: - boost::mutex::scoped_lock lock_; - IViewport& viewport_; - - public: - ViewportLocker(BasicApplicationContext& that) : - lock_(that.viewportMutex_), - viewport_(that.viewport_) - { - } - - IViewport& GetViewport() const - { - return viewport_; - } - }; - - - BasicApplicationContext(Orthanc::WebServiceParameters& orthanc); - - ~BasicApplicationContext(); - - IWidget& SetCentralWidget(IWidget* widget); // Takes ownership - - IWebService& GetWebService() - { - return webService_; - } - - ISlicedVolume& AddSlicedVolume(ISlicedVolume* volume); - - IVolumeLoader& AddVolumeLoader(IVolumeLoader* loader); - - IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor); - - void Start(); - - void Stop(); - - void SetUpdateDelay(unsigned int delay) // In milliseconds - { - updateDelay_ = delay; - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/BasicNativeApplicationContext.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,80 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "BasicNativeApplicationContext.h" +#include "../../Platforms/Generic/OracleWebService.h" + +namespace OrthancStone +{ + IWidget& BasicNativeApplicationContext::SetCentralWidget(IWidget* widget) // Takes ownership + { + centralViewport_->SetCentralWidget(widget); + return *widget; + } + + + void BasicNativeApplicationContext::UpdateThread(BasicNativeApplicationContext* that) + { + while (!that->stopped_) + { + { + GlobalMutexLocker locker(*that); + that->GetCentralViewport().UpdateContent(); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelayInMs_)); + } + } + + + BasicNativeApplicationContext::BasicNativeApplicationContext() : + centralViewport_(new OrthancStone::WidgetViewport()), + stopped_(true), + updateDelayInMs_(100) // By default, 100ms between each refresh of the content + { + srand(time(NULL)); + } + + + void BasicNativeApplicationContext::Start() + { + dynamic_cast<OracleWebService*>(webService_)->Start(); + + if (centralViewport_->HasUpdateContent()) + { + stopped_ = false; + updateThread_ = boost::thread(UpdateThread, this); + } + } + + + void BasicNativeApplicationContext::Stop() + { + stopped_ = true; + + if (updateThread_.joinable()) + { + updateThread_.join(); + } + + dynamic_cast<OracleWebService*>(webService_)->Stop(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/BasicNativeApplicationContext.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,73 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Framework/Viewport/WidgetViewport.h" +#include "../../Framework/Volumes/ISlicedVolume.h" +#include "../../Framework/Volumes/IVolumeLoader.h" + +#include <list> +#include <boost/thread.hpp> +#include "../StoneApplicationContext.h" + +namespace OrthancStone +{ + class BasicNativeApplicationContext : public StoneApplicationContext + { + private: + + static void UpdateThread(BasicNativeApplicationContext* that); + + boost::mutex globalMutex_; + std::unique_ptr<WidgetViewport> centralViewport_; + boost::thread updateThread_; + bool stopped_; + unsigned int updateDelayInMs_; + + public: + class GlobalMutexLocker: public boost::noncopyable + { + boost::mutex::scoped_lock lock_; + public: + GlobalMutexLocker(BasicNativeApplicationContext& that): + lock_(that.globalMutex_) + {} + }; + + BasicNativeApplicationContext(); + + virtual ~BasicNativeApplicationContext() {} + + virtual IWidget& SetCentralWidget(IWidget* widget); // Takes ownership + IViewport& GetCentralViewport() {return *(centralViewport_.get());} + + void Start(); + + void Stop(); + + void SetUpdateDelay(unsigned int delayInMs) + { + updateDelayInMs_ = delayInMs; + } + + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/BasicNativeApplicationRunner.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,240 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if ORTHANC_ENABLE_NATIVE != 1 +#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1 +#endif + +#include "BasicNativeApplicationRunner.h" +#include "BasicNativeApplicationContext.h" +#include <boost/program_options.hpp> + +#include "../../Framework/Toolbox/MessagingToolbox.h" + +#include <Core/Logging.h> +#include <Core/HttpClient.h> +#include <Core/Toolbox.h> +#include <Plugins/Samples/Common/OrthancHttpConnection.h> +#include "../../Platforms/Generic/OracleWebService.h" + +namespace OrthancStone +{ + // Anonymous namespace to avoid clashes against other compilation modules + namespace + { + class LogStatusBar : public IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + LOG(WARNING) << message; + } + }; + } + + int BasicNativeApplicationRunner::Execute(int argc, + char* argv[]) + { + /****************************************************************** + * Initialize all the subcomponents of Orthanc Stone + ******************************************************************/ + + Orthanc::Logging::Initialize(); + Orthanc::Toolbox::InitializeOpenSsl(); + Orthanc::HttpClient::GlobalInitialize(); + + Initialize(); + + /****************************************************************** + * Declare and parse the command-line options of the application + ******************************************************************/ + + boost::program_options::options_description options; + + { // generic options + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"), + "URL to the Orthanc server") + ("username", "Username for the Orthanc server") + ("password", "Password for the Orthanc server") + ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates") + ; + + options.add(generic); + } + + // platform specific options + DeclareCommandLineOptions(options); + + // application specific options + application_.DeclareStartupOptions(options); + + boost::program_options::variables_map parameters; + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(options).run(), parameters); + boost::program_options::notify(parameters); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + + /****************************************************************** + * Configure the application with the command-line parameters + ******************************************************************/ + + if (error || parameters.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]..." + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Demonstration application of Orthanc Stone using SDL." + << std::endl; + + std::cout << options << "\n"; + return error ? -1 : 0; + } + + if (parameters.count("https-verify") && + !parameters["https-verify"].as<bool>()) + { + LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)"; + Orthanc::HttpClient::ConfigureSsl(false, ""); + } + + if (parameters.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + ParseCommandLineOptions(parameters); + + + bool success = true; + try + { + /**************************************************************** + * Initialize the connection to the Orthanc server + ****************************************************************/ + + Orthanc::WebServiceParameters webServiceParameters; + + if (parameters.count("orthanc")) + { + webServiceParameters.SetUrl(parameters["orthanc"].as<std::string>()); + } + + if (parameters.count("username")) + { + webServiceParameters.SetUsername(parameters["username"].as<std::string>()); + } + + if (parameters.count("password")) + { + webServiceParameters.SetPassword(parameters["password"].as<std::string>()); + } + + LOG(WARNING) << "URL to the Orthanc REST API: " << webServiceParameters.GetUrl(); + + { + OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters); + if (!MessagingToolbox::CheckOrthancVersion(orthanc)) + { + LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + + /**************************************************************** + * Initialize the application + ****************************************************************/ + + LOG(WARNING) << "Creating the widgets of the application"; + + LogStatusBar statusBar; + + BasicNativeApplicationContext context; + Oracle oracle(4); // use 4 threads to download content + OracleWebService webService(broker_, oracle, webServiceParameters, context); + context.SetWebService(webService); + + application_.Initialize(&context, statusBar, parameters); + + { + BasicNativeApplicationContext::GlobalMutexLocker locker(context); + context.SetCentralWidget(application_.GetCentralWidget()); + context.GetCentralViewport().SetStatusBar(statusBar); + } + + std::string title = application_.GetTitle(); + if (title.empty()) + { + title = "Stone of Orthanc"; + } + + /**************************************************************** + * Run the application + ****************************************************************/ + + Run(context, title, argc, argv); + + /**************************************************************** + * Finalize the application + ****************************************************************/ + + LOG(WARNING) << "The application is stopping"; + application_.Finalize(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + success = false; + } + + + /****************************************************************** + * Finalize all the subcomponents of Orthanc Stone + ******************************************************************/ + + Finalize(); + Orthanc::HttpClient::GlobalFinalize(); + Orthanc::Toolbox::FinalizeOpenSsl(); + + return (success ? 0 : -1); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/BasicNativeApplicationRunner.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IStoneApplication.h" + +#if ORTHANC_ENABLE_NATIVE != 1 +#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1 +#endif + +namespace OrthancStone +{ + class BasicNativeApplicationContext; + + class BasicNativeApplicationRunner + { + protected: + MessageBroker& broker_; + IStoneApplication& application_; + public: + + BasicNativeApplicationRunner(MessageBroker& broker, + IStoneApplication& application) + : broker_(broker), + application_(application) + { + } + int Execute(int argc, + char* argv[]); + + virtual void Initialize() = 0; + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0; + virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters) = 0; + + virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]) = 0; + virtual void Finalize() = 0; + }; + +}
--- a/Applications/IBasicApplication.cpp Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,300 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "IBasicApplication.h" - -#include "../Framework/Toolbox/MessagingToolbox.h" -#include "Sdl/SdlEngine.h" - -#include <Core/Logging.h> -#include <Core/HttpClient.h> -#include <Core/Toolbox.h> -#include <Plugins/Samples/Common/OrthancHttpConnection.h> - -namespace OrthancStone -{ - // Anonymous namespace to avoid clashes against other compilation modules - namespace - { - class LogStatusBar : public IStatusBar - { - public: - virtual void ClearMessage() - { - } - - virtual void SetMessage(const std::string& message) - { - LOG(WARNING) << message; - } - }; - } - - -#if ORTHANC_ENABLE_SDL == 1 - static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options) - { - // Declare the supported parameters - boost::program_options::options_description generic("Generic options"); - generic.add_options() - ("help", "Display this help and exit") - ("verbose", "Be verbose in logs") - ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"), - "URL to the Orthanc server") - ("username", "Username for the Orthanc server") - ("password", "Password for the Orthanc server") - ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates") - ; - - options.add(generic); - - boost::program_options::options_description sdl("SDL options"); - sdl.add_options() - ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window") - ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window") - ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL") - ; - - options.add(sdl); - } - - - int IBasicApplication::ExecuteWithSdl(IBasicApplication& application, - int argc, - char* argv[]) - { - /****************************************************************** - * Initialize all the subcomponents of Orthanc Stone - ******************************************************************/ - - Orthanc::Logging::Initialize(); - Orthanc::Toolbox::InitializeOpenSsl(); - Orthanc::HttpClient::GlobalInitialize(); - SdlWindow::GlobalInitialize(); - - - /****************************************************************** - * Declare and parse the command-line options of the application - ******************************************************************/ - - boost::program_options::options_description options; - DeclareSdlCommandLineOptions(options); - application.DeclareCommandLineOptions(options); - - boost::program_options::variables_map parameters; - bool error = false; - - try - { - boost::program_options::store(boost::program_options::command_line_parser(argc, argv). - options(options).run(), parameters); - boost::program_options::notify(parameters); - } - catch (boost::program_options::error& e) - { - LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); - error = true; - } - - - /****************************************************************** - * Configure the application with the command-line parameters - ******************************************************************/ - - if (error || parameters.count("help")) - { - std::cout << std::endl - << "Usage: " << argv[0] << " [OPTION]..." - << std::endl - << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." - << std::endl << std::endl - << "Demonstration application of Orthanc Stone using SDL." - << std::endl; - - std::cout << options << "\n"; - return error ? -1 : 0; - } - - if (parameters.count("https-verify") && - !parameters["https-verify"].as<bool>()) - { - LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)"; - Orthanc::HttpClient::ConfigureSsl(false, ""); - } - - if (parameters.count("verbose")) - { - Orthanc::Logging::EnableInfoLevel(true); - } - - if (!parameters.count("width") || - !parameters.count("height") || - !parameters.count("opengl")) - { - LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing"; - return -1; - } - - int w = parameters["width"].as<int>(); - int h = parameters["height"].as<int>(); - if (w <= 0 || h <= 0) - { - LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive"; - return -1; - } - - unsigned int width = static_cast<unsigned int>(w); - unsigned int height = static_cast<unsigned int>(h); - LOG(WARNING) << "Initial display size: " << width << "x" << height; - - bool opengl = parameters["opengl"].as<bool>(); - if (opengl) - { - LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes"; - } - else - { - LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance"; - } - - bool success = true; - try - { - /**************************************************************** - * Initialize the connection to the Orthanc server - ****************************************************************/ - - Orthanc::WebServiceParameters webService; - - if (parameters.count("orthanc")) - { - webService.SetUrl(parameters["orthanc"].as<std::string>()); - } - - std::string username, password; - - if (parameters.count("username")) - { - username = parameters["username"].as<std::string>(); - } - - if (parameters.count("password")) - { - password = parameters["password"].as<std::string>(); - } - - if (!username.empty() || - !password.empty()) - { - webService.SetCredentials(username, password); - } - - LOG(WARNING) << "URL to the Orthanc REST API: " << webService.GetUrl(); - - { - OrthancPlugins::OrthancHttpConnection orthanc(webService); - if (!MessagingToolbox::CheckOrthancVersion(orthanc)) - { - LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - - /**************************************************************** - * Initialize the application - ****************************************************************/ - - LOG(WARNING) << "Creating the widgets of the application"; - - LogStatusBar statusBar; - BasicApplicationContext context(webService); - - application.Initialize(context, statusBar, parameters); - - { - BasicApplicationContext::ViewportLocker locker(context); - locker.GetViewport().SetStatusBar(statusBar); - } - - std::string title = application.GetTitle(); - if (title.empty()) - { - title = "Stone of Orthanc"; - } - - { - /************************************************************** - * Run the application inside a SDL window - **************************************************************/ - - LOG(WARNING) << "Starting the application"; - - SdlWindow window(title.c_str(), width, height, opengl); - SdlEngine sdl(window, context); - - { - BasicApplicationContext::ViewportLocker locker(context); - locker.GetViewport().Register(sdl); // (*) - } - - context.Start(); - sdl.Run(); - - LOG(WARNING) << "Stopping the application"; - - // Don't move the "Stop()" command below out of the block, - // otherwise the application might crash, because the - // "SdlEngine" is an observer of the viewport (*) and the - // update thread started by "context.Start()" would call a - // destructed object (the "SdlEngine" is deleted with the - // lexical scope). - context.Stop(); - } - - - /**************************************************************** - * Finalize the application - ****************************************************************/ - - LOG(WARNING) << "The application has stopped"; - application.Finalize(); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - success = false; - } - - - /****************************************************************** - * Finalize all the subcomponents of Orthanc Stone - ******************************************************************/ - - SdlWindow::GlobalFinalize(); - Orthanc::HttpClient::GlobalFinalize(); - Orthanc::Toolbox::FinalizeOpenSsl(); - - return (success ? 0 : -1); - } -#endif - -}
--- a/Applications/IBasicApplication.h Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "BasicApplicationContext.h" - -#include <boost/program_options.hpp> - -#if ORTHANC_ENABLE_SDL == 1 -# include <SDL.h> // Necessary to avoid undefined reference to `SDL_main' -#endif - -namespace OrthancStone -{ - class IBasicApplication : public boost::noncopyable - { - public: - virtual ~IBasicApplication() - { - } - - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0; - - virtual std::string GetTitle() const = 0; - - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) = 0; - - virtual void Finalize() = 0; - -#if ORTHANC_ENABLE_SDL == 1 - static int ExecuteWithSdl(IBasicApplication& application, - int argc, - char* argv[]); -#endif - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/IStoneApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "StoneApplicationContext.h" +#include <boost/program_options.hpp> +#include "../Framework/Viewport/WidgetViewport.h" +#include "json/json.h" + +namespace OrthancStone +{ + // a StoneApplication is an application that can actually be executed + // in multiple environments. i.e: it can run natively integrated in a QtApplication + // or it can be executed as part of a WebPage when compiled into WebAssembly. + class IStoneApplication : public boost::noncopyable + { + protected: + StoneApplicationContext* context_; + + public: + virtual ~IStoneApplication() + { + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) = 0; + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) = 0; +#if ORTHANC_ENABLE_WASM==1 + virtual void InitializeWasm() {} // specific initialization when the app is running in WebAssembly. This is called after the other Initialize() +#endif + + virtual std::string GetTitle() const = 0; + virtual IWidget* GetCentralWidget() = 0; + + virtual void Finalize() = 0; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/BasicQtApplicationRunner.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,67 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if ORTHANC_ENABLE_QT != 1 +#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1 +#endif + +#include "BasicQtApplicationRunner.h" +#include <boost/program_options.hpp> +#include <QApplication> + +#include "../../Framework/Toolbox/MessagingToolbox.h" + +#include <Core/Logging.h> +#include <Core/HttpClient.h> +#include <Core/Toolbox.h> +#include <Plugins/Samples/Common/OrthancHttpConnection.h> +#include "../../Platforms/Generic/OracleWebService.h" + + +namespace OrthancStone +{ + void BasicQtApplicationRunner::Initialize() + { + } + + void BasicQtApplicationRunner::DeclareCommandLineOptions(boost::program_options::options_description& options) + { + } + + void BasicQtApplicationRunner::Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]) + { + context.Start(); + + QApplication app(argc, argv); + InitializeMainWindow(context); + + window_->show(); + app.exec(); + + context.Stop(); + } + + void BasicQtApplicationRunner::Finalize() + { + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/BasicQtApplicationRunner.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,55 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Generic/BasicNativeApplicationRunner.h" +#include "QStoneMainWindow.h" + +#if ORTHANC_ENABLE_QT != 1 +#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1 +#endif + +namespace OrthancStone +{ + class BasicQtApplicationRunner : public BasicNativeApplicationRunner + { + protected: + std::auto_ptr<QStoneMainWindow> window_; + + virtual void InitializeMainWindow(BasicNativeApplicationContext& context) = 0; + public: + BasicQtApplicationRunner(MessageBroker& broker, + IStoneApplication& application) + : BasicNativeApplicationRunner(broker, application) + { + } + + + virtual void Initialize(); + + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options); + virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters) {} + virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]); + virtual void Finalize(); + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/QCairoWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,164 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "QCairoWidget.h" + +#include <QPainter> +#include <QPaintEvent> + +#include <stdexcept> + +QCairoWidget::QCairoWidget(QWidget *parent) : + QWidget(parent), + context_(NULL) +{ + setFocusPolicy(Qt::StrongFocus); // catch keyPressEvents +} + +QCairoWidget::~QCairoWidget() +{ +} + +void QCairoWidget::SetContext(OrthancStone::BasicNativeApplicationContext& context) +{ + context_ = &context; + context_->GetCentralViewport().Register(*this); // get notified each time the content of the central viewport changes +} + +void QCairoWidget::paintEvent(QPaintEvent* /*event*/) +{ + QPainter painter(this); + + if (image_.get() != NULL && context_ != NULL) + { + OrthancStone::BasicNativeApplicationContext::GlobalMutexLocker locker(*context_); + OrthancStone::IViewport& viewport = context_->GetCentralViewport(); + Orthanc::ImageAccessor a = surface_.GetAccessor(); + viewport.Render(a); + painter.drawImage(0, 0, *image_); + } + else + { + painter.fillRect(rect(), Qt::red); + } +} + +OrthancStone::KeyboardModifiers GetKeyboardModifiers(QInputEvent* event) +{ + Qt::KeyboardModifiers qtModifiers = event->modifiers(); + int stoneModifiers = static_cast<int>(OrthancStone::KeyboardModifiers_None); + if ((qtModifiers & Qt::AltModifier) != 0) + { + stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Alt); + } + if ((qtModifiers & Qt::ControlModifier) != 0) + { + stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Control); + } + if ((qtModifiers & Qt::ShiftModifier) != 0) + { + stoneModifiers |= static_cast<int>(OrthancStone::KeyboardModifiers_Shift); + } + return static_cast<OrthancStone::KeyboardModifiers>(stoneModifiers); +} + +void QCairoWidget::mousePressEvent(QMouseEvent* event) +{ + OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event); + + OrthancStone::MouseButton button; + + switch (event->button()) + { + case Qt::LeftButton: + button = OrthancStone::MouseButton_Left; + break; + + case Qt::RightButton: + button = OrthancStone::MouseButton_Right; + break; + + case Qt::MiddleButton: + button = OrthancStone::MouseButton_Middle; + break; + + default: + return; // Unsupported button + } + context_->GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers); +} + + +void QCairoWidget::mouseReleaseEvent(QMouseEvent* /*eventNotUsed*/) +{ + context_->GetCentralViewport().MouseLeave(); +} + + +void QCairoWidget::mouseMoveEvent(QMouseEvent* event) +{ + context_->GetCentralViewport().MouseMove(event->pos().x(), event->pos().y()); +} + + +void QCairoWidget::wheelEvent(QWheelEvent * event) +{ + OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event); + + if (event->orientation() == Qt::Vertical) + { + if (event->delta() < 0) // TODO: compare direction with SDL and make sure we send the same directions + { + context_->GetCentralViewport().MouseWheel(OrthancStone::MouseWheelDirection_Up, event->pos().x(), event->pos().y(), stoneModifiers); + } + else + { + context_->GetCentralViewport().MouseWheel(OrthancStone::MouseWheelDirection_Down, event->pos().x(), event->pos().y(), stoneModifiers); + } + } +} + +void QCairoWidget::keyPressEvent(QKeyEvent *event) +{ + OrthancStone::KeyboardModifiers stoneModifiers = GetKeyboardModifiers(event); + + context_->GetCentralViewport().KeyPressed(event->text()[0].toLatin1(), stoneModifiers); +} + + +void QCairoWidget::resizeEvent(QResizeEvent* event) +{ + grabGesture(Qt::PanGesture); + QWidget::resizeEvent(event); + + if (event) + { + surface_.SetSize(event->size().width(), event->size().height()); + + image_.reset(new QImage(reinterpret_cast<uchar*>(surface_.GetBuffer()), + event->size().width(), + event->size().height(), + surface_.GetPitch(), + QImage::Format_RGB32)); + + context_->GetCentralViewport().SetSize(event->size().width(), event->size().height()); + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/QCairoWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,75 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include "../../Framework/Widgets/CairoWidget.h" +#include "../../Applications/Generic/BasicNativeApplicationContext.h" +#include "../../Framework/Viewport/CairoSurface.h" + +#include <QWidget> +#include <QGestureEvent> +#include <memory> +#include <cassert> + +class QCairoWidget : public QWidget, public OrthancStone::IViewport::IObserver +{ + Q_OBJECT + +private: + std::auto_ptr<QImage> image_; + OrthancStone::CairoSurface surface_; + OrthancStone::BasicNativeApplicationContext* context_; + +protected: + virtual void paintEvent(QPaintEvent *event); + + virtual void resizeEvent(QResizeEvent *event); + + virtual void mouseMoveEvent(QMouseEvent *event); + + virtual void mousePressEvent(QMouseEvent *event); + + virtual void mouseReleaseEvent(QMouseEvent *event); + + virtual void wheelEvent(QWheelEvent *event); + + virtual void keyPressEvent(QKeyEvent *event); + +public: + explicit QCairoWidget(QWidget *parent); + + virtual ~QCairoWidget(); + + void SetContext(OrthancStone::BasicNativeApplicationContext& context); + + virtual void OnViewportContentChanged(const OrthancStone::IViewport& /*sceneNotUsed*/) + { + update(); // schedule a repaint (handled by Qt) + emit ContentChanged(); + } + +signals: + + void ContentChanged(); + +public slots: + +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/QStoneMainWindow.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,41 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "QStoneMainWindow.h" + +namespace OrthancStone +{ + + QStoneMainWindow::QStoneMainWindow(BasicNativeApplicationContext& context, QWidget *parent) : + QMainWindow(parent), + context_(context) + { + } + + void QStoneMainWindow::SetCentralStoneWidget(QCairoWidget *centralWidget) + { + cairoCentralWidget_ = centralWidget; + cairoCentralWidget_->SetContext(context_); + } + + QStoneMainWindow::~QStoneMainWindow() + { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Qt/QStoneMainWindow.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,45 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ +#pragma once + +#include <QMainWindow> + +#include "QCairoWidget.h" +#include "../Generic/BasicNativeApplicationContext.h" + +namespace OrthancStone +{ + class QStoneMainWindow : public QMainWindow + { + Q_OBJECT + + private: + OrthancStone::BasicNativeApplicationContext& context_; + QCairoWidget *cairoCentralWidget_; + + protected: // you must inherit this class + QStoneMainWindow(BasicNativeApplicationContext& context, QWidget *parent = 0); + void SetCentralStoneWidget(QCairoWidget* centralWidget); + public: + virtual ~QStoneMainWindow(); + + }; + +}
--- a/Applications/Samples/EmptyApplication.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Samples/EmptyApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -32,7 +32,7 @@ class EmptyApplication : public SampleApplicationBase { public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -44,15 +44,14 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { int red = parameters["red"].as<int>(); int green = parameters["green"].as<int>(); int blue = parameters["blue"].as<int>(); - context.SetCentralWidget(new EmptyWidget(red, green, blue)); + context_->SetCentralWidget(new EmptyWidget(red, green, blue)); } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Qt/SampleMainWindow.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,86 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "SampleMainWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of <ui_MainWindow.h> below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include <ui_SampleMainWindow.h> +#include "../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + SampleMainWindow::SampleMainWindow(OrthancStone::BasicNativeApplicationContext& context, OrthancStone::Samples::SampleApplicationBase& stoneSampleApplication, QWidget *parent) : + QStoneMainWindow(context, parent), + ui_(new Ui::SampleMainWindow), + stoneSampleApplication_(stoneSampleApplication) + { + ui_->setupUi(this); + SetCentralStoneWidget(ui_->cairoCentralWidget); + + connect(ui_->toolButton1, &QToolButton::clicked, this, &SampleMainWindow::tool1Clicked); + connect(ui_->toolButton2, &QToolButton::clicked, this, &SampleMainWindow::tool2Clicked); + connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindow::pushButton1Clicked); + connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindow::pushButton2Clicked); + + std::string pushButton1Name; + std::string pushButton2Name; + std::string tool1Name; + std::string tool2Name; + stoneSampleApplication_.GetButtonNames(pushButton1Name, pushButton2Name, tool1Name, tool2Name); + + ui_->toolButton1->setText(QString::fromStdString(tool1Name)); + ui_->toolButton2->setText(QString::fromStdString(tool2Name)); + ui_->pushButton1->setText(QString::fromStdString(pushButton1Name)); + ui_->pushButton2->setText(QString::fromStdString(pushButton2Name)); + } + + SampleMainWindow::~SampleMainWindow() + { + delete ui_; + } + + void SampleMainWindow::tool1Clicked() + { + stoneSampleApplication_.OnTool1Clicked(); + } + + void SampleMainWindow::tool2Clicked() + { + stoneSampleApplication_.OnTool2Clicked(); + } + + void SampleMainWindow::pushButton1Clicked() + { + stoneSampleApplication_.OnPushButton1Clicked(); + } + + void SampleMainWindow::pushButton2Clicked() + { + stoneSampleApplication_.OnPushButton2Clicked(); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Qt/SampleMainWindow.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ +#pragma once + +#include "../../Qt/QCairoWidget.h" +#include "../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class SampleMainWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + class SampleApplicationBase; + + class SampleMainWindow : public QStoneMainWindow + { + Q_OBJECT + + private: + Ui::SampleMainWindow* ui_; + SampleApplicationBase& stoneSampleApplication_; + + public: + explicit SampleMainWindow(OrthancStone::BasicNativeApplicationContext& context, SampleApplicationBase& stoneSampleApplication, QWidget *parent = 0); + ~SampleMainWindow(); + + private slots: + void tool1Clicked(); + void tool2Clicked(); + void pushButton1Clicked(); + void pushButton2Clicked(); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Qt/SampleMainWindow.ui Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SampleMainWindow</class> + <widget class="QMainWindow" name="SampleMainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>903</width> + <height>634</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>300</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>500</width> + <height>300</height> + </size> + </property> + <property name="windowTitle"> + <string>Stone of Orthanc</string> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <widget class="QWidget" name="centralwidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QCairoWidget" name="cairoCentralWidget"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>500</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="horizontalGroupBox"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>100</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="toolButton1"> + <property name="text"> + <string>tool1</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toolButton2"> + <property name="text"> + <string>tool2</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton1"> + <property name="text"> + <string>action1</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton2"> + <property name="text"> + <string>action2</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>903</width> + <height>22</height> + </rect> + </property> + <widget class="QMenu" name="menuTest"> + <property name="title"> + <string>Test</string> + </property> + </widget> + <addaction name="menuTest"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <customwidgets> + <customwidget> + <class>QCairoWidget</class> + <extends>QGraphicsView</extends> + <header location="global">QCairoWidget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Qt/SampleQtApplicationRunner.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,51 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Qt/BasicQtApplicationRunner.h" +#include "SampleMainWindow.h" + +#if ORTHANC_ENABLE_QT != 1 +#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1 +#endif + +namespace OrthancStone +{ + namespace Samples + { + class SampleQtApplicationRunner : public OrthancStone::BasicQtApplicationRunner + { + protected: + virtual void InitializeMainWindow(OrthancStone::BasicNativeApplicationContext& context) + { + window_.reset(new SampleMainWindow(context, dynamic_cast<OrthancStone::Samples::SampleApplicationBase&>(application_))); + } + public: + SampleQtApplicationRunner(MessageBroker& broker, + SampleApplicationBase& application) + : OrthancStone::BasicQtApplicationRunner(broker, application) + { + } + + }; + } +}
--- a/Applications/Samples/SampleApplicationBase.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Samples/SampleApplicationBase.h Thu Aug 30 16:56:08 2018 +0200 @@ -21,27 +21,44 @@ #pragma once -#include "../IBasicApplication.h" +#include "../../Applications/IStoneApplication.h" namespace OrthancStone { namespace Samples { - class SampleApplicationBase : public IBasicApplication + class SampleApplicationBase : public IStoneApplication { public: + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + } + + + virtual std::string GetTitle() const { return "Stone of Orthanc - Sample"; } - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) - { + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() {} + virtual void OnTool2Clicked() {} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2 + ) { + pushButton1 = "action1"; + pushButton2 = "action2"; + tool1 = "tool1"; + tool2 = "tool2"; } - virtual void Finalize() - { - } }; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SampleList.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,37 @@ +// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script + +#if ORTHANC_STONE_SAMPLE == 1 +#include "EmptyApplication.h" +typedef OrthancStone::Samples::EmptyApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 2 +#include "TestPatternApplication.h" +typedef OrthancStone::Samples::TestPatternApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 3 +#include "SingleFrameApplication.h" +typedef OrthancStone::Samples::SingleFrameApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 4 +#include "SingleVolumeApplication.h" +typedef OrthancStone::Samples::SingleVolumeApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 5 +#include "BasicPetCtFusionApplication.h" +typedef OrthancStone::Samples::BasicPetCtFusionApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 6 +#include "SynchronizedSeriesApplication.h" +typedef OrthancStone::Samples::SynchronizedSeriesApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 7 +#include "LayoutPetCtFusionApplication.h" +typedef OrthancStone::Samples::LayoutPetCtFusionApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 8 +#include "SimpleViewerApplication.h" +typedef OrthancStone::Samples::SimpleViewerApplication SampleApplication; + +#else +#error Please set the ORTHANC_STONE_SAMPLE macro +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SampleMainNative.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SampleList.h" +#if ORTHANC_ENABLE_SDL==1 +#include "../Sdl/BasicSdlApplication.h" +#endif +#if ORTHANC_ENABLE_QT==1 +#include "Qt/SampleQtApplicationRunner.h" +#endif +#include "../../Framework/Messages/MessageBroker.h" + +int main(int argc, char* argv[]) +{ + OrthancStone::MessageBroker broker; + SampleApplication sampleStoneApplication(broker); + +#if ORTHANC_ENABLE_SDL==1 + OrthancStone::BasicSdlApplication sdlApplication; + return sdlApplication.Execute(broker, sampleStoneApplication, argc, argv); +#endif +#if ORTHANC_ENABLE_QT==1 + OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); + return qtAppRunner.Execute(argc, argv); +#endif +}
--- a/Applications/Samples/SampleMainSdl.cpp Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script - -#if ORTHANC_STONE_SAMPLE == 1 -#include "EmptyApplication.h" -typedef OrthancStone::Samples::EmptyApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 2 -#include "TestPatternApplication.h" -typedef OrthancStone::Samples::TestPatternApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 3 -#include "SingleFrameApplication.h" -typedef OrthancStone::Samples::SingleFrameApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 4 -#include "SingleVolumeApplication.h" -typedef OrthancStone::Samples::SingleVolumeApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 5 -#include "BasicPetCtFusionApplication.h" -typedef OrthancStone::Samples::BasicPetCtFusionApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 6 -#include "SynchronizedSeriesApplication.h" -typedef OrthancStone::Samples::SynchronizedSeriesApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 7 -#include "LayoutPetCtFusionApplication.h" -typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application; - -#else -#error Please set the ORTHANC_STONE_SAMPLE macro -#endif - - -int main(int argc, char* argv[]) -{ - Application application; - - return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SampleMainWasm.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,32 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include <emscripten/emscripten.h> + +#include "SampleList.h" + + +OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) { + + return new SampleApplication(broker); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SimpleViewerApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,417 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../Framework/Layers/OrthancFrameLayerSource.h" +#include "../../Framework/Layers/CircleMeasureTracker.h" +#include "../../Framework/Layers/LineMeasureTracker.h" +#include "../../Framework/Widgets/LayerWidget.h" +#include "../../Framework/Widgets/LayoutWidget.h" +#include "../../Framework/Messages/IObserver.h" +#include "../../Framework/SmartLoader.h" + +#if ORTHANC_ENABLE_WASM==1 +#include "../../Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h" +#include "../../Platforms/Wasm/Defaults.h" +#endif +#include <Core/Logging.h> + +namespace OrthancStone +{ + namespace Samples + { + class SimpleViewerApplication : + public SampleApplicationBase, +#if ORTHANC_ENABLE_WASM==1 + public IStoneApplicationToWebApplicationAdapter, +#endif + public IObserver + { + private: + class ThumbnailInteractor : public IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + public: + ThumbnailInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + double x, + double y, + IStatusBar* statusBar) + { + if (button == MouseButton_Left) + { + statusBar->SetMessage("selected thumbnail " + widget.GetName()); + std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-")); + application_.SelectSeriesInMainViewport(seriesId); + } + return NULL; + } + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + {} + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + {} + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + {} + + }; + + class MainWidgetInteractor : public IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + + public: + MainWidgetInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + double x, + double y, + IStatusBar* statusBar) + { + if (button == MouseButton_Left) + { + if (application_.currentTool_ == Tools_LineMeasure) + { + return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10); + } + else if (application_.currentTool_ == Tools_CircleMeasure) + { + return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10); + } + } + return NULL; + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + } + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + switch (key) + { + case 's': + widget.SetDefaultView(); + break; + + default: + break; + } + } + }; + + enum Tools { + Tools_LineMeasure, + Tools_CircleMeasure + }; + + Tools currentTool_; + std::unique_ptr<MainWidgetInteractor> mainWidgetInteractor_; + std::unique_ptr<ThumbnailInteractor> thumbnailInteractor_; + LayoutWidget* mainLayout_; + LayoutWidget* thumbnailsLayout_; + LayerWidget* mainWidget_; + std::vector<LayerWidget*> thumbnails_; + std::map<std::string, std::vector<std::string>> instancesIdsPerSeriesId_; + std::map<std::string, Json::Value> seriesTags_; + + unsigned int currentInstanceIndex_; + OrthancStone::WidgetViewport* wasmViewport1_; + OrthancStone::WidgetViewport* wasmViewport2_; + + IStatusBar* statusBar_; + std::unique_ptr<SmartLoader> smartLoader_; + std::unique_ptr<OrthancApiClient> orthancApiClient_; + + public: + SimpleViewerApplication(MessageBroker& broker) : + IObserver(broker), + currentTool_(Tools_LineMeasure), + mainLayout_(NULL), + currentInstanceIndex_(0), + wasmViewport1_(NULL), + wasmViewport2_(NULL) + { + DeclareIgnoredMessage(MessageType_Widget_ContentChanged); + DeclareHandledMessage(MessageType_Widget_GeometryChanged); + + DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready); + DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready); + DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready); + } + + virtual void Finalize() {} + virtual IWidget* GetCentralWidget() {return mainLayout_;} + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("studyId", boost::program_options::value<std::string>(), + "Orthanc ID of the study") + ; + + options.add(generic); + } + + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + statusBar_ = &statusBar; + + {// initialize viewports and layout + mainLayout_ = new LayoutWidget("main-layout"); + mainLayout_->SetPadding(10); + mainLayout_->SetBackgroundCleared(true); + mainLayout_->SetBackgroundColor(0, 0, 0); + mainLayout_->SetHorizontal(); + + thumbnailsLayout_ = new LayoutWidget("thumbnail-layout"); + thumbnailsLayout_->SetPadding(10); + thumbnailsLayout_->SetBackgroundCleared(true); + thumbnailsLayout_->SetBackgroundColor(50, 50, 50); + thumbnailsLayout_->SetVertical(); + + mainWidget_ = new LayerWidget(broker_, "main-viewport"); + mainWidget_->RegisterObserver(*this); + + // hierarchy + mainLayout_->AddWidget(thumbnailsLayout_); + mainLayout_->AddWidget(mainWidget_); + + // sources + smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService())); + smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + + mainLayout_->SetTransmitMouseOver(true); + mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); + mainWidget_->SetInteractor(*mainWidgetInteractor_); + thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); + } + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); + + orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService())); + + if (parameters.count("studyId") < 1) + { + LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; + orthancApiClient_->ScheduleGetStudyIds(*this); + } + else + { + SelectStudy(parameters["studyId"].as<std::string>()); + } + } + + void OnStudyListReceived(const Json::Value& response) + { + if (response.isArray() && response.size() > 1) + { + SelectStudy(response[0].asString()); + } + } + void OnStudyReceived(const Json::Value& response) + { + if (response.isObject() && response["Series"].isArray()) + { + for (size_t i=0; i < response["Series"].size(); i++) + { + orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString()); + } + } + } + + void OnSeriesReceived(const Json::Value& response) + { + if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0) + { + // keep track of all instances IDs + const std::string& seriesId = response["ID"].asString(); + seriesTags_[seriesId] = response; + instancesIdsPerSeriesId_[seriesId] = std::vector<std::string>(); + for (size_t i = 0; i < response["Instances"].size(); i++) + { + const std::string& instanceId = response["Instances"][static_cast<int>(i)].asString(); + instancesIdsPerSeriesId_[seriesId].push_back(instanceId); + } + + // load the first instance in the thumbnail + LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); + + // if this is the first thumbnail loaded, load the first instance in the mainWidget + if (mainWidget_->GetLayerCount() == 0) + { + mainWidget_->AddLayer(smartLoader_->GetFrame(instancesIdsPerSeriesId_[seriesId][0], 0)); + } + } + } + + void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) + { + LOG(INFO) << "Loading thumbnail for series " << seriesId; + LayerWidget* thumbnailWidget = new LayerWidget(broker_, "thumbnail-series-" + seriesId); + thumbnails_.push_back(thumbnailWidget); + thumbnailsLayout_->AddWidget(thumbnailWidget); + thumbnailWidget->RegisterObserver(*this); + thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0)); + thumbnailWidget->SetInteractor(*thumbnailInteractor_); + } + + void SelectStudy(const std::string& studyId) + { + orthancApiClient_->ScheduleGetStudy(*this, studyId); + } + + virtual void HandleMessage(IObservable& from, const IMessage& message) { + switch (message.GetType()) { + case MessageType_Widget_GeometryChanged: + LOG(INFO) << "Widget geometry ready: " << dynamic_cast<LayerWidget&>(from).GetName(); + dynamic_cast<LayerWidget&>(from).SetDefaultView(); + break; + case MessageType_OrthancApi_GetStudyIds_Ready: + OnStudyListReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); + break; + case MessageType_OrthancApi_GetSeries_Ready: + OnSeriesReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); + break; + case MessageType_OrthancApi_GetStudy_Ready: + OnStudyReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); + break; + default: + VLOG("unhandled message type" << message.GetType()); + } + } + + void SelectSeriesInMainViewport(const std::string& seriesId) + { + mainWidget_->ReplaceLayer(0, smartLoader_->GetFrame(instancesIdsPerSeriesId_[seriesId][0], 0)); +#if ORTHANC_ENABLE_WASM==1 + NotifyStatusUpdateFromCppToWeb("series-description=" + seriesTags_[seriesId]["MainDicomTags"]["SeriesDescription"].asString()); +#endif + } + + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;} + virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2 + ) { + tool1 = "line"; + tool2 = "circle"; + pushButton1 = "action1"; + pushButton2 = "action2"; + } + +#if ORTHANC_ENABLE_WASM==1 + virtual void HandleMessageFromWeb(std::string& output, const std::string& input) { + if (input == "select-tool:line-measure") + { + currentTool_ = Tools_LineMeasure; + NotifyStatusUpdateFromCppToWeb("currentTool=line-measure"); + } + else if (input == "select-tool:circle-measure") + { + currentTool_ = Tools_CircleMeasure; + NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure"); + } + + output = "ok"; + } + + virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) { + UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str()); + } + + virtual void InitializeWasm() { + + AttachWidgetToWasmViewport("canvas", thumbnailsLayout_); + AttachWidgetToWasmViewport("canvas2", mainWidget_); + } +#endif + }; + + + } +}
--- a/Applications/Samples/SingleFrameApplication.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -214,7 +214,7 @@ { } - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -229,8 +229,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -250,7 +249,7 @@ #if 1 std::auto_ptr<OrthancFrameLayerSource> layer - (new OrthancFrameLayerSource(context.GetWebService())); + (new OrthancFrameLayerSource(context_->GetWebService())); //layer->SetImageQuality(SliceImageQuality_Jpeg50); layer->LoadFrame(instance, frame); //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); @@ -271,7 +270,7 @@ // 0178023P** // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632) std::auto_ptr<OrthancFrameLayerSource> ct; - ct.reset(new OrthancFrameLayerSource(context.GetWebService())); + ct.reset(new OrthancFrameLayerSource(context_->GetWebService())); //ct->LoadInstance("c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0); //ct->LoadInstance("4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0); // BAD SLICE //ct->SetImageQuality(SliceImageQuality_Jpeg50); @@ -281,7 +280,7 @@ widget->AddLayer(ct.release()); std::auto_ptr<OrthancFrameLayerSource> pet; - pet.reset(new OrthancFrameLayerSource(context.GetWebService())); + pet.reset(new OrthancFrameLayerSource(context_->GetWebService())); //pet->LoadInstance("a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0); pet->LoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); pet->Register(*this); @@ -309,8 +308,8 @@ widget_ = widget.get(); widget_->SetTransmitMouseOver(true); - widget_->SetInteractor(context.AddInteractor(new Interactor(*this))); - context.SetCentralWidget(widget.release()); + widget_->SetInteractor(context_->AddInteractor(new Interactor(*this))); + context_->SetCentralWidget(widget.release()); } }; }
--- a/Applications/Samples/SingleVolumeApplication.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Samples/SingleVolumeApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -89,7 +89,7 @@ public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -108,8 +108,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -147,8 +146,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - unsigned int threads = parameters["threads"].as<unsigned int>(); - bool reverse = parameters["reverse"].as<bool>(); + //unsigned int threads = parameters["threads"].as<unsigned int>(); + //bool reverse = parameters["reverse"].as<bool>(); std::string tmp = parameters["projection"].as<std::string>(); Orthanc::Toolbox::ToLowerCase(tmp); @@ -175,7 +174,7 @@ std::auto_ptr<LayerWidget> widget(new LayerWidget); #if 0 - std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context.GetWebService(), true)); + std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context_->GetWebService(), true)); if (series.empty()) { volume->ScheduleLoadInstance(instance); @@ -187,8 +186,8 @@ widget->AddLayer(new VolumeImageSource(*volume)); - context.AddInteractor(new Interactor(*volume, *widget, projection, 0)); - context.AddSlicedVolume(volume.release()); + context_->AddInteractor(new Interactor(*volume, *widget, projection, 0)); + context_->AddSlicedVolume(volume.release()); { RenderStyle s; @@ -199,14 +198,14 @@ widget->SetLayerStyle(0, s); } #else - std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context.GetWebService(), false)); + std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context_->GetWebService(), false)); //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8"); // 0178023P //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953"); // Captain - std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context.GetWebService(), true)); + std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context_->GetWebService(), true)); //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53"); // 0178023P //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 @@ -216,7 +215,7 @@ pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6"); // Captain 1 //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1"); // Captain 2 - std::auto_ptr<StructureSetLoader> rtStruct(new StructureSetLoader(context.GetWebService())); + std::auto_ptr<StructureSetLoader> rtStruct(new StructureSetLoader(context_->GetWebService())); //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3"); // 0178023P //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA @@ -226,12 +225,12 @@ widget->AddLayer(new VolumeImageSource(*pet)); widget->AddLayer(new DicomStructureSetRendererFactory(*rtStruct)); - context.AddInteractor(new Interactor(*pet, *widget, projection, 1)); - //context.AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); + context_->AddInteractor(new Interactor(*pet, *widget, projection, 1)); + //context_->AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); - context.AddSlicedVolume(ct.release()); - context.AddSlicedVolume(pet.release()); - context.AddVolumeLoader(rtStruct.release()); + context_->AddSlicedVolume(ct.release()); + context_->AddSlicedVolume(pet.release()); + context_->AddVolumeLoader(rtStruct.release()); { RenderStyle s; @@ -263,7 +262,7 @@ statusBar.SetMessage("Use the keys \"c\" to draw circles"); widget->SetTransmitMouseOver(true); - context.SetCentralWidget(widget.release()); + context_->SetCentralWidget(widget.release()); } }; }
--- a/Applications/Samples/TestPatternApplication.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Samples/TestPatternApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -34,7 +34,7 @@ class TestPatternApplication : public SampleApplicationBase { public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -44,8 +44,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -56,8 +55,8 @@ layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>())); layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as<bool>())); - context.SetCentralWidget(layout.release()); - context.SetUpdateDelay(25); // If animation, update the content each 25ms + context_->SetCentralWidget(layout.release()); + context_->SetUpdateDelay(25); // If animation, update the content each 25ms } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/index.html Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,21 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + <title>Wasm Samples</title> + <link href="samples-styles.css" rel="stylesheet" /> + +<body> + <ul> + <li><a href="simple-viewer.html">Simple Viewer</a></li> + </ul> +</body> + +</html> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/simple-viewer.html Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,34 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + <title>Simple Viewer</title> + <link href="samples-styles.css" rel="stylesheet" /> + +<body> + <div id="breadcrumb"> + <span id="patient-id"></span> + <span id="study-description"></span> + <span id="series-description"></span> + </div> + <div> + <canvas id="canvas" data-width-ratio="20" data-height-ratio="50"></canvas> + <canvas id="canvas2" data-width-ratio="70" data-height-ratio="50"></canvas> + </div> + <div id="toolbox"> + <input tool-selector="line-measure" type="radio" name="radio-tool-selector" class="tool-selector">line + <input tool-selector="circle-measure" type="radio" name="radio-tool-selector" class="tool-selector">circle + <button action-trigger="action1" class="action-trigger">action1</button> + <button action-trigger="action2" class="action-trigger">action2</button> + </div> + <script type="text/javascript" src="app-simple-viewer.js"></script> +</body> + +</html> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/simple-viewer.ts Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,39 @@ +///<reference path='../../../Platforms/Wasm/wasm-application-runner.ts'/> + +InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc"); + +function SelectTool(toolName: string) { + SendMessageToStoneApplication("select-tool:" + toolName); +} + +function PerformAction(actionName: string) { + SendMessageToStoneApplication("perform-action:" + actionName); +} + +//initializes the buttons +//----------------------- +// install "SelectTool" handlers +document.querySelectorAll("[tool-selector]").forEach((e) => { + console.log(e); + (e as HTMLInputElement).addEventListener("click", () => { + console.log(e); + SelectTool(e.attributes["tool-selector"].value); + }); +}); + +// install "PerformAction" handlers +document.querySelectorAll("[action-trigger]").forEach((e) => { + (e as HTMLInputElement).addEventListener("click", () => { + PerformAction(e.attributes["action-trigger"].value); + }); +}); + +// this method is called "from the C++ code" when the StoneApplication is updated. +// it can be used to update the UI of the application +function UpdateWebApplication(statusUpdateMessage: string) { + console.log(statusUpdateMessage); + + if (statusUpdateMessage.startsWith("series-description=")) { + document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1]; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/tsconfig-samples.json Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,11 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom", + "dom.iterable" + ] + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/tsconfig-simple-viewer.json Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,10 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + "outFile": "../../../Platforms/Wasm/build-web/app-simple-viewer.js" + }, + "include" : [ + "simple-viewer.ts", + "common-samples.ts" + ] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/samples-library.js Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,8 @@ +// this file contains the JS method you want to expose to C++ code + +// mergeInto(LibraryManager.library, { +// ScheduleRedraw: function() { +// ScheduleRedraw(); +// } +// }); + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplication.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,127 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if ORTHANC_ENABLE_SDL != 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1 +#endif + +#include "BasicSdlApplication.h" +#include <boost/program_options.hpp> + +#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "SdlEngine.h" + +#include <Core/Logging.h> +#include <Core/HttpClient.h> +#include <Core/Toolbox.h> +#include <Plugins/Samples/Common/OrthancHttpConnection.h> +#include "../../Platforms/Generic/OracleWebService.h" + +namespace OrthancStone +{ + void BasicSdlApplication::Initialize() + { + SdlWindow::GlobalInitialize(); + } + + void BasicSdlApplication::DeclareCommandLineOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description sdl("SDL options"); + sdl.add_options() + ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window") + ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window") + ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL") + ; + + options.add(sdl); + } + + void BasicSdlApplication::ParseCommandLineOptions(const boost::program_options::variables_map& parameters) + { + if (!parameters.count("width") || + !parameters.count("height") || + !parameters.count("opengl")) + { + LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + int w = parameters["width"].as<int>(); + int h = parameters["height"].as<int>(); + if (w <= 0 || h <= 0) + { + LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + width_ = static_cast<unsigned int>(w); + height_ = static_cast<unsigned int>(h); + LOG(WARNING) << "Initial display size: " << width_ << "x" << height_; + + enableOpenGl_ = parameters["opengl"].as<bool>(); + if (enableOpenGl_) + { + LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes"; + } + else + { + LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance"; + } + + } + + void BasicSdlApplication::Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]) + { + /************************************************************** + * Run the application inside a SDL window + **************************************************************/ + + LOG(WARNING) << "Starting the application"; + + SdlWindow window(title.c_str(), width_, height_, enableOpenGl_); + SdlEngine sdl(window, context); + + { + BasicNativeApplicationContext::GlobalMutexLocker locker(context); + context.GetCentralViewport().Register(sdl); // (*) + } + + context.Start(); + sdl.Run(); + + LOG(WARNING) << "Stopping the application"; + + // Don't move the "Stop()" command below out of the block, + // otherwise the application might crash, because the + // "SdlEngine" is an observer of the viewport (*) and the + // update thread started by "context.Start()" would call a + // destructed object (the "SdlEngine" is deleted with the + // lexical scope). + context.Stop(); + } + + void BasicSdlApplication::Finalize() + { + SdlWindow::GlobalFinalize(); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplication.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Generic/BasicNativeApplication.h" + +#if ORTHANC_ENABLE_SDL != 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1 +#endif + +#include <SDL.h> // Necessary to avoid undefined reference to `SDL_main' + +namespace OrthancStone +{ + class BasicSdlApplication : public BasicNativeApplication + { + unsigned int width_; + unsigned int height_; + bool enableOpenGl_; + public: + virtual void Initialize(); + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options); + virtual void Run(BasicNativeApplicationContext& context, const std::string& title, int argc, char* argv[]); + virtual void ParseCommandLineOptions(const boost::program_options::variables_map& parameters); + virtual void Finalize(); + }; + +}
--- a/Applications/Sdl/SdlEngine.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Sdl/SdlEngine.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -29,11 +29,10 @@ namespace OrthancStone { - void SdlEngine::SetSize(BasicApplicationContext::ViewportLocker& locker, - unsigned int width, + void SdlEngine::SetSize(unsigned int width, unsigned int height) { - locker.GetViewport().SetSize(width, height); + context_.GetCentralViewport().SetSize(width, height); surface_.SetSize(width, height); } @@ -42,8 +41,8 @@ { if (viewportChanged_) { - BasicApplicationContext::ViewportLocker locker(context_); - surface_.Render(locker.GetViewport()); + BasicNativeApplicationContext::GlobalMutexLocker locker(context_); + surface_.Render(context_.GetCentralViewport()); viewportChanged_ = false; } @@ -99,7 +98,7 @@ SdlEngine::SdlEngine(SdlWindow& window, - BasicApplicationContext& context) : + BasicNativeApplicationContext& context) : window_(window), context_(context), surface_(window), @@ -119,9 +118,9 @@ const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); { - BasicApplicationContext::ViewportLocker locker(context_); - SetSize(locker, window_.GetWidth(), window_.GetHeight()); - locker.GetViewport().SetDefaultView(); + BasicNativeApplicationContext::GlobalMutexLocker locker(context_); + SetSize(window_.GetWidth(), window_.GetHeight()); + context_.GetCentralViewport().SetDefaultView(); } bool stop = false; @@ -134,7 +133,7 @@ while (!stop && SDL_PollEvent(&event)) { - BasicApplicationContext::ViewportLocker locker(context_); + BasicNativeApplicationContext::GlobalMutexLocker locker(context_); if (event.type == SDL_QUIT) { @@ -148,15 +147,15 @@ switch (event.button.button) { case SDL_BUTTON_LEFT: - locker.GetViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers); + context_.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers); break; case SDL_BUTTON_RIGHT: - locker.GetViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers); + context_.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers); break; case SDL_BUTTON_MIDDLE: - locker.GetViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers); + context_.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers); break; default: @@ -165,26 +164,26 @@ } else if (event.type == SDL_MOUSEMOTION) { - locker.GetViewport().MouseMove(event.button.x, event.button.y); + context_.GetCentralViewport().MouseMove(event.button.x, event.button.y); } else if (event.type == SDL_MOUSEBUTTONUP) { - locker.GetViewport().MouseUp(); + context_.GetCentralViewport().MouseUp(); } else if (event.type == SDL_WINDOWEVENT) { switch (event.window.event) { case SDL_WINDOWEVENT_LEAVE: - locker.GetViewport().MouseLeave(); + context_.GetCentralViewport().MouseLeave(); break; case SDL_WINDOWEVENT_ENTER: - locker.GetViewport().MouseEnter(); + context_.GetCentralViewport().MouseEnter(); break; case SDL_WINDOWEVENT_SIZE_CHANGED: - SetSize(locker, event.window.data1, event.window.data2); + SetSize(event.window.data1, event.window.data2); break; default: @@ -200,11 +199,11 @@ if (event.wheel.y > 0) { - locker.GetViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + context_.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); } else if (event.wheel.y < 0) { - locker.GetViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + context_.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); } } else if (event.type == SDL_KEYDOWN && @@ -214,50 +213,50 @@ switch (event.key.keysym.sym) { - case SDLK_a: locker.GetViewport().KeyPressed('a', modifiers); break; - case SDLK_b: locker.GetViewport().KeyPressed('b', modifiers); break; - case SDLK_c: locker.GetViewport().KeyPressed('c', modifiers); break; - case SDLK_d: locker.GetViewport().KeyPressed('d', modifiers); break; - case SDLK_e: locker.GetViewport().KeyPressed('e', modifiers); break; + case SDLK_a: context_.GetCentralViewport().KeyPressed('a', modifiers); break; + case SDLK_b: context_.GetCentralViewport().KeyPressed('b', modifiers); break; + case SDLK_c: context_.GetCentralViewport().KeyPressed('c', modifiers); break; + case SDLK_d: context_.GetCentralViewport().KeyPressed('d', modifiers); break; + case SDLK_e: context_.GetCentralViewport().KeyPressed('e', modifiers); break; case SDLK_f: window_.ToggleMaximize(); break; - case SDLK_g: locker.GetViewport().KeyPressed('g', modifiers); break; - case SDLK_h: locker.GetViewport().KeyPressed('h', modifiers); break; - case SDLK_i: locker.GetViewport().KeyPressed('i', modifiers); break; - case SDLK_j: locker.GetViewport().KeyPressed('j', modifiers); break; - case SDLK_k: locker.GetViewport().KeyPressed('k', modifiers); break; - case SDLK_l: locker.GetViewport().KeyPressed('l', modifiers); break; - case SDLK_m: locker.GetViewport().KeyPressed('m', modifiers); break; - case SDLK_n: locker.GetViewport().KeyPressed('n', modifiers); break; - case SDLK_o: locker.GetViewport().KeyPressed('o', modifiers); break; - case SDLK_p: locker.GetViewport().KeyPressed('p', modifiers); break; + case SDLK_g: context_.GetCentralViewport().KeyPressed('g', modifiers); break; + case SDLK_h: context_.GetCentralViewport().KeyPressed('h', modifiers); break; + case SDLK_i: context_.GetCentralViewport().KeyPressed('i', modifiers); break; + case SDLK_j: context_.GetCentralViewport().KeyPressed('j', modifiers); break; + case SDLK_k: context_.GetCentralViewport().KeyPressed('k', modifiers); break; + case SDLK_l: context_.GetCentralViewport().KeyPressed('l', modifiers); break; + case SDLK_m: context_.GetCentralViewport().KeyPressed('m', modifiers); break; + case SDLK_n: context_.GetCentralViewport().KeyPressed('n', modifiers); break; + case SDLK_o: context_.GetCentralViewport().KeyPressed('o', modifiers); break; + case SDLK_p: context_.GetCentralViewport().KeyPressed('p', modifiers); break; case SDLK_q: stop = true; break; - case SDLK_r: locker.GetViewport().KeyPressed('r', modifiers); break; - case SDLK_s: locker.GetViewport().KeyPressed('s', modifiers); break; - case SDLK_t: locker.GetViewport().KeyPressed('t', modifiers); break; - case SDLK_u: locker.GetViewport().KeyPressed('u', modifiers); break; - case SDLK_v: locker.GetViewport().KeyPressed('v', modifiers); break; - case SDLK_w: locker.GetViewport().KeyPressed('w', modifiers); break; - case SDLK_x: locker.GetViewport().KeyPressed('x', modifiers); break; - case SDLK_y: locker.GetViewport().KeyPressed('y', modifiers); break; - case SDLK_z: locker.GetViewport().KeyPressed('z', modifiers); break; - case SDLK_KP_0: locker.GetViewport().KeyPressed('0', modifiers); break; - case SDLK_KP_1: locker.GetViewport().KeyPressed('1', modifiers); break; - case SDLK_KP_2: locker.GetViewport().KeyPressed('2', modifiers); break; - case SDLK_KP_3: locker.GetViewport().KeyPressed('3', modifiers); break; - case SDLK_KP_4: locker.GetViewport().KeyPressed('4', modifiers); break; - case SDLK_KP_5: locker.GetViewport().KeyPressed('5', modifiers); break; - case SDLK_KP_6: locker.GetViewport().KeyPressed('6', modifiers); break; - case SDLK_KP_7: locker.GetViewport().KeyPressed('7', modifiers); break; - case SDLK_KP_8: locker.GetViewport().KeyPressed('8', modifiers); break; - case SDLK_KP_9: locker.GetViewport().KeyPressed('9', modifiers); break; + case SDLK_r: context_.GetCentralViewport().KeyPressed('r', modifiers); break; + case SDLK_s: context_.GetCentralViewport().KeyPressed('s', modifiers); break; + case SDLK_t: context_.GetCentralViewport().KeyPressed('t', modifiers); break; + case SDLK_u: context_.GetCentralViewport().KeyPressed('u', modifiers); break; + case SDLK_v: context_.GetCentralViewport().KeyPressed('v', modifiers); break; + case SDLK_w: context_.GetCentralViewport().KeyPressed('w', modifiers); break; + case SDLK_x: context_.GetCentralViewport().KeyPressed('x', modifiers); break; + case SDLK_y: context_.GetCentralViewport().KeyPressed('y', modifiers); break; + case SDLK_z: context_.GetCentralViewport().KeyPressed('z', modifiers); break; + case SDLK_KP_0: context_.GetCentralViewport().KeyPressed('0', modifiers); break; + case SDLK_KP_1: context_.GetCentralViewport().KeyPressed('1', modifiers); break; + case SDLK_KP_2: context_.GetCentralViewport().KeyPressed('2', modifiers); break; + case SDLK_KP_3: context_.GetCentralViewport().KeyPressed('3', modifiers); break; + case SDLK_KP_4: context_.GetCentralViewport().KeyPressed('4', modifiers); break; + case SDLK_KP_5: context_.GetCentralViewport().KeyPressed('5', modifiers); break; + case SDLK_KP_6: context_.GetCentralViewport().KeyPressed('6', modifiers); break; + case SDLK_KP_7: context_.GetCentralViewport().KeyPressed('7', modifiers); break; + case SDLK_KP_8: context_.GetCentralViewport().KeyPressed('8', modifiers); break; + case SDLK_KP_9: context_.GetCentralViewport().KeyPressed('9', modifiers); break; case SDLK_PLUS: case SDLK_KP_PLUS: - locker.GetViewport().KeyPressed('+', modifiers); break; + context_.GetCentralViewport().KeyPressed('+', modifiers); break; case SDLK_MINUS: case SDLK_KP_MINUS: - locker.GetViewport().KeyPressed('-', modifiers); break; + context_.GetCentralViewport().KeyPressed('-', modifiers); break; default: break;
--- a/Applications/Sdl/SdlEngine.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Applications/Sdl/SdlEngine.h Thu Aug 30 16:56:08 2018 +0200 @@ -24,7 +24,7 @@ #if ORTHANC_ENABLE_SDL == 1 #include "SdlCairoSurface.h" -#include "../BasicApplicationContext.h" +#include "../Generic/BasicNativeApplicationContext.h" namespace OrthancStone { @@ -32,12 +32,11 @@ { private: SdlWindow& window_; - BasicApplicationContext& context_; + BasicNativeApplicationContext& context_; SdlCairoSurface surface_; bool viewportChanged_; - void SetSize(BasicApplicationContext::ViewportLocker& locker, - unsigned int width, + void SetSize(unsigned int width, unsigned int height); void RenderFrame(); @@ -47,11 +46,11 @@ public: SdlEngine(SdlWindow& window, - BasicApplicationContext& context); + BasicNativeApplicationContext& context); virtual ~SdlEngine(); - virtual void NotifyChange(const IViewport& viewport) + virtual void OnViewportContentChanged(const IViewport& viewport) { viewportChanged_ = true; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneApplicationContext.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,26 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "StoneApplicationContext.h" + +namespace OrthancStone +{ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneApplicationContext.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Framework/Toolbox/IWebService.h" +#include "../Framework/Viewport/WidgetViewport.h" + +#include <list> + +namespace OrthancStone +{ + // a StoneApplicationContext contains the services that a StoneApplication + // uses and that depends on the environment in which the Application executes. + // I.e, the StoneApplicationContext provides a WebService interface such that + // the StoneApplication can perform HTTP requests. In a WASM environment, + // the WebService is provided by the browser while, in a native environment, + // the WebService is provided by the OracleWebService (a C++ Http client) + class StoneApplicationContext : public boost::noncopyable + { + + protected: + IWebService* webService_; + public: + StoneApplicationContext() + : webService_(NULL) + { + } + + IWebService& GetWebService() {return *webService_;} + void SetWebService(IWebService& webService) + { + webService_ = &webService; + } + + virtual ~StoneApplicationContext() {} + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Wasm/StartupParametersBuilder.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,43 @@ +#include "StartupParametersBuilder.h" + +namespace OrthancStone +{ + void StartupParametersBuilder::Clear() { + startupParameters_.clear(); + } + + void StartupParametersBuilder::SetStartupParameter(const char* name, const char* value) { + startupParameters_.push_back(std::make_tuple(name, value)); + } + + void StartupParametersBuilder::GetStartupParameters(boost::program_options::variables_map& parameters, const boost::program_options::options_description& options) { + + const char* argv[startupParameters_.size() + 1]; + int argCounter = 0; + argv[0] = "Toto.exe"; + argCounter++; + + std::string cmdLine = ""; + for (StartupParameters::const_iterator it = startupParameters_.begin(); it != startupParameters_.end(); it++) { + char* arg = new char[128]; + snprintf(arg, 128, "--%s=%s", std::get<0>(*it).c_str(), std::get<1>(*it).c_str()); + argv[argCounter] = arg; + cmdLine = cmdLine + " --" + std::get<0>(*it) + "=" + std::get<1>(*it); + argCounter++; + } + + printf("simulated cmdLine = %s\n", cmdLine.c_str()); + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argCounter, argv). + options(options).run(), parameters); + boost::program_options::notify(parameters); + } + catch (boost::program_options::error& e) + { + printf("Error while parsing the command-line arguments: %s\n", e.what()); + } + + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Wasm/StartupParametersBuilder.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/program_options.hpp> +#include <tuple> + +#if ORTHANC_ENABLE_SDL == 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 0 +#endif + +namespace OrthancStone +{ + // This class is used to generate boost program options from a dico. + // In a Wasm context, startup options are passed as URI arguments that + // are then passed to this class as a dico. + // This class regenerates a fake command-line and parses it to produce + // the same output as if the app was started at command-line. + class StartupParametersBuilder + { + typedef std::list<std::tuple<std::string, std::string>> StartupParameters; + StartupParameters startupParameters_; + + public: + + void Clear(); + void SetStartupParameter(const char* name, const char* value); + void GetStartupParameters(boost::program_options::variables_map& parameters_, const boost::program_options::options_description& options); + }; + +}
--- a/Framework/Layers/CircleMeasureTracker.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/CircleMeasureTracker.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -76,8 +76,10 @@ if (fontSize_ != 0) { cairo_move_to(cr, x, y); +#if ORTHANC_ENABLE_NATIVE==1 // text rendering currently fails in wasm CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); font.Draw(context, FormatRadius(), static_cast<double>(fontSize_) / zoom); +#endif } }
--- a/Framework/Layers/DicomStructureSetRendererFactory.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/DicomStructureSetRendererFactory.h Thu Aug 30 16:56:08 2018 +0200 @@ -37,21 +37,22 @@ { LayerSourceBase::NotifyGeometryReady(); } - + virtual void NotifyGeometryError(const IVolumeLoader& loader) { LayerSourceBase::NotifyGeometryError(); } - + virtual void NotifyContentChange(const IVolumeLoader& loader) { LayerSourceBase::NotifyContentChange(); } - + StructureSetLoader& loader_; public: - DicomStructureSetRendererFactory(StructureSetLoader& loader) : + DicomStructureSetRendererFactory(MessageBroker& broker, StructureSetLoader& loader) : + LayerSourceBase(broker), loader_(loader) { loader_.Register(*this);
--- a/Framework/Layers/ILayerSource.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/ILayerSource.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -23,47 +23,80 @@ #include "ILayerRenderer.h" #include "../Toolbox/Slice.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/IMessage.h" namespace OrthancStone { - class ILayerSource : public boost::noncopyable + class ILayerSource : public IObservable { public: - class IObserver : public boost::noncopyable + struct SliceChangedMessage : public IMessage + { + const Slice& slice_; + SliceChangedMessage(const Slice& slice) + : IMessage(MessageType_LayerSource_SliceChanged), + slice_(slice) + { + } + }; + + struct LayerReadyMessage : public IMessage { - public: - virtual ~IObserver() + std::auto_ptr<ILayerRenderer>& layer_; + const CoordinateSystem3D& slice_; + bool isError_; + + LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer, + const CoordinateSystem3D& slice, + bool isError) // TODO Shouldn't this be separate as NotifyLayerError? + : IMessage(MessageType_LayerSource_LayerReady), + layer_(layer), + slice_(slice), + isError_(isError) { } + }; - // Triggered as soon as the source has enough information to - // answer to "GetExtent()" - virtual void NotifyGeometryReady(const ILayerSource& source) = 0; - - virtual void NotifyGeometryError(const ILayerSource& source) = 0; - - // Triggered if the content of several slices in the source - // volume has changed - virtual void NotifyContentChange(const ILayerSource& source) = 0; + // class IObserver : public boost::noncopyable + // { + // public: + // virtual ~IObserver() + // { + // } + + // // Triggered as soon as the source has enough information to + // // answer to "GetExtent()" + // virtual void NotifyGeometryReady(const ILayerSource& source) = 0; + + // virtual void NotifyGeometryError(const ILayerSource& source) = 0; - // Triggered if the content of some individual slice in the - // source volume has changed - virtual void NotifySliceChange(const ILayerSource& source, - const Slice& slice) = 0; - - // The layer must be deleted by the observer that releases the - // std::auto_ptr - virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError) = 0; // TODO Shouldn't this be separate as NotifyLayerError? - }; + // // Triggered if the content of several slices in the source + // // volume has changed + // virtual void NotifyContentChange(const ILayerSource& source) = 0; + + // // Triggered if the content of some individual slice in the + // // source volume has changed + // virtual void NotifySliceChange(const ILayerSource& source, + // const Slice& slice) = 0; + + // // The layer must be deleted by the observer that releases the + // // std::auto_ptr + // virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer, + // const ILayerSource& source, + // const CoordinateSystem3D& slice, + // bool isError) = 0; // TODO Shouldn't this be separate as NotifyLayerError? + // }; + ILayerSource(MessageBroker& broker) + : IObservable(broker) + {} + virtual ~ILayerSource() { } - virtual void Register(IObserver& observer) = 0; + // virtual void Register(IObserver& observer) = 0; virtual bool GetExtent(std::vector<Vector>& points, const CoordinateSystem3D& viewportSlice) = 0;
--- a/Framework/Layers/LayerSourceBase.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -25,63 +25,32 @@ namespace OrthancStone { - namespace - { - class LayerReadyFunctor : public boost::noncopyable - { - private: - std::auto_ptr<ILayerRenderer> layer_; - const CoordinateSystem3D& slice_; - bool isError_; - - public: - LayerReadyFunctor(ILayerRenderer* layer, - const CoordinateSystem3D& slice, - bool isError) : - layer_(layer), - slice_(slice), - isError_(isError) - { - } - - void operator() (ILayerSource::IObserver& observer, - const ILayerSource& source) - { - observer.NotifyLayerReady(layer_, source, slice_, isError_); - } - }; - } - void LayerSourceBase::NotifyGeometryReady() { - observers_.Apply(*this, &IObserver::NotifyGeometryReady); + EmitMessage(IMessage(MessageType_LayerSource_GeometryReady)); } void LayerSourceBase::NotifyGeometryError() { - observers_.Apply(*this, &IObserver::NotifyGeometryError); - } + EmitMessage(IMessage(MessageType_LayerSource_GeometryError)); + } void LayerSourceBase::NotifyContentChange() { - observers_.Apply(*this, &IObserver::NotifyContentChange); + EmitMessage(IMessage(MessageType_LayerSource_ContentChanged)); } void LayerSourceBase::NotifySliceChange(const Slice& slice) { - observers_.Apply(*this, &IObserver::NotifySliceChange, slice); + EmitMessage(ILayerSource::SliceChangedMessage(slice)); } void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer, const CoordinateSystem3D& slice, bool isError) { - LayerReadyFunctor functor(layer, slice, isError); - observers_.Notify(*this, functor); + std::auto_ptr<ILayerRenderer> renderer(layer); + EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError)); } - void LayerSourceBase::Register(IObserver& observer) - { - observers_.Register(observer); - } }
--- a/Framework/Layers/LayerSourceBase.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -28,11 +28,6 @@ { class LayerSourceBase : public ILayerSource { - private: - typedef ObserversRegistry<ILayerSource, IObserver> Observers; - - Observers observers_; - protected: void NotifyGeometryReady(); @@ -46,7 +41,15 @@ const CoordinateSystem3D& slice, bool isError); - public: - virtual void Register(IObserver& observer); + LayerSourceBase(MessageBroker& broker) + : ILayerSource(broker) + { + DeclareEmittableMessage(MessageType_LayerSource_GeometryReady); + DeclareEmittableMessage(MessageType_LayerSource_GeometryError); + DeclareEmittableMessage(MessageType_LayerSource_ContentChanged); + DeclareEmittableMessage(MessageType_LayerSource_SliceChanged); + DeclareEmittableMessage(MessageType_LayerSource_LayerReady); + } + }; }
--- a/Framework/Layers/LineMeasureTracker.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/LineMeasureTracker.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -63,8 +63,10 @@ if (fontSize_ != 0) { cairo_move_to(cr, x2_, y2_ - static_cast<double>(fontSize_) / zoom); +#if ORTHANC_ENABLE_NATIVE==1 // text rendering currently fails in wasm CairoFont font("sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); font.Draw(context, FormatLength(), static_cast<double>(fontSize_) / zoom); +#endif } }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -31,47 +31,59 @@ namespace OrthancStone { - void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader) + void OrthancFrameLayerSource::HandleMessage(IObservable& from, const IMessage& message) { - if (loader.GetSliceCount() > 0) + switch (message.GetType()) + { + case MessageType_SliceLoader_GeometryReady: + { + const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from); + if (loader.GetSliceCount() > 0) + { + LayerSourceBase::NotifyGeometryReady(); + } + else + { + LayerSourceBase::NotifyGeometryError(); + } + + }; break; + case MessageType_SliceLoader_GeometryError: { - LayerSourceBase::NotifyGeometryReady(); - } - else + const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from); + LayerSourceBase::NotifyGeometryError(); + }; break; + case MessageType_SliceLoader_ImageReady: { - LayerSourceBase::NotifyGeometryError(); + const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message); + bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam); + LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(msg.image_.release(), msg.slice_, isFull), + msg.slice_.GetGeometry(), false); + + }; break; + case MessageType_SliceLoader_ImageError: + { + const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message); + LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true); + }; break; + default: + VLOG("unhandled message type" << message.GetType()); } } - void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader) - { - LayerSourceBase::NotifyGeometryError(); - } - void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - std::auto_ptr<Orthanc::ImageAccessor>& image, - SliceImageQuality quality) + OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) : + LayerSourceBase(broker), + IObserver(broker), + //OrthancSlicesLoader::ISliceLoaderObserver(broker), + loader_(broker, orthanc), + quality_(SliceImageQuality_FullPng) { - bool isFull = (quality == SliceImageQuality_Full); - LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull), - slice.GetGeometry(), false); - } - - void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - SliceImageQuality quality) - { - LayerSourceBase::NotifyLayerReady(NULL, slice.GetGeometry(), true); - } - - - OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) : - loader_(*this, orthanc), - quality_(SliceImageQuality_Full) - { + DeclareHandledMessage(MessageType_SliceLoader_GeometryReady); + DeclareHandledMessage(MessageType_SliceLoader_GeometryError); + DeclareHandledMessage(MessageType_SliceLoader_ImageReady); + DeclareHandledMessage(MessageType_SliceLoader_ImageError); + loader_.RegisterObserver(*this); }
--- a/Framework/Layers/OrthancFrameLayerSource.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.h Thu Aug 30 16:56:08 2018 +0200 @@ -27,31 +27,20 @@ namespace OrthancStone { + // this class is in charge of loading a Frame. + // once it's been loaded (first the geometry and then the image), + // messages are sent to observers so they can use it class OrthancFrameLayerSource : public LayerSourceBase, - private OrthancSlicesLoader::ICallback + public IObserver + //private OrthancSlicesLoader::ISliceLoaderObserver { private: OrthancSlicesLoader loader_; SliceImageQuality quality_; - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader); - - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader); - - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - std::auto_ptr<Orthanc::ImageAccessor>& image, - SliceImageQuality quality); - - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - SliceImageQuality quality); - public: - OrthancFrameLayerSource(IWebService& orthanc); + OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc); void LoadSeries(const std::string& seriesId); @@ -79,5 +68,7 @@ const CoordinateSystem3D& viewportSlice); virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice); + + virtual void HandleMessage(IObservable& from, const IMessage& message); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IMessage.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,42 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "MessageType.h" + +#include <boost/noncopyable.hpp> + +namespace OrthancStone { + + struct IMessage : public boost::noncopyable + { + MessageType messageType_; + public: + IMessage(const MessageType& messageType) + : messageType_(messageType) + {} + virtual ~IMessage() {} + + MessageType GetType() const {return messageType_;} + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IObservable.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,111 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <set> +#include <assert.h> +#include <algorithm> +#include <iostream> + +#include "MessageBroker.h" +#include "MessageType.h" +#include "IObserver.h" + +namespace OrthancStone { + + class MessageNotDeclaredException : public std::logic_error + { + MessageType messageType_; + public: + MessageNotDeclaredException(MessageType messageType) + : std::logic_error("Message not declared by observer."), + messageType_(messageType) + { + } + }; + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set<IObserver*> observers_; + std::set<MessageType> emittableMessages_; + + public: + + IObservable(MessageBroker& broker) + : broker_(broker) + { + } + virtual ~IObservable() + { + } + + void EmitMessage(const IMessage& message) + { + if (emittableMessages_.find(message.GetType()) == emittableMessages_.end()) + { + throw MessageNotDeclaredException(message.GetType()); + } + + broker_.EmitMessage(*this, observers_, message); + } + + void RegisterObserver(IObserver& observer) + { + CheckObserverDeclaredAllObservableMessages(observer); + observers_.insert(&observer); + } + + void UnregisterObserver(IObserver& observer) + { + observers_.erase(&observer); + } + + const std::set<MessageType>& GetEmittableMessages() const + { + return emittableMessages_; + } + + protected: + + void DeclareEmittableMessage(MessageType messageType) + { + emittableMessages_.insert(messageType); + } + + void CheckObserverDeclaredAllObservableMessages(IObserver& observer) + { + for (std::set<MessageType>::const_iterator it = emittableMessages_.begin(); it != emittableMessages_.end(); it++) + { + // the observer must have "declared" all observable messages + if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end() + && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end()) + { + throw MessageNotDeclaredException(*it); + } + } + } + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IObserver.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,88 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "MessageBroker.h" +#include "IMessage.h" +#include <set> +#include <assert.h> + +namespace OrthancStone { + + class IObservable; + + class IObserver : public boost::noncopyable + { + protected: + MessageBroker& broker_; + std::set<MessageType> handledMessages_; + std::set<MessageType> ignoredMessages_; + + public: + IObserver(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IObserver() + { + broker_.Unregister(*this); + } + + void HandleMessage_(IObservable &from, const IMessage &message) + { + assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling + + HandleMessage(from, message); + } + + virtual void HandleMessage(IObservable& from, const IMessage& message) = 0; + + + const std::set<MessageType>& GetHandledMessages() const + { + return handledMessages_; + } + + const std::set<MessageType>& GetIgnoredMessages() const + { + return ignoredMessages_; + } + + protected: + + // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration) + // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer + void DeclareHandledMessage(MessageType messageType) + { + handledMessages_.insert(messageType); + } + + void DeclareIgnoredMessage(MessageType messageType) + { + ignoredMessages_.insert(messageType); + } + + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageBroker.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "MessageBroker.h" + +#include <algorithm> +#include <assert.h> +#include <vector> + +#include "IObserver.h" +#include "MessageType.h" + +namespace OrthancStone { + + void MessageBroker::EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message) + { + std::vector<IObserver*> activeObservers; + std::set_intersection(observers.begin(), + observers.end(), + activeObservers_.begin(), + activeObservers_.end(), + std::back_inserter(activeObservers) + ); + + for (std::vector<IObserver*>::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++) + { + if ((*observer)->GetHandledMessages().find(message.GetType()) != (*observer)->GetHandledMessages().end()) + { + (*observer)->HandleMessage_(from, message); + } + else + { + assert((*observer)->GetIgnoredMessages().find(message.GetType()) != (*observer)->GetIgnoredMessages().end()); // message has not been declared by Observer (this should already have been checked during registration) + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageBroker.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../StoneEnumerations.h" + +#include "boost/noncopyable.hpp" +#include <map> +#include <list> +#include <set> + +namespace OrthancStone +{ + class IObserver; + class IObservable; + class IMessage; + + /* + * This is a central message broker. It keeps track of all observers and knows + * when an observer is deleted. + * This way, it can prevent an observable to send a message to a dead observer. + */ + class MessageBroker : public boost::noncopyable + { + + std::set<IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) + + public: + + void Register(IObserver& observer) + { + activeObservers_.insert(&observer); + } + + void Unregister(IObserver& observer) + { + activeObservers_.erase(&observer); + } + + void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message); + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageType.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,55 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +namespace OrthancStone { + + enum MessageType + { + MessageType_Widget_GeometryChanged, + MessageType_Widget_ContentChanged, + + MessageType_LayerSource_GeometryReady, + MessageType_LayerSource_GeometryError, + MessageType_LayerSource_ContentChanged, + MessageType_LayerSource_SliceChanged, + MessageType_LayerSource_LayerReady, + + MessageType_SliceLoader_GeometryReady, + MessageType_SliceLoader_GeometryError, + MessageType_SliceLoader_ImageReady, + MessageType_SliceLoader_ImageError, + + MessageType_HttpRequestSuccess, + MessageType_HttpRequestError, + + MessageType_OrthancApi_InternalGetJsonResponseReady, + MessageType_OrthancApi_InternalGetJsonResponseError, + + MessageType_OrthancApi_GetStudyIds_Ready, + MessageType_OrthancApi_GetStudy_Ready, + MessageType_OrthancApi_GetSeries_Ready, + + // used in unit tests only + MessageType_Test1, + MessageType_Test2 + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/SmartLoader.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,103 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SmartLoader.h" +#include "Layers/OrthancFrameLayerSource.h" + +namespace OrthancStone +{ + SmartLoader::SmartLoader(MessageBroker& broker, IWebService& webService) : + IObservable(broker), + IObserver(broker), + imageQuality_(SliceImageQuality_FullPam), + webService_(webService), + orthancApiClient_(broker, webService) + { + DeclareHandledMessage(MessageType_LayerSource_GeometryReady); + DeclareHandledMessage(MessageType_LayerSource_LayerReady); + DeclareIgnoredMessage(MessageType_LayerSource_GeometryError); + DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged); + DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged); + +// DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); +// DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + } + + void SmartLoader::HandleMessage(IObservable& from, const IMessage& message) + { + switch (message.GetType()) { + case MessageType_LayerSource_GeometryReady: + { + const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from); + // TODO keep track of objects that have been loaded already + }; break; + case MessageType_LayerSource_LayerReady: + { + const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from); + // TODO keep track of objects that have been loaded already + }; break; +// case MessageType_OrthancApi_GetStudyIds_Ready: +// { + +// const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast<OrthancApiClient::GetJsonResponseReadyMessage&>(message); + +// }; break; + default: + VLOG("unhandled message type" << message.GetType()); + } + + // forward messages to its own observers + IObservable::broker_.EmitMessage(from, IObservable::observers_, message); + } + + ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame) + { + // TODO: check if this frame has already been loaded or is already being loaded. + // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately" + // (it can not be immediate because Observers needs to register first and this is done after this method returns) + // - if currently loading, we need to return an object that will observe the existing LayerSource and forward + // the messages to its observables + // in both cases, we must be carefull about objects lifecycle !!! + std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_)); + layerSource->SetImageQuality(imageQuality_); + layerSource->RegisterObserver(*this); + layerSource->LoadFrame(instanceId, frame); + + return layerSource.release(); + } + + void SmartLoader::LoadStudyList() + { +// orthancApiClient_.ScheduleGetJsonRequest("/studies"); + } + + void PreloadStudy(const std::string studyId) + { + /* TODO */ + } + + void PreloadSeries(const std::string seriesId) + { + /* TODO */ + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/SmartLoader.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once +#include <map> + +#include "Layers/ILayerSource.h" +#include "Messages/IObservable.h" +#include "../Platforms/Generic/OracleWebService.h" +#include "Toolbox/OrthancApiClient.h" + +namespace OrthancStone +{ + class SmartLoader : public IObservable, IObserver + { + SliceImageQuality imageQuality_; + IWebService& webService_; + OrthancApiClient orthancApiClient_; + + int studyListRequest_; + + public: + SmartLoader(MessageBroker& broker, IWebService& webService); // TODO: add maxPreloadStorageSizeInBytes + + virtual void HandleMessage(IObservable& from, const IMessage& message); + + void PreloadStudy(const std::string studyId); + void PreloadSeries(const std::string seriesId); + void LoadStudyList(); + + void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; } + + ILayerSource* GetFrame(const std::string& instanceId, unsigned int frame); + + void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId); + + }; + +}
--- a/Framework/StoneEnumerations.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/StoneEnumerations.h Thu Aug 30 16:56:08 2018 +0200 @@ -77,10 +77,13 @@ enum SliceImageQuality { - SliceImageQuality_Full, + SliceImageQuality_FullPng, // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth) + SliceImageQuality_FullPam, // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN) SliceImageQuality_Jpeg50, SliceImageQuality_Jpeg90, - SliceImageQuality_Jpeg95 + SliceImageQuality_Jpeg95, + + SliceImageQuality_InternalRaw // downloads the raw pixels data as they are stored in the DICOM file (internal use only) }; enum SopClassUid
--- a/Framework/Toolbox/IWebService.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Toolbox/IWebService.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -22,40 +22,116 @@ #pragma once #include <Core/IDynamicObject.h> - +#include "../../Framework/Messages/IObserver.h" #include <string> +#include <Core/Logging.h> namespace OrthancStone { - class IWebService : public boost::noncopyable + // The IWebService performs HTTP requests. + // Since applications can run in native or WASM environment and, since + // in a WASM environment, the WebService is asynchronous, the IWebservice + // also implements an asynchronous interface: you must schedule a request + // and you'll be notified when the response/error is ready. + class IWebService { + protected: + MessageBroker& broker_; public: - class ICallback : public boost::noncopyable + typedef std::map<std::string, std::string> Headers; + + class ICallback : public IObserver { public: + struct HttpRequestSuccessMessage: public IMessage + { + const std::string& Uri; + const void* Answer; + size_t AnswerSize; + Orthanc::IDynamicObject* Payload; + HttpRequestSuccessMessage(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + : IMessage(MessageType_HttpRequestSuccess), + Uri(uri), + Answer(answer), + AnswerSize(answerSize), + Payload(payload) + {} + }; + + struct HttpRequestErrorMessage: public IMessage + { + const std::string& Uri; + Orthanc::IDynamicObject* Payload; + HttpRequestErrorMessage(const std::string& uri, + Orthanc::IDynamicObject* payload) + : IMessage(MessageType_HttpRequestError), + Uri(uri), + Payload(payload) + {} + }; + + ICallback(MessageBroker& broker) + : IObserver(broker) + { + DeclareHandledMessage(MessageType_HttpRequestError); + DeclareHandledMessage(MessageType_HttpRequestSuccess); + } virtual ~ICallback() { } - virtual void NotifyError(const std::string& uri, - Orthanc::IDynamicObject* payload) = 0; + virtual void HandleMessage(IObservable& from, const IMessage& message) + { + switch(message.GetType()) + { + case MessageType_HttpRequestError: + { + const HttpRequestErrorMessage& msg = dynamic_cast<const HttpRequestErrorMessage&>(message); + OnHttpRequestError(msg.Uri, + msg.Payload); + }; break; - virtual void NotifySuccess(const std::string& uri, - const void* answer, - size_t answerSize, - Orthanc::IDynamicObject* payload) = 0; + case MessageType_HttpRequestSuccess: + { + const HttpRequestSuccessMessage& msg = dynamic_cast<const HttpRequestSuccessMessage&>(message); + OnHttpRequestSuccess(msg.Uri, + msg.Answer, + msg.AnswerSize, + msg.Payload); + }; break; + default: + VLOG("unhandled message type" << message.GetType()); + } + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) = 0; + + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) = 0; }; - + + IWebService(MessageBroker& broker) + : broker_(broker) + {} + virtual ~IWebService() { } virtual void ScheduleGetRequest(ICallback& callback, const std::string& uri, + const Headers& headers, Orthanc::IDynamicObject* payload) = 0; virtual void SchedulePostRequest(ICallback& callback, const std::string& uri, + const Headers& headers, const std::string& body, Orthanc::IDynamicObject* payload) = 0; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/OrthancApiClient.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,205 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "OrthancApiClient.h" + +#include "MessagingToolbox.h" +#include <Core/OrthancException.h> + +namespace OrthancStone { + + struct OrthancApiClient::InternalGetJsonResponseReadyMessage : + public IMessage + { + OrthancApiClient::BaseRequest* request_; + Json::Value response_; + + InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest* request, + const Json::Value& response) + : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady), + request_(request), + response_(response) + { + } + + }; + + struct OrthancApiClient::InternalGetJsonResponseErrorMessage : + public IMessage + { + OrthancApiClient::BaseRequest* request_; + + InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request) + : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError), + request_(request) + { + } + }; + + + // this class handles a single request to the OrthancApiClient. + // Once the response is ready, it will emit a message to the responseObserver + // the responseObserver must handle only that message (and not all messages from the OrthancApiClient) + class OrthancApiClient::BaseRequest: +// public IObserver, + public IObservable, + public Orthanc::IDynamicObject + { + public: + std::string uri_; + OrthancApiClient& orthanc_; + MessageType messageToEmitWhenResponseReady_; + OrthancApiClient::Mode mode_; + + public: + BaseRequest( + OrthancApiClient& orthanc, + IObserver& responseObserver, + const std::string& uri, + MessageType messageToEmitWhenResponseReady, + OrthancApiClient::Mode mode) + : + //IObserver(orthanc.broker_), + IObservable(orthanc.broker_), + uri_(uri), + orthanc_(orthanc), + messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady), + mode_(mode) + { + // this object will emit only a single message, the one the final responseObserver is expecting + DeclareEmittableMessage(messageToEmitWhenResponseReady); + +// // this object is observing the OrthancApi so it must handle all messages +// DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); +// DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + + //orthanc_.RegisterObserver(*this); + this->RegisterObserver(responseObserver); + } + virtual ~BaseRequest() {} + +// // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver +// virtual void HandleMessage(IObservable& from, const IMessage& message) +// { +// switch (message.GetType()) +// { +// case MessageType_OrthancApi_InternalGetJsonResponseReady: +// { +// const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message); +// EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_)); +// orthanc_.ReleaseRequest(messageReceived.request_); +// }; break; +// default: +// throw MessageNotDeclaredException(message.GetType()); +// } +// } + + }; + + + class OrthancApiClient::WebCallback : public IWebService::ICallback + { + private: + OrthancApiClient& that_; + + public: + WebCallback(MessageBroker& broker, OrthancApiClient& that) : + IWebService::ICallback(broker), + that_(that) + { + } + + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + { + OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload); // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed" + + switch (request->mode_) + { + case OrthancApiClient::Mode_GetJson: + { + Json::Value response; + if (MessagingToolbox::ParseJson(response, answer, answerSize)) + { + request->EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(request->messageToEmitWhenResponseReady_, request->uri_, response)); + } + else + { +// OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); +// that_.EmitMessage(msg); + } + }; break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + that_.ReleaseRequest(request); + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) + { + OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload); // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed" + + switch (request->mode_) + { + case OrthancApiClient::Mode_GetJson: + { +// OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); +// that_.EmitMessage(msg); + // TODO: the request shall send an error message + }; break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + that_.ReleaseRequest(request); + } + }; + + OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc) + : IObservable(broker), + orthanc_(orthanc), + webCallback_(new OrthancApiClient::WebCallback(broker, *this)) + { + DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); + DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + } + + void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady) + { + OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this, + responseObserver, + uri, + messageToEmitWhenResponseReady, + OrthancApiClient::Mode_GetJson); + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request); + requestsInProgress_.insert(request); + } + + void OrthancApiClient::ReleaseRequest(BaseRequest* request) + { + requestsInProgress_.erase(request); + delete request; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/OrthancApiClient.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,90 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <json/json.h> + +#include "IWebService.h" +#include "../Messages/IObservable.h" + + +namespace OrthancStone +{ + class OrthancApiClient: + public IObservable + { + protected: + class BaseRequest; + class GetJsonRequest; + + struct InternalGetJsonResponseReadyMessage; + struct InternalGetJsonResponseErrorMessage; + + public: + struct GetJsonResponseReadyMessage : public IMessage + { + Json::Value response_; + std::string uri_; + + GetJsonResponseReadyMessage(MessageType messageType, + const std::string& uri, + const Json::Value& response) + : IMessage(messageType), + response_(response), + uri_(uri) + { + } + }; + + public: + + enum Mode + { + Mode_GetJson + }; + + protected: + IWebService& orthanc_; + class WebCallback; + boost::shared_ptr<WebCallback> webCallback_; // This is a PImpl pattern + std::set<BaseRequest*> requestsInProgress_; + +// int ScheduleGetJsonRequest(const std::string& uri); + + void ReleaseRequest(BaseRequest* request); + + public: + OrthancApiClient(MessageBroker& broker, + IWebService& orthanc); + virtual ~OrthancApiClient() {} + + // schedule a GET request expecting a JSON request. + // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call + void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady); + + void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);} + void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);} + void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);} + + }; +}
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -29,6 +29,7 @@ #include <Core/Images/ImageProcessing.h> #include <Core/Images/JpegReader.h> #include <Core/Images/PngReader.h> +#include <Core/Images/PamReader.h> #include <Core/Logging.h> #include <Core/OrthancException.h> #include <Core/Toolbox.h> @@ -47,10 +48,10 @@ static std::string base64_decode(const std::string &in) { std::string out; - + std::vector<int> T(256,-1); - for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; - + for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + int val=0, valb=-8; for (size_t i = 0; i < in.size(); i++) { unsigned char c = in[i]; @@ -78,32 +79,32 @@ const Slice* slice_; std::string instanceId_; SliceImageQuality quality_; - + Operation(Mode mode) : mode_(mode) { } - + public: Mode GetMode() const { return mode_; } - + SliceImageQuality GetQuality() const { assert(mode_ == Mode_LoadImage || mode_ == Mode_LoadRawImage); return quality_; } - + unsigned int GetSliceIndex() const { assert(mode_ == Mode_LoadImage || mode_ == Mode_LoadRawImage); return sliceIndex_; } - + const Slice& GetSlice() const { assert(mode_ == Mode_LoadImage || @@ -111,32 +112,32 @@ assert(slice_ != NULL); return *slice_; } - + unsigned int GetFrame() const { assert(mode_ == Mode_FrameGeometry); return frame_; } - + const std::string& GetInstanceId() const { assert(mode_ == Mode_FrameGeometry || mode_ == Mode_InstanceGeometry); return instanceId_; } - + static Operation* DownloadSeriesGeometry() { return new Operation(Mode_SeriesGeometry); } - + static Operation* DownloadInstanceGeometry(const std::string& instanceId) { std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry)); operation->instanceId_ = instanceId; return operation.release(); } - + static Operation* DownloadFrameGeometry(const std::string& instanceId, unsigned int frame) { @@ -145,7 +146,7 @@ operation->frame_ = frame; return operation.release(); } - + static Operation* DownloadSliceImage(unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality) @@ -156,110 +157,129 @@ tmp->quality_ = quality; return tmp.release(); } - + static Operation* DownloadSliceRawImage(unsigned int sliceIndex, const Slice& slice) { std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage)); tmp->sliceIndex_ = sliceIndex; tmp->slice_ = &slice; - tmp->quality_ = SliceImageQuality_Full; + tmp->quality_ = SliceImageQuality_InternalRaw; return tmp.release(); } + + static Operation* DownloadDicomFile(const Slice& slice) + { + std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile)); + tmp->slice_ = &slice; + return tmp.release(); + } + }; - + class OrthancSlicesLoader::WebCallback : public IWebService::ICallback { private: OrthancSlicesLoader& that_; - + public: - WebCallback(OrthancSlicesLoader& that) : + WebCallback(MessageBroker& broker, OrthancSlicesLoader& that) : + IWebService::ICallback(broker), that_(that) { } - - virtual void NotifySuccess(const std::string& uri, - const void* answer, - size_t answerSize, - Orthanc::IDynamicObject* payload) + + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) { std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); - + switch (operation->GetMode()) { - case Mode_SeriesGeometry: - that_.ParseSeriesGeometry(answer, answerSize); + case Mode_SeriesGeometry: + that_.ParseSeriesGeometry(answer, answerSize); + break; + + case Mode_InstanceGeometry: + that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize); + break; + + case Mode_FrameGeometry: + that_.ParseFrameGeometry(operation->GetInstanceId(), + operation->GetFrame(), answer, answerSize); + break; + + case Mode_LoadImage: + switch (operation->GetQuality()) + { + case SliceImageQuality_FullPng: + that_.ParseSliceImagePng(*operation, answer, answerSize); break; - - case Mode_InstanceGeometry: - that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize); - break; - - case Mode_FrameGeometry: - that_.ParseFrameGeometry(operation->GetInstanceId(), - operation->GetFrame(), answer, answerSize); + case SliceImageQuality_FullPam: + that_.ParseSliceImagePam(*operation, answer, answerSize); break; - case Mode_LoadImage: - switch (operation->GetQuality()) - { - case SliceImageQuality_Full: - that_.ParseSliceImagePng(*operation, answer, answerSize); - break; - - case SliceImageQuality_Jpeg50: - case SliceImageQuality_Jpeg90: - case SliceImageQuality_Jpeg95: - that_.ParseSliceImageJpeg(*operation, answer, answerSize); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - break; - - case Mode_LoadRawImage: - that_.ParseSliceRawImage(*operation, answer, answerSize); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - virtual void NotifyError(const std::string& uri, - Orthanc::IDynamicObject* payload) - { - std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); - LOG(ERROR) << "Cannot download " << uri; - - switch (operation->GetMode()) - { - case Mode_FrameGeometry: - case Mode_SeriesGeometry: - that_.userCallback_.NotifyGeometryError(that_); - that_.state_ = State_Error; - break; - - case Mode_LoadImage: - that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(), - operation->GetSlice(), - operation->GetQuality()); + case SliceImageQuality_Jpeg50: + case SliceImageQuality_Jpeg90: + case SliceImageQuality_Jpeg95: + that_.ParseSliceImageJpeg(*operation, answer, answerSize); break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + break; + + case Mode_LoadRawImage: + that_.ParseSliceRawImage(*operation, answer, answerSize); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - } + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); + LOG(ERROR) << "Cannot download " << uri; + + switch (operation->GetMode()) + { + case Mode_FrameGeometry: + case Mode_SeriesGeometry: + that_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + that_.state_ = State_Error; + break; + + case Mode_LoadImage: + { + OrthancSlicesLoader::SliceImageErrorMessage msg(operation->GetSliceIndex(), + operation->GetSlice(), + operation->GetQuality()); + that_.EmitMessage(msg); + }; break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } }; - + + void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message) + { + // forward messages to its own observers + IObservable::broker_.EmitMessage(from, IObservable::observers_, message); + } void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation, - std::auto_ptr<Orthanc::ImageAccessor>& image) const + std::auto_ptr<Orthanc::ImageAccessor>& image) { if (image.get() == NULL) { @@ -267,19 +287,19 @@ } else { - userCallback_.NotifySliceImageReady - (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); + OrthancSlicesLoader::SliceImageReadyMessage msg(operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); + EmitMessage(msg); } } - + - void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const + void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) { - userCallback_.NotifySliceImageError - (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); + OrthancSlicesLoader::SliceImageErrorMessage msg(operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); + EmitMessage(msg); } - - + + void OrthancSlicesLoader::SortAndFinalizeSlices() { bool ok = false; @@ -295,21 +315,21 @@ ok = true; } } - + state_ = State_GeometryReady; - + if (ok) { LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; - userCallback_.NotifyGeometryReady(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady)); } else { LOG(ERROR) << "This series is empty"; - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); } } - + void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer, size_t size) @@ -318,18 +338,18 @@ if (!MessagingToolbox::ParseJson(series, answer, size) || series.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); return; } - + Json::Value::Members instances = series.getMemberNames(); - + slices_.Reserve(instances.size()); - + for (size_t i = 0; i < instances.size(); i++) { OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); @@ -338,7 +358,7 @@ { frames = 1; } - + for (unsigned int frame = 0; frame < frames; frame++) { std::auto_ptr<Slice> slice(new Slice); @@ -352,11 +372,11 @@ } } } - + SortAndFinalizeSlices(); } - - + + void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId, const void* answer, size_t size) @@ -365,15 +385,15 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); return; } - + OrthancPlugins::FullOrthancDataset dataset(tags); - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); - + unsigned int frames; if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) { @@ -381,7 +401,7 @@ } LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)"; - + for (unsigned int frame = 0; frame < frames; frame++) { std::auto_ptr<Slice> slice(new Slice); @@ -392,15 +412,15 @@ else { LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); return; } } - + SortAndFinalizeSlices(); } - - + + void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId, unsigned int frame, const void* answer, @@ -410,33 +430,74 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); return; } - + OrthancPlugins::FullOrthancDataset dataset(tags); - + state_ = State_GeometryReady; - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); - + std::auto_ptr<Slice> slice(new Slice); if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { - LOG(INFO) << "Loaded instance " << instanceId; + LOG(INFO) << "Loaded instance geometry " << instanceId; slices_.AddSlice(slice.release()); - userCallback_.NotifyGeometryReady(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady)); } else { LOG(WARNING) << "Skipping invalid instance " << instanceId; - userCallback_.NotifyGeometryError(*this); + EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); } } - + + + void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation, + const void* answer, + size_t size) + { + std::auto_ptr<Orthanc::ImageAccessor> image; + + try + { + image.reset(new Orthanc::PngReader); + dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size); + } + catch (Orthanc::OrthancException&) + { + NotifySliceImageError(operation); + return; + } + + if (image->GetWidth() != operation.GetSlice().GetWidth() || + image->GetHeight() != operation.GetSlice().GetHeight()) + { + NotifySliceImageError(operation); + return; + } - void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation, + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == + Orthanc::PixelFormat_SignedGrayscale16) + { + if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + NotifySliceImageError(operation); + return; + } + } + + NotifySliceImageSuccess(operation, image); + } + + void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation, const void* answer, size_t size) { @@ -444,8 +505,8 @@ try { - image.reset(new Orthanc::PngReader); - dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size); + image.reset(new Orthanc::PamReader); + dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(answer), size)); } catch (Orthanc::OrthancException&) { @@ -459,7 +520,7 @@ NotifySliceImageError(operation); return; } - + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == Orthanc::PixelFormat_SignedGrayscale16) { @@ -475,9 +536,9 @@ } NotifySliceImageSuccess(operation, image); - } + } - + void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation, const void* answer, size_t size) @@ -491,7 +552,7 @@ NotifySliceImageError(operation); return; } - + Json::Value& info = encoded["Orthanc"]; if (!info.isMember("PixelData") || !info.isMember("Stretched") || @@ -504,30 +565,30 @@ NotifySliceImageError(operation); return; } - + bool isSigned = false; bool isStretched = info["Stretched"].asBool(); - + if (info.isMember("IsSigned")) { if (info["IsSigned"].type() != Json::booleanValue) { NotifySliceImageError(operation); return; - } + } else { isSigned = info["IsSigned"].asBool(); } } - + std::auto_ptr<Orthanc::ImageAccessor> reader; - + { std::string jpeg; //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); jpeg = base64_decode(info["PixelData"].asString()); - + try { reader.reset(new Orthanc::JpegReader); @@ -539,10 +600,10 @@ return; } } - + Orthanc::PixelFormat expectedFormat = - operation.GetSlice().GetConverter().GetExpectedPixelFormat(); - + operation.GetSlice().GetConverter().GetExpectedPixelFormat(); + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image { if (expectedFormat != Orthanc::PixelFormat_RGB24) @@ -550,7 +611,7 @@ NotifySliceImageError(operation); return; } - + if (isSigned || isStretched) { NotifySliceImageError(operation); @@ -562,13 +623,13 @@ return; } } - + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) { NotifySliceImageError(operation); return; } - + if (!isStretched) { if (expectedFormat != reader->GetFormat()) @@ -582,10 +643,10 @@ return; } } - + int32_t stretchLow = 0; int32_t stretchHigh = 0; - + if (!info.isMember("StretchLow") || !info.isMember("StretchHigh") || info["StretchLow"].type() != Json::intValue || @@ -594,10 +655,10 @@ NotifySliceImageError(operation); return; } - + stretchLow = info["StretchLow"].asInt(); stretchHigh = info["StretchHigh"].asInt(); - + if (stretchLow < -32768 || stretchHigh > 65535 || (stretchLow < 0 && stretchHigh > 32767)) @@ -606,29 +667,29 @@ NotifySliceImageError(operation); return; } - + // Decode a grayscale JPEG 8bpp image coming from the Web viewer std::auto_ptr<Orthanc::ImageAccessor> image - (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); - + (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); + Orthanc::ImageProcessing::Convert(*image, *reader); reader.reset(NULL); - + float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; - + if (!LinearAlgebra::IsCloseToZero(scaling)) { float offset = static_cast<float>(stretchLow) / scaling; Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); } - + NotifySliceImageSuccess(operation, image); } - - + + class StringImage : - public Orthanc::ImageAccessor, - public boost::noncopyable + public Orthanc::ImageAccessor, + public boost::noncopyable { private: std::string buffer_; @@ -643,23 +704,22 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); } - + buffer_.swap(buffer); // The source buffer is now empty - + void* data = (buffer_.empty() ? NULL : &buffer_[0]); - + AssignWritable(format, width, height, Orthanc::GetBytesPerPixel(format) * width, data); } }; - void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation, const void* answer, size_t size) { Orthanc::GzipCompressor compressor; - + std::string raw; compressor.Uncompress(raw, answer, size); @@ -676,9 +736,9 @@ // This is the case of RT-DOSE (uint32_t values) std::auto_ptr<Orthanc::ImageAccessor> image - (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), - info.GetHeight(), raw)); - + (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), + info.GetHeight(), raw)); + // TODO - Only for big endian for (unsigned int y = 0; y < image->GetHeight(); y++) { @@ -688,7 +748,7 @@ *p = le32toh(*p); } } - + NotifySliceImageSuccess(operation, image); } else if (info.GetBitsAllocated() == 16 && @@ -700,30 +760,36 @@ raw.size() == info.GetWidth() * info.GetHeight() * 2) { std::auto_ptr<Orthanc::ImageAccessor> image - (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), - info.GetHeight(), raw)); - + (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), + info.GetHeight(), raw)); + // TODO - Big endian ? - + NotifySliceImageSuccess(operation, image); } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - + } - - - OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback, + + + OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker, + //ISliceLoaderObserver& callback, IWebService& orthanc) : - webCallback_(new WebCallback(*this)), - userCallback_(callback), + IObservable(broker), + webCallback_(new WebCallback(broker, *this)), + //userCallback_(callback), orthanc_(orthanc), state_(State_Initialization) { + DeclareEmittableMessage(MessageType_SliceLoader_GeometryReady); + DeclareEmittableMessage(MessageType_SliceLoader_GeometryError); + DeclareEmittableMessage(MessageType_SliceLoader_ImageError); + DeclareEmittableMessage(MessageType_SliceLoader_ImageReady); } - + void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId) { @@ -735,11 +801,11 @@ { state_ = State_LoadingGeometry; std::string uri = "/series/" + seriesId + "/instances-tags"; - orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry()); + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry()); } } - - + + void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId) { if (state_ != State_Initialization) @@ -749,16 +815,16 @@ else { state_ = State_LoadingGeometry; - + // Tag "3004-000c" is "Grid Frame Offset Vector", which is // mandatory to read RT DOSE, but is too long to be returned by default std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c"; orthanc_.ScheduleGetRequest - (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId)); + (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId)); } } - + void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId, unsigned int frame) { @@ -771,27 +837,27 @@ state_ = State_LoadingGeometry; std::string uri = "/instances/" + instanceId + "/tags"; orthanc_.ScheduleGetRequest - (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame)); + (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame)); } } - + bool OrthancSlicesLoader::IsGeometryReady() const { return state_ == State_GeometryReady; } - - + + size_t OrthancSlicesLoader::GetSliceCount() const { if (state_ != State_GeometryReady) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.GetSliceCount(); } - + const Slice& OrthancSlicesLoader::GetSlice(size_t index) const { @@ -799,11 +865,11 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.GetSlice(index); } - + bool OrthancSlicesLoader::LookupSlice(size_t& index, const CoordinateSystem3D& plane) const { @@ -811,76 +877,109 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.LookupSlice(index, plane); } - + void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice, size_t index) { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + switch (slice.GetConverter().GetExpectedPixelFormat()) + { + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + IWebService::Headers headers; + headers["Accept"] = "image/png"; + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), + Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng)); + } + + void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, + size_t index) + { + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast<std::string>(slice.GetFrame())); switch (slice.GetConverter().GetExpectedPixelFormat()) { - case Orthanc::PixelFormat_RGB24: - uri += "/preview"; - break; + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; - case Orthanc::PixelFormat_Grayscale16: - uri += "/image-uint16"; - break; + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; - case Orthanc::PixelFormat_SignedGrayscale16: - uri += "/image-int16"; - break; + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - orthanc_.ScheduleGetRequest(*webCallback_, uri, - Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full)); + IWebService::Headers headers; + headers["Accept"] = "image/x-portable-arbitrarymap"; + orthanc_.ScheduleGetRequest(*webCallback_, uri, headers, + Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam)); } + void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, size_t index, SliceImageQuality quality) { unsigned int value; - + switch (quality) { - case SliceImageQuality_Jpeg50: - value = 50; - break; - - case SliceImageQuality_Jpeg90: - value = 90; - break; - - case SliceImageQuality_Jpeg95: - value = 95; - break; + case SliceImageQuality_Jpeg50: + value = 50; + break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + case SliceImageQuality_Jpeg90: + value = 90; + break; + + case SliceImageQuality_Jpeg95: + value = 95; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } // This requires the official Web viewer plugin to be installed! - std::string uri = ("/web-viewer/instances/jpeg" + - boost::lexical_cast<std::string>(value) + - "-" + slice.GetOrthancInstanceId() + "_" + + std::string uri = ("/web-viewer/instances/jpeg" + + boost::lexical_cast<std::string>(value) + + "-" + slice.GetOrthancInstanceId() + "_" + boost::lexical_cast<std::string>(slice.GetFrame())); - - orthanc_.ScheduleGetRequest(*webCallback_, uri, + + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSliceImage(index, slice, quality)); } - - - + + + void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index, SliceImageQuality quality) { @@ -888,25 +987,28 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + const Slice& slice = GetSlice(index); - + if (slice.HasOrthancDecoding()) { - if (quality == SliceImageQuality_Full) + switch (quality) { + case SliceImageQuality_FullPng: ScheduleSliceImagePng(slice, index); - } - else - { + break; + case SliceImageQuality_FullPam: + ScheduleSliceImagePam(slice, index); + break; + default: ScheduleSliceImageJpeg(slice, index, quality); } } else { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz"); - orthanc_.ScheduleGetRequest(*webCallback_, uri, + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSliceRawImage(index, slice)); } }
--- a/Framework/Toolbox/OrthancSlicesLoader.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Thu Aug 30 16:56:08 2018 +0200 @@ -24,35 +24,49 @@ #include "IWebService.h" #include "SlicesSorter.h" #include "../StoneEnumerations.h" - +#include "../Messages/IObservable.h" #include <boost/shared_ptr.hpp> namespace OrthancStone { - class OrthancSlicesLoader : public boost::noncopyable + class OrthancSlicesLoader : public IObservable { public: - class ICallback : public boost::noncopyable + struct SliceImageReadyMessage : public IMessage { - public: - virtual ~ICallback() + unsigned int sliceIndex_; + const Slice& slice_; + std::auto_ptr<Orthanc::ImageAccessor>& image_; + SliceImageQuality effectiveQuality_; + + SliceImageReadyMessage(unsigned int sliceIndex, + const Slice& slice, + std::auto_ptr<Orthanc::ImageAccessor>& image, + SliceImageQuality effectiveQuality) + : IMessage(MessageType_SliceLoader_ImageReady), + sliceIndex_(sliceIndex), + slice_(slice), + image_(image), + effectiveQuality_(effectiveQuality) { } + }; - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0; - - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0; + struct SliceImageErrorMessage : public IMessage + { + const Slice& slice_; + unsigned int sliceIndex_; + SliceImageQuality effectiveQuality_; - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - std::auto_ptr<Orthanc::ImageAccessor>& image, - SliceImageQuality effectiveQuality) = 0; - - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - SliceImageQuality quality) = 0; + SliceImageErrorMessage(unsigned int sliceIndex, + const Slice& slice, + SliceImageQuality effectiveQuality) + : IMessage(MessageType_SliceLoader_ImageError), + slice_(slice), + sliceIndex_(sliceIndex), + effectiveQuality_(effectiveQuality) + { + } }; private: @@ -70,7 +84,8 @@ Mode_InstanceGeometry, Mode_FrameGeometry, Mode_LoadImage, - Mode_LoadRawImage + Mode_LoadRawImage, + Mode_LoadDicomFile }; class Operation; @@ -78,15 +93,14 @@ boost::shared_ptr<WebCallback> webCallback_; // This is a PImpl pattern - ICallback& userCallback_; IWebService& orthanc_; State state_; SlicesSorter slices_; void NotifySliceImageSuccess(const Operation& operation, - std::auto_ptr<Orthanc::ImageAccessor>& image) const; + std::auto_ptr<Orthanc::ImageAccessor>& image); - void NotifySliceImageError(const Operation& operation) const; + void NotifySliceImageError(const Operation& operation); void ParseSeriesGeometry(const void* answer, size_t size); @@ -104,6 +118,10 @@ const void* answer, size_t size); + void ParseSliceImagePam(const Operation& operation, + const void* answer, + size_t size); + void ParseSliceImageJpeg(const Operation& operation, const void* answer, size_t size); @@ -114,7 +132,10 @@ void ScheduleSliceImagePng(const Slice& slice, size_t index); - + + void ScheduleSliceImagePam(const Slice& slice, + size_t index); + void ScheduleSliceImageJpeg(const Slice& slice, size_t index, SliceImageQuality quality); @@ -122,7 +143,8 @@ void SortAndFinalizeSlices(); public: - OrthancSlicesLoader(ICallback& callback, + OrthancSlicesLoader(MessageBroker& broker, + //ISliceLoaderObserver& callback, IWebService& orthanc); void ScheduleLoadSeries(const std::string& seriesId); @@ -143,5 +165,7 @@ void ScheduleLoadSliceImage(size_t index, SliceImageQuality requestedQuality); + + virtual void HandleMessage(IObservable& from, const IMessage& message); }; }
--- a/Framework/Viewport/IMouseTracker.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Viewport/IMouseTracker.h Thu Aug 30 16:56:08 2018 +0200 @@ -25,6 +25,9 @@ namespace OrthancStone { + // this is tracking a mouse in screen coordinates/pixels unlike + // the IWorldSceneMouseTracker that is tracking a mouse + // in scene coordinates/mm. class IMouseTracker : public boost::noncopyable { public:
--- a/Framework/Viewport/IViewport.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Viewport/IViewport.h Thu Aug 30 16:56:08 2018 +0200 @@ -40,7 +40,7 @@ { } - virtual void NotifyChange(const IViewport& scene) = 0; + virtual void OnViewportContentChanged(const IViewport& scene) = 0; }; virtual ~IViewport() @@ -86,6 +86,6 @@ virtual void UpdateContent() = 0; // Should only be called from IWidget - virtual void NotifyChange(const IWidget& widget) = 0; + virtual void NotifyContentChanged(const IWidget& widget) = 0; }; }
--- a/Framework/Viewport/WidgetViewport.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Viewport/WidgetViewport.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -64,7 +64,7 @@ } mouseTracker_.reset(NULL); - + centralWidget_.reset(widget); centralWidget_->SetViewport(*this); @@ -74,16 +74,16 @@ } backgroundChanged_ = true; - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); return *widget; } - void WidgetViewport::NotifyChange(const IWidget& widget) + void WidgetViewport::NotifyContentChanged(const IWidget& widget) { backgroundChanged_ = true; - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } @@ -97,7 +97,7 @@ centralWidget_->SetSize(width, height); } - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } @@ -154,7 +154,7 @@ mouseTracker_.reset(NULL); } - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } @@ -164,7 +164,7 @@ { mouseTracker_->MouseUp(); mouseTracker_.reset(NULL); - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } } @@ -195,7 +195,7 @@ if (repaint) { // The scene must be repainted, notify the observers - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } } @@ -203,7 +203,7 @@ void WidgetViewport::MouseEnter() { isMouseOver_ = true; - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); } @@ -217,7 +217,7 @@ mouseTracker_.reset(NULL); } - observers_.Apply(*this, &IObserver::NotifyChange); + observers_.Apply(*this, &IObserver::OnViewportContentChanged); }
--- a/Framework/Viewport/WidgetViewport.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Viewport/WidgetViewport.h Thu Aug 30 16:56:08 2018 +0200 @@ -51,7 +51,7 @@ IWidget& SetCentralWidget(IWidget* widget); // Takes ownership - virtual void NotifyChange(const IWidget& widget); + virtual void NotifyContentChanged(const IWidget& widget); virtual void Register(IObserver& observer) {
--- a/Framework/Volumes/StructureSetLoader.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Volumes/StructureSetLoader.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -61,14 +61,14 @@ }; - void StructureSetLoader::NotifyError(const std::string& uri, + void StructureSetLoader::OnHttpRequestError(const std::string& uri, Orthanc::IDynamicObject* payload) { // TODO } - void StructureSetLoader::NotifySuccess(const std::string& uri, + void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri, const void* answer, size_t answerSize, Orthanc::IDynamicObject* payload) @@ -88,7 +88,7 @@ for (std::set<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) { - orthanc_.SchedulePostRequest(*this, "/tools/lookup", *it, + orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it, new Operation(Operation::Type_LookupSopInstanceUid, *it)); } @@ -115,7 +115,7 @@ } const std::string& instance = lookup[0]["ID"].asString(); - orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", + orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(), new Operation(Operation::Type_LoadReferencedSlice, instance)); } else @@ -145,7 +145,8 @@ } - StructureSetLoader::StructureSetLoader(IWebService& orthanc) : + StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) : + IWebService::ICallback(broker), orthanc_(orthanc) { } @@ -160,7 +161,7 @@ else { const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050"; - orthanc_.ScheduleGetRequest(*this, uri, new Operation(Operation::Type_LoadStructureSet, instance)); + orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance)); } }
--- a/Framework/Volumes/StructureSetLoader.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Volumes/StructureSetLoader.h Thu Aug 30 16:56:08 2018 +0200 @@ -34,10 +34,10 @@ private: class Operation; - virtual void NotifyError(const std::string& uri, + virtual void OnHttpRequestError(const std::string& uri, Orthanc::IDynamicObject* payload); - virtual void NotifySuccess(const std::string& uri, + virtual void OnHttpRequestSuccess(const std::string& uri, const void* answer, size_t answerSize, Orthanc::IDynamicObject* payload); @@ -46,7 +46,7 @@ std::auto_ptr<DicomStructureSet> structureSet_; public: - StructureSetLoader(IWebService& orthanc); + StructureSetLoader(MessageBroker& broker, IWebService& orthanc); void ScheduleLoadInstance(const std::string& instance);
--- a/Framework/Widgets/CairoWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/CairoWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -32,6 +32,10 @@ return true; } + CairoWidget::CairoWidget(const std::string& name) + : WidgetBase(name) + { + } void CairoWidget::SetSize(unsigned int width, unsigned int height)
--- a/Framework/Widgets/CairoWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/CairoWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -38,6 +38,8 @@ int y) = 0; public: + CairoWidget(const std::string& name); + virtual void SetSize(unsigned int width, unsigned int height);
--- a/Framework/Widgets/EmptyWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/EmptyWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -26,19 +26,16 @@ namespace OrthancStone { - namespace Samples + bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) { - bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) - { - // Note: This call is slow - Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); - return true; - } + // Note: This call is slow + Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); + return true; + } - - void EmptyWidget::UpdateContent() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } + + void EmptyWidget::UpdateContent() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } }
--- a/Framework/Widgets/EmptyWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/EmptyWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -25,93 +25,90 @@ namespace OrthancStone { - namespace Samples - { - /** + /** * This is a test widget that simply fills its surface with an * uniform color. **/ - class EmptyWidget : public IWidget - { - private: - uint8_t red_; - uint8_t green_; - uint8_t blue_; + class EmptyWidget : public IWidget + { + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; - public: - EmptyWidget(uint8_t red, - uint8_t green, - uint8_t blue) : - red_(red), - green_(green), - blue_(blue) - { - } + public: + EmptyWidget(uint8_t red, + uint8_t green, + uint8_t blue) : + red_(red), + green_(green), + blue_(blue) + { + } + + virtual void SetDefaultView() + { + } - virtual void SetDefaultView() - { - } - - virtual void SetParent(OrthancStone::IWidget& widget) - { - } - - virtual void SetViewport(IViewport& viewport) - { - } + virtual void SetParent(IWidget& widget) + { + } + + virtual void SetViewport(IViewport& viewport) + { + } - virtual void NotifyChange() - { - } + virtual void NotifyContentChanged() + { + } - virtual void SetStatusBar(IStatusBar& statusBar) - { - } + virtual void SetStatusBar(IStatusBar& statusBar) + { + } + + virtual void SetSize(unsigned int width, + unsigned int height) + { + } - virtual void SetSize(unsigned int width, - unsigned int height) - { - } - - virtual bool Render(Orthanc::ImageAccessor& surface); + virtual bool Render(Orthanc::ImageAccessor& surface); - virtual IMouseTracker* CreateMouseTracker(MouseButton button, - int x, - int y, - KeyboardModifiers modifiers) - { - return NULL; - } + virtual IMouseTracker* CreateMouseTracker(MouseButton button, + int x, + int y, + KeyboardModifiers modifiers) + { + return NULL; + } - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - } + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + } - virtual void MouseWheel(MouseWheelDirection direction, - int x, - int y, - KeyboardModifiers modifiers) - { - } + virtual void MouseWheel(MouseWheelDirection direction, + int x, + int y, + KeyboardModifiers modifiers) + { + } - virtual void KeyPressed(char key, - KeyboardModifiers modifiers) - { - } + virtual void KeyPressed(char key, + KeyboardModifiers modifiers) + { + } - virtual bool HasUpdateContent() const - { - return false; - } + virtual bool HasUpdateContent() const + { + return false; + } - virtual void UpdateContent(); + virtual void UpdateContent(); - virtual bool HasRenderMouseOver() - { - return false; - } - }; - } + virtual bool HasRenderMouseOver() + { + return false; + } + }; }
--- a/Framework/Widgets/IWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/IWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -73,6 +73,6 @@ // Subclasses can call this method to signal the display of the // widget must be refreshed - virtual void NotifyChange() = 0; + virtual void NotifyContentChanged() = 0; }; }
--- a/Framework/Widgets/IWorldSceneInteractor.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/IWorldSceneInteractor.h Thu Aug 30 16:56:08 2018 +0200 @@ -41,6 +41,7 @@ virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, const ViewportGeometry& view, MouseButton button, + KeyboardModifiers modifiers, double x, double y, IStatusBar* statusBar) = 0;
--- a/Framework/Widgets/IWorldSceneMouseTracker.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/IWorldSceneMouseTracker.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -25,16 +25,20 @@ namespace OrthancStone { + + // this is tracking a mouse in scene coordinates/mm unlike + // the IMouseTracker that is tracking a mouse + // in screen coordinates/pixels. class IWorldSceneMouseTracker : public boost::noncopyable { public: virtual ~IWorldSceneMouseTracker() { } - + virtual void Render(CairoContext& context, double zoom) = 0; - + virtual void MouseUp() = 0; virtual void MouseMove(double x,
--- a/Framework/Widgets/LayerWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/LayerWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -55,7 +55,7 @@ countMissing_++; } } - + public: Scene(const CoordinateSystem3D& slice, double thickness, @@ -184,7 +184,7 @@ #endif cairo_set_line_width(cr, 2.0 / view.GetZoom()); - cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_source_rgb(cr, 1, 1, 1); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_fill(cr); @@ -215,7 +215,7 @@ { double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) - slice_.ProjectAlongNormal(slice_.GetOrigin())); - + if (z < 0) { z = -z; @@ -249,7 +249,7 @@ return true; } } - + void LayerWidget::GetLayerExtent(Extent2D& extent, ILayerSource& source) const @@ -268,7 +268,7 @@ } } - + Extent2D LayerWidget::GetSceneExtent() { Extent2D sceneExtent; @@ -341,7 +341,7 @@ currentScene_->ContainsPlane(slice)) { currentScene_->SetLayer(index, tmp.release()); - NotifyChange(); + NotifyContentChanged(); } else if (pendingScene_.get() != NULL && pendingScene_->ContainsPlane(slice)) @@ -353,15 +353,27 @@ pendingScene_->IsComplete()) { currentScene_ = pendingScene_; - NotifyChange(); + NotifyContentChanged(); } } } - LayerWidget::LayerWidget() : + LayerWidget::LayerWidget(MessageBroker& broker, const std::string& name) : + WorldSceneWidget(name), + IObserver(broker), + IObservable(broker), started_(false) { + DeclareHandledMessage(MessageType_LayerSource_GeometryReady); + DeclareHandledMessage(MessageType_LayerSource_ContentChanged); + DeclareHandledMessage(MessageType_LayerSource_LayerReady); + DeclareHandledMessage(MessageType_LayerSource_SliceChanged); + DeclareHandledMessage(MessageType_LayerSource_GeometryError); + + DeclareEmittableMessage(MessageType_Widget_GeometryChanged); + DeclareEmittableMessage(MessageType_Widget_ContentChanged); + SetBackgroundCleared(true); } @@ -388,14 +400,36 @@ layersIndex_[layer] = index; ResetPendingScene(); - layer->Register(*this); + layer->RegisterObserver(*this); ResetChangedLayers(); return index; } - + void LayerWidget::ReplaceLayer(size_t index, ILayerSource* layer) // Takes ownership + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + delete layers_[index]; + layers_[index] = layer; + layersIndex_[layer] = index; + + ResetPendingScene(); + layer->RegisterObserver(*this); + + InvalidateLayer(index); + } + + const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const { if (layer >= layers_.size()) @@ -429,7 +463,7 @@ pendingScene_->SetLayerStyle(layer, style); } - NotifyChange(); + NotifyContentChanged(); } @@ -457,26 +491,47 @@ } } + void LayerWidget::HandleMessage(IObservable& from, const IMessage& message) + { + switch (message.GetType()) { + case MessageType_LayerSource_GeometryReady: + OnGeometryReady(dynamic_cast<const ILayerSource&>(from)); + break; + case MessageType_LayerSource_GeometryError: + LOG(ERROR) << "Cannot get geometry"; + break; + case MessageType_LayerSource_ContentChanged: + OnContentChanged(dynamic_cast<const ILayerSource&>(from)); + break; + case MessageType_LayerSource_SliceChanged: + OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_); + break; + case MessageType_LayerSource_LayerReady: + { + const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message); + OnLayerReady(layerReadyMessage.layer_, + dynamic_cast<const ILayerSource&>(from), + layerReadyMessage.slice_, + layerReadyMessage.isError_); + }; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } - void LayerWidget::NotifyGeometryReady(const ILayerSource& source) + void LayerWidget::OnGeometryReady(const ILayerSource& source) { size_t i; if (LookupLayer(i, source)) { - LOG(INFO) << "Geometry ready for layer " << i; + LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName(); changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(slice_); } + EmitMessage(IMessage(MessageType_Widget_GeometryChanged)); } - - void LayerWidget::NotifyGeometryError(const ILayerSource& source) - { - LOG(ERROR) << "Cannot get geometry"; - } - - void LayerWidget::InvalidateAllLayers() { for (size_t i = 0; i < layers_.size(); i++) @@ -503,7 +558,7 @@ } - void LayerWidget::NotifyContentChange(const ILayerSource& source) + void LayerWidget::OnContentChanged(const ILayerSource& source) { size_t index; if (LookupLayer(index, source)) @@ -513,8 +568,8 @@ } - void LayerWidget::NotifySliceChange(const ILayerSource& source, - const Slice& slice) + void LayerWidget::OnSliceChanged(const ILayerSource& source, + const Slice& slice) { if (slice.ContainsPlane(slice_)) { @@ -527,10 +582,10 @@ } - void LayerWidget::NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError) + void LayerWidget::OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer, + const ILayerSource& source, + const CoordinateSystem3D& slice, + bool isError) { size_t index; if (LookupLayer(index, source))
--- a/Framework/Widgets/LayerWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/LayerWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -24,14 +24,16 @@ #include "WorldSceneWidget.h" #include "../Layers/ILayerSource.h" #include "../Toolbox/Extent2D.h" +#include "../../Framework/Messages/IObserver.h" #include <map> namespace OrthancStone { class LayerWidget : - public WorldSceneWidget, - private ILayerSource::IObserver + public WorldSceneWidget, + public IObserver, + public IObservable { private: class Scene; @@ -53,25 +55,28 @@ void GetLayerExtent(Extent2D& extent, ILayerSource& source) const; - virtual void NotifyGeometryReady(const ILayerSource& source); + void OnGeometryReady(const ILayerSource& source); - virtual void NotifyGeometryError(const ILayerSource& source); - - virtual void NotifyContentChange(const ILayerSource& source); + virtual void OnContentChanged(const ILayerSource& source); - virtual void NotifySliceChange(const ILayerSource& source, - const Slice& slice); + virtual void OnSliceChanged(const ILayerSource& source, + const Slice& slice); - virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError); + virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer, + const ILayerSource& source, + const CoordinateSystem3D& slice, + bool isError); + void ResetChangedLayers(); public: + LayerWidget(MessageBroker& broker, const std::string& name); + + virtual void HandleMessage(IObservable& from, const IMessage& message); + virtual Extent2D GetSceneExtent(); - + protected: virtual bool RenderScene(CairoContext& context, const ViewportGeometry& view); @@ -87,12 +92,12 @@ void InvalidateLayer(size_t layer); public: - LayerWidget(); - virtual ~LayerWidget(); size_t AddLayer(ILayerSource* layer); // Takes ownership + void ReplaceLayer(size_t layerIndex, ILayerSource* layer); // Takes ownership + size_t GetLayerCount() const { return layers_.size();
--- a/Framework/Widgets/LayoutWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/LayoutWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -82,12 +82,10 @@ int top_; unsigned int width_; unsigned int height_; - bool hasUpdate_; public: ChildWidget(IWidget* widget) : - widget_(widget), - hasUpdate_(widget->HasUpdateContent()) + widget_(widget) { assert(widget != NULL); SetEmpty(); @@ -95,7 +93,7 @@ void UpdateContent() { - if (hasUpdate_) + if (widget_->HasUpdateContent()) { widget_->UpdateContent(); } @@ -263,11 +261,12 @@ } } - NotifyChange(*this); + NotifyContentChanged(*this); } - LayoutWidget::LayoutWidget() : + LayoutWidget::LayoutWidget(const std::string& name) : + WidgetBase(name), isHorizontal_(true), width_(0), height_(0), @@ -298,10 +297,10 @@ } - void LayoutWidget::NotifyChange(const IWidget& widget) + void LayoutWidget::NotifyContentChanged(const IWidget& widget) { // One of the children has changed - WidgetBase::NotifyChange(); + WidgetBase::NotifyContentChanged(); }
--- a/Framework/Widgets/LayoutWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/LayoutWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -49,13 +49,13 @@ void ComputeChildrenExtents(); public: - LayoutWidget(); + LayoutWidget(const std::string& name); virtual ~LayoutWidget(); virtual void SetDefaultView(); - virtual void NotifyChange(const IWidget& widget); + virtual void NotifyContentChanged(const IWidget& widget); void SetHorizontal();
--- a/Framework/Widgets/TestCairoWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/TestCairoWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -36,7 +36,7 @@ value_ = 1; } - NotifyChange(); + NotifyContentChanged(); } @@ -77,7 +77,8 @@ } - TestCairoWidget::TestCairoWidget(bool animate) : + TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) : + CairoWidget(name), width_(0), height_(0), value_(1),
--- a/Framework/Widgets/TestCairoWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/TestCairoWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -43,7 +43,7 @@ int y); public: - TestCairoWidget(bool animate); + TestCairoWidget(const std::string& name, bool animate); virtual void SetSize(unsigned int width, unsigned int height);
--- a/Framework/Widgets/TestWorldSceneWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/TestWorldSceneWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -34,6 +34,7 @@ virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, const ViewportGeometry& view, MouseButton button, + KeyboardModifiers modifiers, double x, double y, IStatusBar* statusBar) @@ -110,7 +111,8 @@ } - TestWorldSceneWidget::TestWorldSceneWidget(bool animate) : + TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) : + WorldSceneWidget(name), interactor_(new Interactor), animate_(animate), count_(0) @@ -130,7 +132,7 @@ if (animate_) { count_++; - NotifyChange(); + NotifyContentChanged(); } else {
--- a/Framework/Widgets/TestWorldSceneWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/TestWorldSceneWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -43,7 +43,7 @@ const ViewportGeometry& view); public: - TestWorldSceneWidget(bool animate); + TestWorldSceneWidget(const std::string& name, bool animate); virtual Extent2D GetSceneExtent();
--- a/Framework/Widgets/WidgetBase.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/WidgetBase.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -27,16 +27,16 @@ namespace OrthancStone { - void WidgetBase::NotifyChange() + void WidgetBase::NotifyContentChanged() { if (parent_ != NULL) { - parent_->NotifyChange(); + parent_->NotifyContentChanged(); } if (viewport_ != NULL) { - viewport_->NotifyChange(*this); + viewport_->NotifyContentChanged(*this); } } @@ -101,12 +101,13 @@ } - WidgetBase::WidgetBase() : + WidgetBase::WidgetBase(const std::string& name) : parent_(NULL), viewport_(NULL), statusBar_(NULL), backgroundCleared_(false), - transmitMouseOver_(false) + transmitMouseOver_(false), + name_(name) { backgroundColor_[0] = 0; backgroundColor_[1] = 0;
--- a/Framework/Widgets/WidgetBase.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/WidgetBase.h Thu Aug 30 16:56:08 2018 +0200 @@ -36,6 +36,7 @@ bool backgroundCleared_; uint8_t backgroundColor_[3]; bool transmitMouseOver_; + std::string name_; protected: void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const; @@ -52,7 +53,7 @@ } public: - WidgetBase(); + WidgetBase(const std::string& name); virtual void SetDefaultView() { @@ -104,6 +105,12 @@ return transmitMouseOver_; } - virtual void NotifyChange(); + virtual void NotifyContentChanged(); + + const std::string& GetName() const + { + return name_; + } + }; }
--- a/Framework/Widgets/WorldSceneWidget.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/WorldSceneWidget.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -59,12 +59,16 @@ }; + // this is an adapter between a IWorldSceneMouseTracker + // that is tracking a mouse in scene coordinates/mm and + // an IMouseTracker that is tracking a mouse + // in screen coordinates/pixels. class WorldSceneWidget::SceneMouseTracker : public IMouseTracker { private: ViewportGeometry view_; std::auto_ptr<IWorldSceneMouseTracker> tracker_; - + public: SceneMouseTracker(const ViewportGeometry& view, IWorldSceneMouseTracker* tracker) : @@ -77,17 +81,17 @@ virtual void Render(Orthanc::ImageAccessor& target) { CairoSurface surface(target); - CairoContext context(surface); + CairoContext context(surface); view_.ApplyTransform(context); tracker_->Render(context, view_.GetZoom()); } - virtual void MouseUp() + virtual void MouseUp() { tracker_->MouseUp(); } - virtual void MouseMove(int x, + virtual void MouseMove(int x, int y) { double sceneX, sceneY; @@ -97,121 +101,80 @@ }; - class WorldSceneWidget::PanMouseTracker : public IMouseTracker + WorldSceneWidget::PanMouseTracker::PanMouseTracker(WorldSceneWidget& that, + int x, + int y) : + that_(that), + downX_(x), + downY_(y) { - private: - WorldSceneWidget& that_; - double previousPanX_; - double previousPanY_; - double downX_; - double downY_; + that_.view_.GetPan(previousPanX_, previousPanY_); + } + + void WorldSceneWidget::PanMouseTracker::MouseMove(int x, int y) + { + that_.view_.SetPan(previousPanX_ + x - downX_, + previousPanY_ + y - downY_); + + that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_); + } - public: - PanMouseTracker(WorldSceneWidget& that, - int x, - int y) : - that_(that), - downX_(x), - downY_(y) + WorldSceneWidget::ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget& that, + int x, + int y) : + that_(that), + downX_(x), + downY_(y) + { + oldZoom_ = that_.view_.GetZoom(); + MapMouseToScene(centerX_, centerY_, that_.view_, downX_, downY_); + } + + void WorldSceneWidget::ZoomMouseTracker::MouseMove(int x, + int y) + { + static const double MIN_ZOOM = -4; + static const double MAX_ZOOM = 4; + + if (that_.view_.GetDisplayHeight() <= 3) { - that_.view_.GetPan(previousPanX_, previousPanY_); - } - - virtual void Render(Orthanc::ImageAccessor& surface) - { - } - - virtual void MouseUp() - { + return; // Cannot zoom on such a small image } - virtual void MouseMove(int x, - int y) - { - that_.view_.SetPan(previousPanX_ + x - downX_, - previousPanY_ + y - downY_); - - that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_); - } - }; - + double dy = (static_cast<double>(y - downY_) / + static_cast<double>(that_.view_.GetDisplayHeight() - 1)); // In the range [-1,1] + double z; - class WorldSceneWidget::ZoomMouseTracker : public IMouseTracker - { - private: - WorldSceneWidget& that_; - int downX_; - int downY_; - double centerX_; - double centerY_; - double oldZoom_; - - public: - ZoomMouseTracker(WorldSceneWidget& that, - int x, - int y) : - that_(that), - downX_(x), - downY_(y) + // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM] + if (dy < -1.0) { - oldZoom_ = that_.view_.GetZoom(); - MapMouseToScene(centerX_, centerY_, that_.view_, downX_, downY_); + z = MIN_ZOOM; } - - virtual void Render(Orthanc::ImageAccessor& surface) + else if (dy > 1.0) { + z = MAX_ZOOM; } - - virtual void MouseUp() + else { + z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0; } - virtual void MouseMove(int x, - int y) - { - static const double MIN_ZOOM = -4; - static const double MAX_ZOOM = 4; + z = pow(2.0, z); - if (that_.view_.GetDisplayHeight() <= 3) - { - return; // Cannot zoom on such a small image - } - - double dy = (static_cast<double>(y - downY_) / - static_cast<double>(that_.view_.GetDisplayHeight() - 1)); // In the range [-1,1] - double z; + that_.view_.SetZoom(oldZoom_ * z); - // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM] - if (dy < -1.0) - { - z = MIN_ZOOM; - } - else if (dy > 1.0) - { - z = MAX_ZOOM; - } - else - { - z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0; - } - - z = pow(2.0, z); + // Correct the pan so that the original click point is kept at + // the same location on the display + double panX, panY; + that_.view_.GetPan(panX, panY); - that_.view_.SetZoom(oldZoom_ * z); - - // Correct the pan so that the original click point is kept at - // the same location on the display - double panX, panY; - that_.view_.GetPan(panX, panY); + int tx, ty; + that_.view_.MapSceneToDisplay(tx, ty, centerX_, centerY_); + that_.view_.SetPan(panX + static_cast<double>(downX_ - tx), + panY + static_cast<double>(downY_ - ty)); - int tx, ty; - that_.view_.MapSceneToDisplay(tx, ty, centerX_, centerY_); - that_.view_.SetPan(panX + static_cast<double>(downX_ - tx), - panY + static_cast<double>(downY_ - ty)); - - that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_); - } - }; + that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_); + } bool WorldSceneWidget::RenderCairo(CairoContext& context) @@ -272,7 +235,7 @@ SetSceneExtent(view_); view_.SetDefaultView(); - NotifyChange(); + NotifyContentChanged(); observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_); } @@ -282,7 +245,7 @@ { view_ = view; - NotifyChange(); + NotifyContentChanged(); observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_); } @@ -302,24 +265,25 @@ double sceneX, sceneY; MapMouseToScene(sceneX, sceneY, view_, x, y); + // asks the Widget Interactor to provide a mouse tracker std::auto_ptr<IWorldSceneMouseTracker> tracker - (CreateMouseSceneTracker(view_, button, sceneX, sceneY, modifiers)); + (CreateMouseSceneTracker(view_, button, sceneX, sceneY, modifiers)); if (tracker.get() != NULL) { return new SceneMouseTracker(view_, tracker.release()); } - + //TODO: allow Interactor to create Pan & Zoom switch (button) { - case MouseButton_Middle: - return new PanMouseTracker(*this, x, y); + case MouseButton_Middle: + return new PanMouseTracker(*this, x, y); - case MouseButton_Right: - return new ZoomMouseTracker(*this, x, y); + case MouseButton_Right: + return new ZoomMouseTracker(*this, x, y); - default: - return NULL; + default: + return NULL; } } @@ -343,7 +307,7 @@ { if (interactor_) { - return interactor_->CreateMouseTracker(*this, view, button, x, y, GetStatusBar()); + return interactor_->CreateMouseTracker(*this, view, button, modifiers, x, y, GetStatusBar()); } else { @@ -355,7 +319,7 @@ void WorldSceneWidget::MouseWheel(MouseWheelDirection direction, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers) { if (interactor_) {
--- a/Framework/Widgets/WorldSceneWidget.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/Widgets/WorldSceneWidget.h Thu Aug 30 16:56:08 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -46,12 +46,49 @@ const ViewportGeometry& view) = 0; }; + class PanMouseTracker : public IMouseTracker + { + private: + WorldSceneWidget& that_; + double previousPanX_; + double previousPanY_; + double downX_; + double downY_; + + public: + PanMouseTracker(WorldSceneWidget& that, int x, int y); + + virtual void Render(Orthanc::ImageAccessor& surface) {} + + virtual void MouseUp() {} + + virtual void MouseMove(int x, int y); + }; + + class ZoomMouseTracker : public IMouseTracker + { + private: + WorldSceneWidget& that_; + int downX_; + int downY_; + double centerX_; + double centerY_; + double oldZoom_; + + public: + ZoomMouseTracker(WorldSceneWidget& that, int x, int y); + + void Render(Orthanc::ImageAccessor& surface) {} + + virtual void MouseUp() {} + + virtual void MouseMove(int x, int y); + }; + private: struct SizeChangeFunctor; class SceneMouseTracker; - class PanMouseTracker; - class ZoomMouseTracker; typedef ObserversRegistry<WorldSceneWidget, IWorldObserver> Observers; @@ -75,7 +112,8 @@ void SetSceneExtent(ViewportGeometry& geometry); public: - WorldSceneWidget() : + WorldSceneWidget(const std::string& name) : + CairoWidget(name), interactor_(NULL) { }
--- a/Framework/dev.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Framework/dev.h Thu Aug 30 16:56:08 2018 +0200 @@ -43,7 +43,7 @@ // TODO: Handle errors while loading class OrthancVolumeImage : public SlicedVolumeBase, - private OrthancSlicesLoader::ICallback + public OrthancStone::IObserver { private: OrthancSlicesLoader loader_; @@ -106,7 +106,7 @@ } - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) + void OnSliceGeometryReady(const OrthancSlicesLoader& loader) { if (loader.GetSliceCount() == 0) { @@ -151,15 +151,15 @@ unsigned int width = loader.GetSlice(0).GetWidth(); unsigned int height = loader.GetSlice(0).GetHeight(); Orthanc::PixelFormat format = loader.GetSlice(0).GetConverter().GetExpectedPixelFormat(); - LOG(INFO) << "Creating a volume image of size " << width << "x" << height + LOG(INFO) << "Creating a volume image of size " << width << "x" << height << "x" << loader.GetSliceCount() << " in " << Orthanc::EnumerationToString(format); image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount(), computeRange_)); image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry()); - image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), + image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), loader.GetSlice(0).GetPixelSpacingY(), spacingZ); image_->Clear(); - + downloadStack_.reset(new DownloadStack(loader.GetSliceCount())); pendingSlices_ = loader.GetSliceCount(); @@ -173,13 +173,7 @@ SlicedVolumeBase::NotifyGeometryReady(); } - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) - { - LOG(ERROR) << "Unable to download a volume image"; - SlicedVolumeBase::NotifyGeometryError(); - } - - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, + virtual void OnSliceImageReady(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, std::auto_ptr<Orthanc::ImageAccessor>& image, @@ -190,7 +184,7 @@ Orthanc::ImageProcessing::Copy(writer.GetAccessor(), *image); } - SlicedVolumeBase::NotifySliceChange(sliceIndex, slice); + SlicedVolumeBase::NotifySliceChange(sliceIndex, slice); if (pendingSlices_ == 1) { @@ -205,22 +199,47 @@ ScheduleSliceDownload(); } - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - SliceImageQuality quality) + virtual void HandleMessage(const IObservable& from, const IMessage& message) { - LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image"; - ScheduleSliceDownload(); + switch (message.GetType()) + { + case MessageType_SliceLoader_GeometryReady: + OnSliceGeometryReady(dynamic_cast<const OrthancSlicesLoader&>(from)); + case MessageType_SliceLoader_GeometryError: + { + LOG(ERROR) << "Unable to download a volume image"; + SlicedVolumeBase::NotifyGeometryError(); + }; break; + case MessageType_SliceLoader_ImageReady: + { + const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message); + OnSliceImageReady(dynamic_cast<const OrthancSlicesLoader&>(from), + msg.sliceIndex_, + msg.slice_, + msg.image_, + msg.effectiveQuality_); + }; break; + case MessageType_SliceLoader_ImageError: + { + const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message); + LOG(ERROR) << "Cannot download slice " << msg.sliceIndex_ << " in a volume image"; + ScheduleSliceDownload(); + }; break; + default: + VLOG("unhandled message type" << message.GetType()); + } } public: - OrthancVolumeImage(IWebService& orthanc, + OrthancVolumeImage(MessageBroker& broker, + IWebService& orthanc, bool computeRange) : - loader_(*this, orthanc), + OrthancStone::IObserver(broker), + loader_(broker, orthanc), computeRange_(computeRange), pendingSlices_(0) { + loader_.RegisterObserver(*this); } void ScheduleLoadSeries(const std::string& seriesId) @@ -576,7 +595,8 @@ public: - VolumeImageSource(OrthancVolumeImage& volume) : + VolumeImageSource(MessageBroker& broker, OrthancVolumeImage& volume) : + LayerSourceBase(broker), volume_(volume) { volume_.Register(*this); @@ -814,7 +834,8 @@ LayerWidget& otherPlane_; public: - SliceLocationSource(LayerWidget& otherPlane) : + SliceLocationSource(MessageBroker& broker, LayerWidget& otherPlane) : + LayerSourceBase(broker), otherPlane_(otherPlane) { NotifyGeometryReady();
--- a/Platforms/Generic/CMakeLists.txt Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/CMakeLists.txt Thu Aug 30 16:56:08 2018 +0200 @@ -29,6 +29,8 @@ LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) +SET(ENABLE_SDL OFF) +SET(ENABLE_QT ON) SET(ORTHANC_SANDBOXED OFF) SET(ENABLE_CRYPTO_OPTIONS ON) SET(ENABLE_GOOGLE_TEST ON) @@ -45,9 +47,20 @@ ## Build all the sample applications ##################################################################### -macro(BuildSample Target Sample) +if (ENABLE_QT) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui + ) +endif() + +macro(BuildSingeFileSample Target Header Sample) add_executable(${Target} - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainSdl.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} ${APPLICATIONS_SOURCES} ) set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) @@ -57,13 +70,14 @@ # TODO - Re-enable all these samples! -BuildSample(OrthancStoneEmpty 1) -BuildSample(OrthancStoneTestPattern 2) -BuildSample(OrthancStoneSingleFrame 3) -BuildSample(OrthancStoneSingleVolume 4) -#BuildSample(OrthancStoneBasicPetCtFusion 5) -#BuildSample(OrthancStoneSynchronizedSeries 6) -#BuildSample(OrthancStoneLayoutPetCtFusion 7) +#BuildSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2) +#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +##BuildSample(OrthancStoneBasicPetCtFusion 5) +##BuildSample(OrthancStoneSynchronizedSeries 6) +##BuildSample(OrthancStoneLayoutPetCtFusion 7) +BuildSingeFileSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8) ##################################################################### @@ -72,6 +86,7 @@ add_executable(UnitTests ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp )
--- a/Platforms/Generic/Oracle.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/Oracle.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -27,6 +27,7 @@ #include <vector> #include <stdio.h> +#include <boost/thread/mutex.hpp> namespace OrthancStone { @@ -40,7 +41,6 @@ State_Stopped }; - boost::mutex* globalMutex_; boost::mutex oracleMutex_; State state_; std::vector<boost::thread*> threads_; @@ -71,23 +71,13 @@ // Random sleeping to test //boost::this_thread::sleep(boost::posix_time::milliseconds(50 * (1 + rand() % 10))); - if (that->globalMutex_ != NULL) - { - boost::mutex::scoped_lock lock(*that->globalMutex_); - command.Commit(); - } - else - { - command.Commit(); - } + command.Commit(); } } } public: - PImpl(boost::mutex* globalMutex, - unsigned int threadCount) : - globalMutex_(globalMutex), + PImpl(unsigned int threadCount) : state_(State_Init), threads_(threadCount) { @@ -182,19 +172,11 @@ }; - Oracle::Oracle(boost::mutex& globalMutex, - unsigned int threadCount) : - pimpl_(new PImpl(&globalMutex, threadCount)) + Oracle::Oracle(unsigned int threadCount) : + pimpl_(new PImpl(threadCount)) { } - - Oracle::Oracle(unsigned int threadCount) : - pimpl_(new PImpl(NULL, threadCount)) - { - } - - void Oracle::Start() { pimpl_->Start();
--- a/Platforms/Generic/Oracle.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/Oracle.h Thu Aug 30 16:56:08 2018 +0200 @@ -24,7 +24,6 @@ #include "IOracleCommand.h" #include <boost/shared_ptr.hpp> -#include <boost/thread/mutex.hpp> namespace OrthancStone { @@ -36,9 +35,6 @@ boost::shared_ptr<PImpl> pimpl_; public: - Oracle(boost::mutex& globalMutex, - unsigned int threadCount); - Oracle(unsigned int threadCount); void Start();
--- a/Platforms/Generic/OracleWebService.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/OracleWebService.h Thu Aug 30 16:56:08 2018 +0200 @@ -25,36 +25,55 @@ #include "Oracle.h" #include "WebServiceGetCommand.h" #include "WebServicePostCommand.h" +#include "../../Applications/Generic/BasicNativeApplicationContext.h" namespace OrthancStone { + // The OracleWebService performs HTTP requests in a native environment. class OracleWebService : public IWebService { private: Oracle& oracle_; + BasicNativeApplicationContext& context_; Orthanc::WebServiceParameters parameters_; public: - OracleWebService(Oracle& oracle, - const Orthanc::WebServiceParameters& parameters) : + OracleWebService(MessageBroker& broker, + Oracle& oracle, + const Orthanc::WebServiceParameters& parameters, + BasicNativeApplicationContext& context) : + IWebService(broker), oracle_(oracle), + context_(context), parameters_(parameters) { } virtual void ScheduleGetRequest(ICallback& callback, const std::string& uri, + const Headers& headers, Orthanc::IDynamicObject* payload) { - oracle_.Submit(new WebServiceGetCommand(callback, parameters_, uri, payload)); + oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload, context_)); } virtual void SchedulePostRequest(ICallback& callback, const std::string& uri, + const Headers& headers, const std::string& body, Orthanc::IDynamicObject* payload) { - oracle_.Submit(new WebServicePostCommand(callback, parameters_, uri, body, payload)); + oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload, context_)); + } + + void Start() + { + oracle_.Start(); + } + + void Stop() + { + oracle_.Stop(); } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/WebServiceCommandBase.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "WebServiceCommandBase.h" + +#include <Core/HttpClient.h> + +namespace OrthancStone +{ + WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker, + IWebService::ICallback& callback, + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context) : + IObservable(broker), + callback_(callback), + parameters_(parameters), + uri_(uri), + headers_(headers), + payload_(payload), + context_(context) + { + DeclareEmittableMessage(MessageType_HttpRequestError); + DeclareEmittableMessage(MessageType_HttpRequestSuccess); + RegisterObserver(callback); + } + + + void WebServiceCommandBase::Commit() + { + BasicNativeApplicationContext::GlobalMutexLocker lock(context_); // we want to make sure that, i.e, the UpdateThread is not triggered while we are updating the "model" with the result of a WebServiceCommand + + if (success_) + { + IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release()); + EmitMessage(message); + } + else + { + IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release()); + EmitMessage(message); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/WebServiceCommandBase.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,61 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IOracleCommand.h" + +#include "../../Framework/Toolbox/IWebService.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Applications/Generic/BasicNativeApplicationContext.h" + +#include <Core/WebServiceParameters.h> + +#include <memory> + +namespace OrthancStone +{ + class WebServiceCommandBase : public IOracleCommand, IObservable + { + protected: + IWebService::ICallback& callback_; + Orthanc::WebServiceParameters parameters_; + std::string uri_; + std::map<std::string, std::string> headers_; + std::auto_ptr<Orthanc::IDynamicObject> payload_; + bool success_; + std::string answer_; + BasicNativeApplicationContext& context_; + + public: + WebServiceCommandBase(MessageBroker& broker, + IWebService::ICallback& callback, + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const std::map<std::string, std::string>& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context); + + virtual void Execute() = 0; + + virtual void Commit(); + }; +}
--- a/Platforms/Generic/WebServiceGetCommand.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -25,14 +25,14 @@ namespace OrthancStone { - WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback, + WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, - Orthanc::IDynamicObject* payload /* takes ownership */) : - callback_(callback), - parameters_(parameters), - uri_(uri), - payload_(payload) + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context) : + WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context) { } @@ -42,19 +42,13 @@ Orthanc::HttpClient client(parameters_, uri_); client.SetTimeout(60); client.SetMethod(Orthanc::HttpMethod_Get); + + for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + success_ = client.Apply(answer_); } - - void WebServiceGetCommand::Commit() - { - if (success_) - { - callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release()); - } - else - { - callback_.NotifyError(uri_, payload_.release()); - } - } }
--- a/Platforms/Generic/WebServiceGetCommand.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.h Thu Aug 30 16:56:08 2018 +0200 @@ -21,34 +21,21 @@ #pragma once -#include "IOracleCommand.h" - -#include "../../Framework/Toolbox/IWebService.h" - -#include <Core/WebServiceParameters.h> - -#include <memory> +#include "WebServiceCommandBase.h" namespace OrthancStone { - class WebServiceGetCommand : public IOracleCommand + class WebServiceGetCommand : public WebServiceCommandBase { - private: - IWebService::ICallback& callback_; - Orthanc::WebServiceParameters parameters_; - std::string uri_; - std::auto_ptr<Orthanc::IDynamicObject> payload_; - bool success_; - std::string answer_; - public: - WebServiceGetCommand(IWebService::ICallback& callback, + WebServiceGetCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, - Orthanc::IDynamicObject* payload /* takes ownership */); + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context); virtual void Execute(); - - virtual void Commit(); }; }
--- a/Platforms/Generic/WebServicePostCommand.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/WebServicePostCommand.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -25,16 +25,16 @@ namespace OrthancStone { - WebServicePostCommand::WebServicePostCommand(IWebService::ICallback& callback, + WebServicePostCommand::WebServicePostCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, const std::string& body, - Orthanc::IDynamicObject* payload /* takes ownership */) : - callback_(callback), - parameters_(parameters), - uri_(uri), - body_(body), - payload_(payload) + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context) : + WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context), + body_(body) { } @@ -44,18 +44,13 @@ client.SetTimeout(60); client.SetMethod(Orthanc::HttpMethod_Post); client.GetBody().swap(body_); + + for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + success_ = client.Apply(answer_); } - void WebServicePostCommand::Commit() - { - if (success_) - { - callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release()); - } - else - { - callback_.NotifyError(uri_, payload_.release()); - } - } }
--- a/Platforms/Generic/WebServicePostCommand.h Tue Aug 28 21:00:35 2018 +0200 +++ b/Platforms/Generic/WebServicePostCommand.h Thu Aug 30 16:56:08 2018 +0200 @@ -21,36 +21,25 @@ #pragma once -#include "IOracleCommand.h" - -#include "../../Framework/Toolbox/IWebService.h" - -#include <Core/WebServiceParameters.h> - -#include <memory> +#include "WebServiceCommandBase.h" namespace OrthancStone { - class WebServicePostCommand : public IOracleCommand + class WebServicePostCommand : public WebServiceCommandBase { - private: - IWebService::ICallback& callback_; - Orthanc::WebServiceParameters parameters_; - std::string uri_; + protected: std::string body_; - std::auto_ptr<Orthanc::IDynamicObject> payload_; - bool success_; - std::string answer_; public: - WebServicePostCommand(IWebService::ICallback& callback, + WebServicePostCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, const std::string& body, - Orthanc::IDynamicObject* payload /* takes ownership */); + Orthanc::IDynamicObject* payload /* takes ownership */, + BasicNativeApplicationContext& context); virtual void Execute(); - - virtual void Commit(); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/CMakeLists.txt Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,88 @@ +# Usage (Linux): +# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. + +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1") +set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + +# Handling of memory +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize + +# To debug exceptions +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ORTHANC_SANDBOXED ON) +SET(ENABLE_QT OFF) +SET(ENABLE_SDL OFF) +add_definitions(-DORTHANC_ENABLE_WASM=1) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) + + + +# Regenerate a dummy "WasmWebService.c" file each time the "WasmWebService.js" file +# is modified, so as to force a new execution of the linking +add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/WasmWebService.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmWebService.c" "" + DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.js") + +add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/default-library.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/default-library.c" "" + DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js") + + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + + +macro(BuildSample Target Header Sample) + add_executable(${Target} + ${STONE_WASM_SOURCES} + + ${AUTOGENERATED_DIR}/WasmWebService.c + ${AUTOGENERATED_DIR}/default-library.c + + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp +# ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) +endmacro() + +#BuildSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2) +#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +#BuildSample(OrthancStoneBasicPetCtFusion 5) +#BuildSample(OrthancStoneSynchronizedSeries 6) +#BuildSample(OrthancStoneLayoutPetCtFusion 7) +BuildSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,275 @@ +#include "Defaults.h" + +#include "WasmWebService.h" +#include <Framework/dev.h> +#include "Framework/Widgets/TestCairoWidget.h" +#include <Framework/Viewport/WidgetViewport.h> +#include <Framework/Widgets/LayerWidget.h> +#include <algorithm> +#include "Applications/Wasm/StartupParametersBuilder.h" +#include "Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h" + +static unsigned int width_ = 0; +static unsigned int height_ = 0; + +/**********************************/ + +static std::unique_ptr<OrthancStone::IStoneApplication> application; +static OrthancStone::IStoneApplicationToWebApplicationAdapter* applicationWebAdapter = NULL; +static std::unique_ptr<OrthancStone::StoneApplicationContext> context; +static OrthancStone::StartupParametersBuilder startupParametersBuilder; +static OrthancStone::MessageBroker broker; + +static OrthancStone::ViewportContentChangedObserver viewportContentChangedObserver_; +static OrthancStone::StatusBar statusBar_; + +static std::list<std::shared_ptr<OrthancStone::WidgetViewport>> viewports_; + +std::shared_ptr<OrthancStone::WidgetViewport> FindViewportSharedPtr(ViewportHandle viewport) { + for (const auto& v : viewports_) { + if (v.get() == viewport) { + return v; + } + } + assert(false); + return std::shared_ptr<OrthancStone::WidgetViewport>(); +} + +#ifdef __cplusplus +extern "C" { +#endif + + using namespace OrthancStone; + + // when WASM needs a C++ viewport + ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() { + + std::shared_ptr<OrthancStone::WidgetViewport> viewport(new OrthancStone::WidgetViewport); + printf("viewport %x\n", (int)viewport.get()); + + viewports_.push_back(viewport); + + printf("There are now %d viewports in C++\n", viewports_.size()); + + viewport->SetStatusBar(statusBar_); + viewport->Register(viewportContentChangedObserver_); + + return viewport.get(); + } + + // when WASM does not need a viewport anymore, it should release it + void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { + viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;}); + + printf("There are now %d viewports in C++\n", viewports_.size()); + } + + void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { + + printf("CreateWasmApplication\n"); + + application.reset(CreateUserApplication(broker)); + applicationWebAdapter = dynamic_cast<OrthancStone::IStoneApplicationToWebApplicationAdapter*>(application.get()); + WasmWebService::SetBroker(broker); + + startupParametersBuilder.Clear(); + } + + void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, + const char* value) { + startupParametersBuilder.SetStartupParameter(keyc, value); + } + + void EMSCRIPTEN_KEEPALIVE StartWasmApplication() { + + printf("StartWasmApplication\n"); + + // recreate a command line from uri arguments and parse it + boost::program_options::variables_map parameters; + boost::program_options::options_description options; + application->DeclareStartupOptions(options); + startupParametersBuilder.GetStartupParameters(parameters, options); + + context.reset(new OrthancStone::StoneApplicationContext()); + context->SetWebService(OrthancStone::WasmWebService::GetInstance()); + application->Initialize(context.get(), statusBar_, parameters); + application->InitializeWasm(); + +// viewport->SetSize(width_, height_); + printf("StartWasmApplication - completed\n"); + } + + void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent() + { + for (auto viewport : viewports_) { + // TODO Only launch the JavaScript timer if "HasUpdateContent()" + if (viewport->HasUpdateContent()) + { + viewport->UpdateContent(); + } + + } + + } + + + void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height) + { + width_ = width; + height_ = height; + + viewport->SetSize(width, height); + } + + int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport, + unsigned int width, + unsigned int height, + uint8_t* data) + { + viewportContentChangedObserver_.Reset(); + + //printf("ViewportRender called %dx%d\n", width, height); + if (width == 0 || + height == 0) + { + return 1; + } + + Orthanc::ImageAccessor surface; + surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data); + + viewport->Render(surface); + + // Convert from BGRA32 memory layout (only color mode supported by + // Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 (as + // expected by HTML5 canvas). This simply amounts to swapping the + // B and R channels. + uint8_t* p = data; + for (unsigned int y = 0; y < height; y++) { + for (unsigned int x = 0; x < width; x++) { + uint8_t tmp = p[0]; + p[0] = p[2]; + p[2] = tmp; + + p += 4; + } + } + + return 1; + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport, + unsigned int rawButton, + int x, + int y, + unsigned int rawModifiers) + { + OrthancStone::MouseButton button; + switch (rawButton) + { + case 0: + button = OrthancStone::MouseButton_Left; + break; + + case 1: + button = OrthancStone::MouseButton_Middle; + break; + + case 2: + button = OrthancStone::MouseButton_Right; + break; + + default: + return; // Unknown button + } + + viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport, + int deltaY, + int x, + int y, + int isControl) + { + if (deltaY != 0) + { + OrthancStone::MouseWheelDirection direction = (deltaY < 0 ? + OrthancStone::MouseWheelDirection_Up : + OrthancStone::MouseWheelDirection_Down); + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + + if (isControl != 0) + { + modifiers = OrthancStone::KeyboardModifiers_Control; + } + + viewport->MouseWheel(direction, x, y, modifiers); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport, + int x, + int y) + { + viewport->MouseMove(x, y); + } + + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, + const char* key, + bool isShiftPressed, + bool isControlPressed, + bool isAltPressed) + + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (isShiftPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Shift); + } + if (isControlPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Control); + } + if (isAltPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Alt); + } + printf("key pressed : %c\n", key[0]); + viewport->KeyPressed(key[0], modifiers); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport) + { + viewport->MouseUp(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport) + { + viewport->MouseEnter(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport) + { + viewport->MouseLeave(); + } + + const char* EMSCRIPTEN_KEEPALIVE SendMessageToStoneApplication(const char* message) + { + static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) + + if (applicationWebAdapter != NULL) { + printf("sending message to C++"); + applicationWebAdapter->HandleMessageFromWeb(output, std::string(message)); + return output.c_str(); + } + return "This stone application does not have a Web Adapter"; + } + + +#ifdef __cplusplus +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,76 @@ +#pragma once + +#include <emscripten/emscripten.h> + +#include <Framework/dev.h> +#include <Framework/Viewport/WidgetViewport.h> +#include <Framework/Widgets/LayerWidget.h> +#include <Framework/Widgets/LayoutWidget.h> +#include <Applications/IStoneApplication.h> + +typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++ + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); + extern void UpdateStoneApplicationStatusFromCpp(const char* statusUpdateMessage); + + // C++ methods accessible from JS + extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle); + extern void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, const char* value); + + +#ifdef __cplusplus +} +#endif + +extern OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker); + +namespace OrthancStone { + + // default Observer to trigger Viewport redraw when something changes in the Viewport + class ViewportContentChangedObserver : + public OrthancStone::IViewport::IObserver + { + private: + // Flag to avoid flooding JavaScript with redundant Redraw requests + bool isScheduled_; + + public: + ViewportContentChangedObserver() : + isScheduled_(false) + { + } + + void Reset() + { + isScheduled_ = false; + } + + virtual void OnViewportContentChanged(const OrthancStone::IViewport &viewport) + { + if (!isScheduled_) + { + ScheduleWebViewportRedrawFromCpp((ViewportHandle)&viewport); // loosing constness when transmitted to Web + isScheduled_ = true; + } + } + }; + + // default status bar to log messages on the console/stdout + class StatusBar : public OrthancStone::IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + printf("%s\n", message.c_str()); + } + }; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,13 @@ +#pragma once + +#include <string> + +namespace OrthancStone +{ + class IStoneApplicationToWebApplicationAdapter + { + public: + virtual void HandleMessageFromWeb(std::string& output, const std::string& input) = 0; + virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) = 0; + }; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmViewport.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,13 @@ +#include "WasmViewport.h" + +#include <vector> +#include <memory> + +std::vector<std::shared_ptr<OrthancStone::WidgetViewport>> wasmViewports; + +void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget) { + std::shared_ptr<OrthancStone::WidgetViewport> viewport(CreateWasmViewportFromCpp(htmlCanvasId)); + viewport->SetCentralWidget(centralWidget); + + wasmViewports.push_back(viewport); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmViewport.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,18 @@ +#pragma once + +#include <Framework/Viewport/WidgetViewport.h> + +#include <emscripten/emscripten.h> + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern OrthancStone::WidgetViewport* CreateWasmViewportFromCpp(const char* htmlCanvasId); + +#ifdef __cplusplus +} +#endif + +extern void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,122 @@ +#include "WasmWebService.h" +#include "json/value.h" +#include "json/writer.h" +#include <emscripten/emscripten.h> + +#ifdef __cplusplus +extern "C" { +#endif + + extern void WasmWebService_ScheduleGetRequest(void* callback, + const char* uri, + const char* headersInJsonString, + void* payload); + + extern void WasmWebService_SchedulePostRequest(void* callback, + const char* uri, + const char* headersInJsonString, + const void* body, + size_t bodySize, + void* payload); + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback, + const char* uri, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> + OnHttpRequestError(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback, + const char* uri, + const void* body, + size_t bodySize, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> + OnHttpRequestSuccess(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_SetBaseUri(const char* baseUri) + { + OrthancStone::WasmWebService::GetInstance().SetBaseUri(baseUri); + } + +#ifdef __cplusplus +} +#endif + + + +namespace OrthancStone +{ + MessageBroker* WasmWebService::broker_ = NULL; + + void WasmWebService::SetBaseUri(const std::string baseUri) + { + // Make sure the base url ends with "/" + if (baseUri.empty() || + baseUri[baseUri.size() - 1] != '/') + { + baseUri_ = baseUri + "/"; + } + else + { + baseUri_ = baseUri; + } + } + + void ToJsonString(std::string& output, const IWebService::Headers& headers) + { + Json::Value jsonHeaders; + for (IWebService::Headers::const_iterator it = headers.begin(); it != headers.end(); it++ ) + { + jsonHeaders[it->first] = it->second; + } + + Json::StreamWriterBuilder builder; + std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); + std::ostringstream outputStr; + + writer->write(jsonHeaders, &outputStr); + output = outputStr.str(); + } + + void WasmWebService::ScheduleGetRequest(ICallback& callback, + const std::string& relativeUri, + const Headers& headers, + Orthanc::IDynamicObject* payload) + { + std::string uri = baseUri_ + relativeUri; + std::string headersInJsonString; + ToJsonString(headersInJsonString, headers); + WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), headersInJsonString.c_str(), payload); + } + + void WasmWebService::SchedulePostRequest(ICallback& callback, + const std::string& relativeUri, + const Headers& headers, + const std::string& body, + Orthanc::IDynamicObject* payload) + { + std::string uri = baseUri_ + relativeUri; + std::string headersInJsonString; + ToJsonString(headersInJsonString, headers); + WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(), + body.c_str(), body.size(), payload); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.h Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,59 @@ +#pragma once + +#include <Framework/Toolbox/IWebService.h> +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class WasmWebService : public IWebService + { + private: + std::string baseUri_; + static MessageBroker* broker_; + + // Private constructor => Singleton design pattern + WasmWebService(MessageBroker& broker) : + IWebService(broker), + baseUri_("../../") // note: this is configurable from the JS code by calling WasmWebService_SetBaseUri + { + } + + public: + static WasmWebService& GetInstance() + { + if (broker_ == NULL) + { + printf("WasmWebService::GetInstance(): broker not initialized\n"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + static WasmWebService instance(*broker_); + return instance; + } + + static void SetBroker(MessageBroker& broker) + { + broker_ = &broker; + } + + void SetBaseUri(const std::string baseUri); + + virtual void ScheduleGetRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + Orthanc::IDynamicObject* payload); + + virtual void SchedulePostRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + const std::string& body, + Orthanc::IDynamicObject* payload); + + virtual void Start() + { + } + + virtual void Stop() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.js Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,59 @@ +mergeInto(LibraryManager.library, { + WasmWebService_ScheduleGetRequest: function(callback, url, headersInJsonString, payload) { + // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data + // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ + var xhr = new XMLHttpRequest(); + var url_ = UTF8ToString(url); + var headersInJsonString_ = UTF8ToString(headersInJsonString); + + xhr.open('GET', url_, true); + xhr.responseType = 'arraybuffer'; + var headers = JSON.parse(headersInJsonString_); + for (var key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + // console.log(xhr); + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + // TODO - Is "new Uint8Array()" necessary? This copies the + // answer to the WebAssembly stack, hence necessitating + // increasing the TOTAL_STACK parameter of Emscripten + WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, url_, payload); + } + } + } + + xhr.send(); + }, + + WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) { + var xhr = new XMLHttpRequest(); + var url_ = UTF8ToString(url); + var headersInJsonString_ = UTF8ToString(headersInJsonString); + xhr.open('POST', url_, true); + xhr.responseType = 'arraybuffer'; + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + + var headers = JSON.parse(headersInJsonString_); + for (var key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, url_, payload); + } + } + } + + xhr.send(new Uint8ClampedArray(HEAPU8.buffer, body, bodySize)); + } +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/build-wasm.sh Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,15 @@ +#!/bin/bash + +currentDir=$(pwd) +wasmRootDir=$(pwd) + +mkdir -p $wasmRootDir/build +cd $wasmRootDir/build + +source ~/Downloads/emsdk/emsdk_env.sh +cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. +make -j 5 + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/build-web.sh Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,24 @@ +#!/bin/bash + +# this script currently assumes that the wasm code has been built on its side and is availabie in Wasm/build/ + +currentDir=$(pwd) +wasmRootDir=$(pwd) +samplesRootDir=$(pwd)/../../Applications/Samples/ + +outputDir=$wasmRootDir/build-web/ +mkdir -p $outputDir + +cp $samplesRootDir/Web/index.html $outputDir +cp $samplesRootDir/Web/samples-styles.css $outputDir + +cp $samplesRootDir/Web/simple-viewer.html $outputDir +tsc --allowJs --project $samplesRootDir/Web/tsconfig-simple-viewer.json +cp $currentDir/build/OrthancStoneSimpleViewer.js $outputDir +cp $currentDir/build/OrthancStoneSimpleViewer.wasm $outputDir + + +# cat ../wasm/build/wasm-app.js $currentDir/../../../orthanc-stone/Platforms/WebAssembly/defaults.js > $outputDir/app.js + +cd $currentDir +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/default-library.js Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,16 @@ +// this file contains the JS method you want to expose to C++ code + +mergeInto(LibraryManager.library, { + ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { + ScheduleWebViewportRedraw(cppViewportHandle); + }, + CreateWasmViewportFromCpp: function(htmlCanvasId) { + return CreateWasmViewport(htmlCanvasId); + }, + // each time the StoneApplication updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface + UpdateStoneApplicationStatusFromCpp: function(statusUpdateMessage) { + var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); + UpdateWebApplication(statusUpdateMessage_); + } +}); + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/nginx.local.conf Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,44 @@ +# Local config to serve the WASM samples static files and reverse proxy Orthanc. +# Uses port 9977 instead of 80. + +# `events` section is mandatory +events { + worker_connections 1024; # Default: 1024 +} + +http { + + # prevent nginx sync issues on OSX + proxy_buffering off; + + server { + listen 9977 default_server; + client_max_body_size 4G; + + # location may have to be adjusted depending on your OS and nginx install + include /etc/nginx/mime.types; + # if not in your system mime.types, add this line to support WASM: + # types { + # application/wasm wasm; + # } + + # serve WASM static files + root build-web/; + location / { + } + + # reverse proxy orthanc + location /orthanc/ { + rewrite /orthanc(.*) $1 break; + proxy_pass http://127.0.0.1:8042; + proxy_set_header Host $http_host; + proxy_set_header my-auth-header good-token; + proxy_request_buffering off; + proxy_max_temp_file_size 0; + client_max_body_size 0; + } + + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/stone-framework-loader.ts Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,96 @@ +module Stone { + /** + * This file contains primitives to interface with WebAssembly and + * with the Stone framework. + **/ + + export declare type InitializationCallback = () => void; + + export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + + export class Framework + { + private static singleton_ : Framework = null; + private static wasmModuleName_ : string = null; + + public static Configure(wasmModuleName: string) { + this.wasmModuleName_ = wasmModuleName; + } + + private constructor(verbose : boolean) + { + //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); + } + + + public ccall(name: string, + returnType: string, + argTypes: Array<string>, + argValues: Array<any>) : any + { + return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); + } + + + public cwrap(name: string, + returnType: string, + argTypes: Array<string>) : any + { + return StoneFrameworkModule.cwrap(name, returnType, argTypes); + } + + + public static GetInstance() : Framework + { + if (Framework.singleton_ == null) { + throw new Error('The WebAssembly module is not loaded yet'); + } else { + return Framework.singleton_; + } + } + + + public static Initialize(verbose: boolean, + callback: InitializationCallback) + { + console.log('Initializing WebAssembly Module'); + + (<any> window).StoneFrameworkModule = { + preRun: [ + function() { + console.log('Loading the Stone Framework using WebAssembly'); + } + ], + postRun: [ + function() { + // This function is called by ".js" wrapper once the ".wasm" + // WebAssembly module has been loaded and compiled by the + // browser + console.log('WebAssembly is ready'); + Framework.singleton_ = new Framework(verbose); + callback(); + } + ], + print: function(text : string) { + console.log(text); + }, + printErr: function(text : string) { + console.error(text); + }, + totalDependencies: 0 + }; + + // Dynamic loading of the JavaScript wrapper around WebAssembly + var script = document.createElement('script'); + script.type = 'application/javascript'; + //script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.src = this.wasmModuleName_ + ".js";// "OrthancStoneSimpleViewer.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.async = true; + document.head.appendChild(script); + } + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/tsconfig-stone.json Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,7 @@ +{ + "include" : [ + "../../../Platforms/Wasm/stone-framework-loader.ts", + "../../../Platforms/Wasm/wasm-application-runner.ts", + "../../../Platforms/Wasm/wasm-viewport.ts" + ] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-application-runner.ts Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,112 @@ +///<reference path='stone-framework-loader.ts'/> +///<reference path='wasm-viewport.ts'/> + +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} + +declare var StoneFrameworkModule : Stone.Framework; + +// global functions +var WasmWebService_NotifyError: Function = null; +var WasmWebService_NotifySuccess: Function = null; +var WasmWebService_SetBaseUri: Function = null; +var NotifyUpdateContent: Function = null; +var SetStartupParameter: Function = null; +var CreateWasmApplication: Function = null; +var CreateCppViewport: Function = null; +var ReleaseCppViewport: Function = null; +var StartWasmApplication: Function = null; +var SendMessageToStoneApplication: Function = null; + + +function UpdateContentThread() { + if (NotifyUpdateContent != null) { + NotifyUpdateContent(); + } + + setTimeout(UpdateContentThread, 100); // Update the viewport content every 100ms if need be +} + + +function GetUriParameters() { + var parameters = window.location.search.substr(1); + + if (parameters != null && + parameters != '') { + var result = {}; + var tokens = parameters.split('&'); + + for (var i = 0; i < tokens.length; i++) { + var tmp = tokens[i].split('='); + if (tmp.length == 2) { + result[tmp[0]] = decodeURIComponent(tmp[1]); + } + } + + return result; + } + else { + return {}; + } +} + +// function UpdateWebApplication(statusUpdateMessage: string) { +// console.log(statusUpdateMessage); +// } + +function _InitializeWasmApplication(canvasId: string, orthancBaseUrl: string): void { + + /************************************** */ + CreateWasmApplication(); + WasmWebService_SetBaseUri(orthancBaseUrl); + + + // parse uri and transmit the parameters to the app before initializing it + var parameters = GetUriParameters(); + + for (var key in parameters) { + if (parameters.hasOwnProperty(key)) { + SetStartupParameter(key, parameters[key]); + } + } + + StartWasmApplication(); + /************************************** */ + + UpdateContentThread(); +} + +function InitializeWasmApplication(wasmModuleName: string, orthancBaseUrl: string) { + + Stone.Framework.Configure(wasmModuleName); + + // Wait for the Orthanc Framework to be initialized (this initializes + // the WebAssembly environment) and then, create and initialize the Wasm application + Stone.Framework.Initialize(true, function () { + + console.log("Connecting C++ methods to JS methods"); + + SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); + CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); + CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); + ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); + StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['number']); + + WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); + WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + WasmWebService_SetBaseUri = StoneFrameworkModule.cwrap('WasmWebService_SetBaseUri', null, ['string']); + NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []); + + SendMessageToStoneApplication = StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']); + + console.log("Connecting C++ methods to JS methods - done"); + + // Prevent scrolling + document.body.addEventListener('touchmove', function (event) { + event.preventDefault(); + }, false); + + _InitializeWasmApplication("canvas", orthancBaseUrl); + }); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-viewport.ts Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,272 @@ +var isPendingRedraw = false; + +function ScheduleWebViewportRedraw(cppViewportHandle: any) : void +{ + if (!isPendingRedraw) { + isPendingRedraw = true; + console.log('Scheduling a refresh of the viewport, as its content changed'); + window.requestAnimationFrame(function() { + isPendingRedraw = false; + Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + }); + } +} + +declare function UTF8ToString(any): string; + +function CreateWasmViewport(htmlCanvasId: string) : any { + var cppViewportHandle = CreateCppViewport(); + var canvasId = UTF8ToString(htmlCanvasId); + var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted + webViewport.Initialize(); + + return cppViewportHandle; +} + +module Stone { + +// export declare type InitializationCallback = () => void; + +// export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + export class WasmViewport { + + private static cppWebViewportsMaps_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); + + private module_ : any; + private canvasId_ : string; + private htmlCanvas_ : HTMLCanvasElement; + private context_ : CanvasRenderingContext2D; + private imageData_ : any = null; + private renderingBuffer_ : any = null; + private touchZoom_ : any = false; + private touchTranslation_ : any = false; + + private ViewportSetSize : Function; + private ViewportRender : Function; + private ViewportMouseDown : Function; + private ViewportMouseMove : Function; + private ViewportMouseUp : Function; + private ViewportMouseEnter : Function; + private ViewportMouseLeave : Function; + private ViewportMouseWheel : Function; + private ViewportKeyPressed : Function; + + private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object + + public constructor(module: any, canvasId: string, cppViewport: any) { + + this.pimpl_ = cppViewport; + WasmViewport.cppWebViewportsMaps_[this.pimpl_] = this; + + this.module_ = module; + this.canvasId_ = canvasId; + this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement; + if (this.htmlCanvas_ == null) { + console.log("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'"); + } + this.context_ = this.htmlCanvas_.getContext('2d'); + + this.ViewportSetSize = this.module_.cwrap('ViewportSetSize', null, [ 'number', 'number', 'number' ]); + this.ViewportRender = this.module_.cwrap('ViewportRender', null, [ 'number', 'number', 'number', 'number' ]); + this.ViewportMouseDown = this.module_.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportMouseMove = this.module_.cwrap('ViewportMouseMove', null, [ 'number', 'number', 'number' ]); + this.ViewportMouseUp = this.module_.cwrap('ViewportMouseUp', null, [ 'number' ]); + this.ViewportMouseEnter = this.module_.cwrap('ViewportMouseEnter', null, [ 'number' ]); + this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]); + this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'string', 'number', 'number' ]); + } + + public GetCppViewport() : number { + return this.pimpl_; + } + + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + if (WasmViewport.cppWebViewportsMaps_[cppViewportHandle] !== undefined) { + return WasmViewport.cppWebViewportsMaps_[cppViewportHandle]; + } + console.log("WasmViewport not found !"); + return undefined; + } + + public Redraw() { + if (this.imageData_ === null || + this.renderingBuffer_ === null || + this.ViewportRender(this.pimpl_, + this.imageData_.width, + this.imageData_.height, + this.renderingBuffer_) == 0) { + console.log('The rendering has failed'); + } else { + // Create an accessor to the rendering buffer (i.e. create a + // "window" above the heap of the WASM module), then copy it to + // the ImageData object + this.imageData_.data.set(new Uint8ClampedArray( + this.module_.buffer, + this.renderingBuffer_, + this.imageData_.width * this.imageData_.height * 4)); + + this.context_.putImageData(this.imageData_, 0, 0); + } + } + + public Resize() { + if (this.imageData_ != null && + (this.imageData_.width != window.innerWidth || + this.imageData_.height != window.innerHeight)) { + this.imageData_ = null; + } + + // width/height can be defined in percent of window width/height through html attributes like data-width-ratio="50" and data-height-ratio="20" + var widthRatio = Number(this.htmlCanvas_.dataset["widthRatio"]) || 100; + var heightRatio = Number(this.htmlCanvas_.dataset["heightRatio"]) || 100; + + this.htmlCanvas_.width = window.innerWidth * (widthRatio / 100); + this.htmlCanvas_.height = window.innerHeight * (heightRatio / 100); + + console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + + if (this.imageData_ === null) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } + + this.Redraw(); + } + + public Initialize() { + + // Force the rendering of the viewport for the first time + this.Resize(); + + var that : WasmViewport = this; + // Register an event listener to call the Resize() function + // each time the window is resized. + window.addEventListener('resize', function(event) { + that.Resize(); + }, false); + + this.htmlCanvas_.addEventListener('contextmenu', function(event) { + // Prevent right click on the canvas + event.preventDefault(); + }, false); + + this.htmlCanvas_.addEventListener('mouseleave', function(event) { + that.ViewportMouseLeave(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mouseenter', function(event) { + that.ViewportMouseEnter(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mousedown', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO */); + }); + + this.htmlCanvas_.addEventListener('mousemove', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseMove(that.pimpl_, x, y); + }); + + this.htmlCanvas_.addEventListener('mouseup', function(event) { + that.ViewportMouseUp(that.pimpl_); + }); + + window.addEventListener('keydown', function(event) { + that.ViewportKeyPressed(that.pimpl_, event.key, event.shiftKey, event.ctrlKey, event.altKey); + }); + + this.htmlCanvas_.addEventListener('wheel', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey); + event.preventDefault(); + }); + + this.htmlCanvas_.addEventListener('touchstart', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchend', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchmove', function(event) { + if (that.touchTranslation_.length == 2) { + var t = that.GetTouchTranslation(event); + that.ViewportMouseMove(that.pimpl_, t[0], t[1]); + } + else if (that.touchZoom_.length == 3) { + var z0 = that.touchZoom_; + var z1 = that.GetTouchZoom(event); + that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]); + } + else { + // Realize the gesture event + if (event.targetTouches.length == 1) { + // Exactly one finger inside the canvas => Setup a translation + that.touchTranslation_ = that.GetTouchTranslation(event); + that.ViewportMouseDown(that.pimpl_, + 1 /* middle button */, + that.touchTranslation_[0], + that.touchTranslation_[1], 0); + } else if (event.targetTouches.length == 2) { + // Exactly 2 fingers inside the canvas => Setup a pinch/zoom + that.touchZoom_ = that.GetTouchZoom(event); + var z0 = that.touchZoom_; + that.ViewportMouseDown(that.pimpl_, + 2 /* right button */, + z0[0], + z0[1], 0); + } + } + }); + } + + public ResetTouch() { + if (this.touchTranslation_ || + this.touchZoom_) { + this.ViewportMouseUp(this.pimpl_); + } + + this.touchTranslation_ = false; + this.touchZoom_ = false; + } + + public GetTouchTranslation(event) { + var touch = event.targetTouches[0]; + return [ + touch.pageX, + touch.pageY + ]; + } + + public GetTouchZoom(event) { + var touch1 = event.targetTouches[0]; + var touch2 = event.targetTouches[1]; + var dx = (touch1.pageX - touch2.pageX); + var dy = (touch1.pageY - touch2.pageY); + var d = Math.sqrt(dx * dx + dy * dy); + return [ + (touch1.pageX + touch2.pageX) / 2.0, + (touch1.pageY + touch2.pageY) / 2.0, + d + ]; + } + +} +} + \ No newline at end of file
--- a/Platforms/WebAssembly/CMakeLists.txt Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Usage (Linux): -# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. - -cmake_minimum_required(VERSION 2.8.3) - - -##################################################################### -## Configuration of the Emscripten compiler for WebAssembly target -##################################################################### - -set(WASM_FLAGS "-s WASM=1") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js") - -# Handling of memory -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912") # 512MB + resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize - -# To debug exceptions -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") - - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - -include(../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ORTHANC_SANDBOXED ON) -SET(ENABLE_SDL OFF) - -include(../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) - - - - - - -# Regenerate a dummy "library.c" file each time the "library.js" file -# is modified, so as to force a new execution of the linking -add_custom_command( - OUTPUT "${AUTOGENERATED_DIR}/library.c" - COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" "" - DEPENDS "${CMAKE_SOURCE_DIR}/library.js")
--- a/Platforms/WebAssembly/library.js Tue Aug 28 21:00:35 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -mergeInto(LibraryManager.library, { -});
--- a/README Tue Aug 28 21:00:35 2018 +0200 +++ b/README Thu Aug 30 16:56:08 2018 +0200 @@ -72,6 +72,10 @@ * Optionally, SDL, a cross-platform multimedia library: https://www.libsdl.org/ +Prerequisites to compile on Ubuntu: +``` +sudo apt-get install -y libcairo-dev libpixman-1-dev libsdl2-dev +``` Installation and usage ---------------------- @@ -83,7 +87,43 @@ http://book.orthanc-server.com/developers/stone.html Stone of Orthanc comes with several sample applications in the -"Samples" folder. These samples use SDL. +"Samples" folder. These samples can be compiled into Web Assembly +or into native SDL applications. + +to build the WASM samples: +------------------------- +``` +cd ~/orthanc-stone/Platforms/Wasm +./build-wasm.sh +``` + +to serve the WASM samples: +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch an nginx that will serve the WASM static files and reverse proxy Orthanc +sudo nginx -p $(pwd) -c nginx.local.conf +``` +Now, you can open the samples in http://localhost:9977 + +to build the SDL native samples (SimpleViewer only): +------------------------------- +``` +mkdir -p ~/builds/orthanc-stone-build +cd ~/builds/orthanc-stone-build +cmake -DALLOW_DOWNLOADS=ON ~/orthanc-stone/ +cmake --build . --target OrthancStoneSimpleViewer -- -j 5 +``` + +to execute the native samples: +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch the sample +./OrthancStoneSimpleViewer --instance1=XX --instance2=XX +``` Licensing
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Tue Aug 28 21:00:35 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Aug 30 16:56:08 2018 +0200 @@ -39,6 +39,10 @@ message(FATAL_ERROR "Cannot enable SDL in sandboxed environments") endif() + if (ENABLE_QT) + message(FATAL_ERROR "Cannot enable QT in sandboxed environments") + endif() + if (ENABLE_SSL) message(FATAL_ERROR "Cannot enable SSL in sandboxed environments") endif() @@ -69,12 +73,26 @@ endif() -if (ENABLE_SDL) - include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) +if (ENABLE_SDL AND ENABLE_QT) + message("SDL and QT may not be defined together") +elseif(ENABLE_SDL) + message("SDL is enabled") + include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_NATIVE=1) + add_definitions(-DORTHANC_ENABLE_QT=0) add_definitions(-DORTHANC_ENABLE_SDL=1) +elseif(ENABLE_QT) + message("QT is enabled") + include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_NATIVE=1) + add_definitions(-DORTHANC_ENABLE_QT=1) + add_definitions(-DORTHANC_ENABLE_SDL=0) else() + message("SDL and QT are both disabled") unset(USE_SYSTEM_SDL CACHE) add_definitions(-DORTHANC_ENABLE_SDL=0) + add_definitions(-DORTHANC_ENABLE_QT=0) + add_definitions(-DORTHANC_ENABLE_NATIVE=0) endif() @@ -93,7 +111,9 @@ -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) - +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DCHECK_OBSERVERS_MESSAGES) +endif() ##################################################################### ## Embed the colormaps into the binaries @@ -141,21 +161,46 @@ ## All the source files required to build Stone of Orthanc ##################################################################### +set(APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h + ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp + ) + if (NOT ORTHANC_SANDBOXED) set(PLATFORM_SOURCES + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.h ) - set(APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/BasicApplicationContext.cpp - ${ORTHANC_STONE_ROOT}/Applications/IBasicApplication.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp + if (ENABLE_SDL OR ENABLE_QT) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Generic/BasicNativeApplicationRunner.cpp + ${ORTHANC_STONE_ROOT}/Applications/Generic/BasicNativeApplicationContext.cpp + ) + if (ENABLE_SDL) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp + ) + endif() + endif() +else() + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp ) + + set(STONE_WASM_SOURCES + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmViewport.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h + ) endif() list(APPEND ORTHANC_STONE_SOURCES @@ -163,11 +208,13 @@ #${ORTHANC_STONE_ROOT}/Framework/Layers/SiblingSliceLocationFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp + ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomStructureSetRendererFactory.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/FrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/GrayscaleFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Layers/ILayerSource.h ${ORTHANC_STONE_ROOT}/Framework/Layers/LayerSourceBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp @@ -182,10 +229,12 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IWebService.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp @@ -195,6 +244,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/SlicedVolumeBase.cpp @@ -203,6 +253,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/CairoWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/EmptyWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayerWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp @@ -210,6 +261,12 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h + ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp @@ -227,5 +284,7 @@ # Optional components ${SDL_SOURCES} + ${QT_SOURCES} ${BOOST_EXTENDED_SOURCES} ) +
--- a/Resources/CMake/OrthancStoneParameters.cmake Tue Aug 28 21:00:35 2018 +0200 +++ b/Resources/CMake/OrthancStoneParameters.cmake Thu Aug 30 16:56:08 2018 +0200 @@ -50,3 +50,4 @@ ##################################################################### set(ENABLE_SDL ON CACHE INTERNAL "Include support for SDL") +set(ENABLE_QT OFF CACHE INTERNAL "Include support for Qt")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/QtConfiguration.cmake Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,40 @@ +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) +SET(CMAKE_AUTOUIC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Find the QtWidgets library +find_package(Qt5Widgets) +find_package(Qt5Core) + +list(APPEND QT_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.cpp + ${ORTHANC_STONE_ROOT}/Applications/Qt/BasicQtApplicationRunner.cpp + ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.cpp +) + +include_directories(${ORTHANC_STONE_ROOT}/Applications/Qt/) + +link_libraries( + Qt5::Widgets + Qt5::Core +)
--- a/TODO Tue Aug 28 21:00:35 2018 +0200 +++ b/TODO Thu Aug 30 16:56:08 2018 +0200 @@ -26,7 +26,6 @@ ------------- * Tune number of loading threads in LayeredSceneWidget -* Add cache over IOrthancServices (for SDL/Qt/...) * LayoutWidget: Do not update full background if only 1 widget has changed * LayoutWidget: Threads to refresh each child * Implement binary search to speed up search for closest slice @@ -38,7 +37,6 @@ Platform-specific ----------------- -* Qt widget example * Add precompiled headers for Microsoft Visual Studio * Investigate crash in CurlOrthancConnection if using MinGW32 in Release mode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/TestMessageBroker.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -0,0 +1,158 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "gtest/gtest.h" + +#include "../Framework/Messages/MessageBroker.h" +#include "../Framework/Messages/IMessage.h" +#include "../Framework/Messages/IObservable.h" +#include "../Framework/Messages/IObserver.h" +#include "../Framework/StoneEnumerations.h" + + +static int test1Counter = 0; +static int test2Counter = 0; +class MyFullObserver : public OrthancStone::IObserver +{ + +public: + MyFullObserver(OrthancStone::MessageBroker& broker) + : OrthancStone::IObserver(broker) + { + DeclareHandledMessage(OrthancStone::MessageType_Test1); + DeclareIgnoredMessage(OrthancStone::MessageType_Test2); + } + + + void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { + switch (message.GetType()) + { + case OrthancStone::MessageType_Test1: + test1Counter++; + break; + case OrthancStone::MessageType_Test2: + test2Counter++; + break; + default: + throw OrthancStone::MessageNotDeclaredException(message.GetType()); + } + } + +}; + +class MyPartialObserver : public OrthancStone::IObserver +{ + +public: + MyPartialObserver(OrthancStone::MessageBroker& broker) + : OrthancStone::IObserver(broker) + { + DeclareHandledMessage(OrthancStone::MessageType_Test1); + // don't declare Test2 on purpose + } + + + void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { + switch (message.GetType()) + { + case OrthancStone::MessageType_Test1: + test1Counter++; + break; + case OrthancStone::MessageType_Test2: + test2Counter++; + break; + default: + throw OrthancStone::MessageNotDeclaredException(message.GetType()); + } + } + +}; + + +class MyObservable : public OrthancStone::IObservable +{ + +public: + MyObservable(OrthancStone::MessageBroker& broker) + : OrthancStone::IObservable(broker) + { + DeclareEmittableMessage(OrthancStone::MessageType_Test1); + DeclareEmittableMessage(OrthancStone::MessageType_Test2); + } + +}; + + +TEST(MessageBroker, NormalUsage) +{ + OrthancStone::MessageBroker broker; + MyObservable observable(broker); + + test1Counter = 0; + + // no observers have been registered -> nothing shall happen + observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); + + ASSERT_EQ(0, test1Counter); + + // register an observer, check it is called + MyFullObserver fullObserver(broker); + ASSERT_NO_THROW(observable.RegisterObserver(fullObserver)); + + observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); + + ASSERT_EQ(1, test1Counter); + + // register an invalid observer, check it raises an exception + MyPartialObserver partialObserver(broker); + ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException); + + // check an exception is thrown when the observable emits an undeclared message + ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException); + + // unregister the observer, make sure nothing happens afterwards + observable.UnregisterObserver(fullObserver); + observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); + ASSERT_EQ(1, test1Counter); +} + +TEST(MessageBroker, DeleteObserverWhileRegistered) +{ + OrthancStone::MessageBroker broker; + MyObservable observable(broker); + + test1Counter = 0; + + { + // register an observer, check it is called + MyFullObserver observer(broker); + observable.RegisterObserver(observer); + + observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); + + ASSERT_EQ(1, test1Counter); + } + + // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !) + observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); + + ASSERT_EQ(1, test1Counter); +}
--- a/UnitTestsSources/UnitTestsMain.cpp Tue Aug 28 21:00:35 2018 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Thu Aug 30 16:56:08 2018 +0200 @@ -55,7 +55,7 @@ for (size_t i = 0; i < loader.GetSliceCount(); i++) { - const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_Full); + const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng); } }