# HG changeset patch # User Alain Mazy # Date 1563542113 -7200 # Node ID d6c029d15aaa88036a5a800760a64c0d374c0b92 # Parent 4d1f57773b5be15414885de20d9ada99c0392052# Parent 878763ce66afefa6082b127328d4cec7b9019c5d Merged am-dev into default diff -r 4d1f57773b5b -r d6c029d15aaa .hgignore diff -r 4d1f57773b5b -r d6c029d15aaa Applications/Generic/GuiAdapter.cpp --- a/Applications/Generic/GuiAdapter.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Applications/Generic/GuiAdapter.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -41,14 +41,14 @@ widgets_.push_back(widget); } - std::ostream& operator<<( - std::ostream& os, const GuiAdapterKeyboardEvent& event) - { - os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " << - "shift: " << event.shiftKey << ", " << - "alt: " << event.altKey; - return os; - } + std::ostream& operator<<( + std::ostream& os, const GuiAdapterKeyboardEvent& event) + { + os << "sym: " << event.sym << " (" << (int)(event.sym[0]) << ") ctrl: " << event.ctrlKey << ", " << + "shift: " << event.shiftKey << ", " << + "alt: " << event.altKey; + return os; + } #if ORTHANC_ENABLE_WASM == 1 void GuiAdapter::Run() @@ -428,15 +428,15 @@ switch (source.button.button) { case SDL_BUTTON_MIDDLE: - dest.button = 1; + dest.button =GUIADAPTER_MOUSEBUTTON_MIDDLE; break; case SDL_BUTTON_RIGHT: - dest.button = 2; + dest.button = GUIADAPTER_MOUSEBUTTON_RIGHT; break; case SDL_BUTTON_LEFT: - dest.button = 0; + dest.button = GUIADAPTER_MOUSEBUTTON_LEFT; break; default: @@ -573,14 +573,14 @@ // the SDL window name IS the canvas name ("canvas" is used because this lib // is designed for Wasm - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.mouse.type) { @@ -604,14 +604,14 @@ ORTHANC_ASSERT(event.sym[0] != 0); ORTHANC_ASSERT(event.sym[1] == 0); - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.type) { @@ -636,23 +636,23 @@ // SDL ONLY void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event) { - if (windowID == 0) - { - LOG(WARNING) << "GuiAdapter::OnMouseEvent -- windowID == 0 and event won't be routed!"; - } - else - { + if (windowID == 0) + { + LOG(WARNING) << "GuiAdapter::OnMouseEvent -- windowID == 0 and event won't be routed!"; + } + else + { // the SDL window name IS the canvas name ("canvas" is used because this lib // is designed for Wasm - SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); - - ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); - ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); std::string windowTitle(windowTitleSz); - ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); switch (event.type) { @@ -692,7 +692,7 @@ //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!"); //if(foundWidget) // foundWidget-> - } + } } // SDL ONLY @@ -791,8 +791,8 @@ } #endif } - else if (event.type == SDL_MOUSEWHEEL) - { + else if (event.type == SDL_MOUSEWHEEL) + { int scancodeCount = 0; const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); @@ -813,21 +813,21 @@ GuiAdapterWheelEvent dest; ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event); - OnMouseWheelEvent(event.window.windowID, dest); - - //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); - - //int x, y; - //SDL_GetMouseState(&x, &y); - - //if (event.wheel.y > 0) - //{ - // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); - //} - //else if (event.wheel.y < 0) - //{ - // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); - //} + OnMouseWheelEvent(event.window.windowID, dest); + + //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + //int x, y; + //SDL_GetMouseState(&x, &y); + + //if (event.wheel.y > 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + //} + //else if (event.wheel.y < 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + //} } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { diff -r 4d1f57773b5b -r d6c029d15aaa Applications/Generic/GuiAdapter.h --- a/Applications/Generic/GuiAdapter.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Applications/Generic/GuiAdapter.h Fri Jul 19 15:15:13 2019 +0200 @@ -67,6 +67,14 @@ }; + enum GuiAdapterMouseButtonType + { + GUIADAPTER_MOUSEBUTTON_LEFT = 0, + GUIADAPTER_MOUSEBUTTON_MIDDLE = 1, + GUIADAPTER_MOUSEBUTTON_RIGHT = 2 + }; + + enum GuiAdapterHidEventType { GUIADAPTER_EVENT_MOUSEDOWN = 1973, @@ -140,6 +148,14 @@ //long canvasX; //long canvasY; //long padding; + + public: + GuiAdapterMouseEvent() + : ctrlKey(false), + shiftKey(false), + altKey(false) + { + } }; struct GuiAdapterWheelEvent { @@ -162,7 +178,7 @@ bool altKey; }; - std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); + std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); /* Mousedown event trigger when either the left or right (or middle) mouse is pressed diff -r 4d1f57773b5b -r d6c029d15aaa Applications/Generic/Scene2DInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/Scene2DInteractor.h Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once + +#include "../../Framework/Scene2D/PointerEvent.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +//#include "../../Framework/Scene2D/Internals/CompositorHelper.h" +#include "GuiAdapter.h" + + +namespace OrthancStone +{ + + class Scene2DInteractor + { + protected: + boost::shared_ptr viewportController_; +// boost::shared_ptr compositor_; + + public: + Scene2DInteractor(boost::shared_ptr viewportController) : + viewportController_(viewportController) + {} + +// void SetCompositor(boost::shared_ptr compositor) +// { +// compositor_ = compositor; +// } + + virtual bool OnMouseEvent(const GuiAdapterMouseEvent& guiEvent, const PointerEvent& pointerEvent) = 0; // returns true if it has handled the event + virtual bool OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) = 0; // returns true if it has handled the event + virtual bool OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) = 0; // returns true if it has handled the event + + }; +} diff -r 4d1f57773b5b -r d6c029d15aaa Applications/Qt/QCairoWidget.h --- a/Applications/Qt/QCairoWidget.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Applications/Qt/QCairoWidget.h Fri Jul 19 15:15:13 2019 +0200 @@ -21,8 +21,8 @@ #pragma once #include "../../Applications/Generic/NativeStoneApplicationContext.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Widgets/IWidget.h" +#include "../../Framework/Wrappers/CairoSurface.h" +#include "../../Framework/Deprecated/Widgets/IWidget.h" #include #include diff -r 4d1f57773b5b -r d6c029d15aaa Applications/Qt/QtStoneApplicationRunner.cpp --- a/Applications/Qt/QtStoneApplicationRunner.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Applications/Qt/QtStoneApplicationRunner.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -27,7 +27,7 @@ #include #include -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include #include diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Deprecated/Toolbox/BaseWebService.cpp --- a/Framework/Deprecated/Toolbox/BaseWebService.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -27,6 +27,8 @@ #include #include +#include +#include namespace Deprecated { @@ -89,7 +91,7 @@ OrthancStone::MessageHandler* failureCallback, unsigned int timeoutInSeconds) { - if (cache_.find(uri) == cache_.end()) + if (!cacheEnabled_ || cache_.find(uri) == cache_.end()) { GetAsyncInternal(uri, headers, new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered @@ -101,6 +103,15 @@ } else { + // put the uri on top of the most recently accessed list + std::deque::iterator it = std::find(orderedCacheKeys_.begin(), orderedCacheKeys_.end(), uri); + if (it != orderedCacheKeys_.end()) + { + std::string uri = *it; + orderedCacheKeys_.erase(it); + orderedCacheKeys_.push_front(uri); + } + // create a command and "post" it to the Oracle so it is executed and commited "later" NotifyHttpSuccessLater(cache_[uri], payload, successCallback); } @@ -123,7 +134,28 @@ void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) { - cache_[message.GetUri()] = boost::shared_ptr(new CachedHttpRequestSuccessMessage(message)); + if (cacheEnabled_) + { + while (cacheCurrentSize_ + message.GetAnswerSize() > cacheMaxSize_ && orderedCacheKeys_.size() > 0) + { + VLOG(1) << "BaseWebService: clearing cache: " << cacheCurrentSize_ << "/" << cacheMaxSize_ << "(" << message.GetAnswerSize() << ")"; + const std::string& oldestUri = orderedCacheKeys_.back(); + HttpCache::iterator it = cache_.find(oldestUri); + if (it != cache_.end()) + { + cacheCurrentSize_ -= it->second->GetAnswerSize(); + cache_.erase(it); + } + orderedCacheKeys_.pop_back(); + + } + + boost::shared_ptr cachedMessage(new CachedHttpRequestSuccessMessage(message)); + cache_[message.GetUri()] = cachedMessage; + orderedCacheKeys_.push_front(message.GetUri()); + cacheCurrentSize_ += message.GetAnswerSize(); + } + NotifyHttpSuccess(message); } diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Deprecated/Toolbox/BaseWebService.h --- a/Framework/Deprecated/Toolbox/BaseWebService.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Fri Jul 19 15:15:13 2019 +0200 @@ -25,6 +25,7 @@ #include #include +#include namespace Deprecated { @@ -81,14 +82,21 @@ class BaseWebServicePayload; bool cacheEnabled_; - std::map > cache_; // TODO: this is currently an infinite cache ! + size_t cacheCurrentSize_; + size_t cacheMaxSize_; + + typedef std::map > HttpCache; + HttpCache cache_; + std::deque orderedCacheKeys_; public: BaseWebService(OrthancStone::MessageBroker& broker) : IWebService(broker), IObserver(broker), - cacheEnabled_(true) + cacheEnabled_(false), + cacheCurrentSize_(0), + cacheMaxSize_(100*1024*1024) { } diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Deprecated/Toolbox/OrthancApiClient.cpp --- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -73,7 +73,7 @@ std::auto_ptr< OrthancStone::MessageHandler > binaryHandler_; std::auto_ptr< OrthancStone::MessageHandler > failureHandler_; std::auto_ptr< Orthanc::IDynamicObject > userPayload_; - + OrthancStone::MessageBroker& broker_; void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const { if (failureHandler_.get() != NULL) @@ -84,12 +84,15 @@ } public: - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : emptyHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) + { if (handler == NULL) { @@ -97,12 +100,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : binaryHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -110,12 +115,14 @@ } } - WebServicePayload(OrthancStone::MessageHandler* handler, + WebServicePayload(OrthancStone::MessageBroker& broker, + OrthancStone::MessageHandler* handler, OrthancStone::MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : jsonHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload) + userPayload_(userPayload), + broker_(broker) { if (handler == NULL) { @@ -127,26 +134,35 @@ { if (emptyHandler_.get() != NULL) { - emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage - (message.GetUri(), userPayload_.get())); + if (broker_.IsActive(*(emptyHandler_->GetObserver()))) + { + emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage + (message.GetUri(), userPayload_.get())); + } } else if (binaryHandler_.get() != NULL) { - binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage - (message.GetUri(), message.GetAnswer(), - message.GetAnswerSize(), userPayload_.get())); + if (broker_.IsActive(*(binaryHandler_->GetObserver()))) + { + binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage + (message.GetUri(), message.GetAnswer(), + message.GetAnswerSize(), userPayload_.get())); + } } else if (jsonHandler_.get() != NULL) { - Json::Value response; - if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + if (broker_.IsActive(*(jsonHandler_->GetObserver()))) { - jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage - (message.GetUri(), response, userPayload_.get())); - } - else - { - NotifyConversionError(message); + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + { + jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage + (message.GetUri(), response, userPayload_.get())); + } + else + { + NotifyConversionError(message); + } } } else @@ -186,7 +202,7 @@ IWebService::HttpHeaders emptyHeaders; web_.GetAsync(baseUrl_ + uri, emptyHeaders, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -216,7 +232,7 @@ // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -232,7 +248,7 @@ Orthanc::IDynamicObject* payload) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -255,7 +271,7 @@ Orthanc::IDynamicObject* payload /* takes ownership */) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable @@ -302,7 +318,7 @@ Orthanc::IDynamicObject* payload) { web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(successCallback, failureCallback, payload), + new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), new OrthancStone::Callable (*this, &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Fri Jul 19 15:15:13 2019 +0200 @@ -60,6 +60,22 @@ return frame_; } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (source_.get() != NULL) + { + size += source_->GetPitch() * source_->GetHeight(); + } + if (converted_.get() != NULL) + { + size += converted_->GetPitch() * converted_->GetHeight(); + } + + return size; + } + + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset); void SetSourceImage(Orthanc::ImageAccessor* image); // Takes ownership diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Fri Jul 19 15:15:13 2019 +0200 @@ -355,5 +355,10 @@ float& maxValue) const = 0; friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to + + virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs) + { + return 0; + } }; } diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographyMaskLayer.h --- a/Framework/Radiography/RadiographyMaskLayer.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.h Fri Jul 19 15:15:13 2019 +0200 @@ -49,6 +49,18 @@ { } + virtual size_t GetApproximateMemoryUsage() const + { + size_t size = 0; + if (mask_.get() != NULL) + { + size += mask_->GetPitch() * mask_->GetHeight(); + } + + return size; + } + + void SetCorners(const std::vector& corners); void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index); diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -147,6 +147,16 @@ return *layer; } + size_t RadiographyScene::GetApproximateMemoryUsage() const + { + size_t size = 0; + for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) + { + size += it->second->GetApproximateMemoryUsage(); + } + return size; + } + void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) { BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Fri Jul 19 15:15:13 2019 +0200 @@ -163,6 +163,8 @@ virtual ~RadiographyScene(); + virtual size_t GetApproximateMemoryUsage() const; + bool GetWindowing(float& center, float& width) const; diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -60,6 +60,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { @@ -143,6 +148,11 @@ if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) + { + scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); + } + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Radiography/RadiographySceneWriter.cpp --- a/Framework/Radiography/RadiographySceneWriter.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -30,6 +30,14 @@ void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene) { output["version"] = 1; + float windowCenter, windowWidth; + bool hasWindowing = scene.GetWindowing(windowCenter, windowWidth); + output["hasWindowing"] = hasWindowing; + if (hasWindowing) + { + output["windowCenter"] = windowCenter; + output["windowWidth"] = windowWidth; + } output["layers"] = Json::arrayValue; std::vector layersIndexes; diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/CairoCompositor.h diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/Internals/CompositorHelper.h --- a/Framework/Scene2D/Internals/CompositorHelper.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Scene2D/Internals/CompositorHelper.h Fri Jul 19 15:15:13 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Scene2D.h" - +#include "../ScenePoint2D.h" #include #include diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/Internals/ICairoContextProvider.h diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/OpenGLCompositor.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2D/OpenGLCompositor.h diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2DViewport/ViewportController.cpp --- a/Framework/Scene2DViewport/ViewportController.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -161,6 +161,12 @@ BroadcastMessage(SceneTransformChanged(*this)); } + void ViewportController::FitContent() + { + viewport_.GetScene().FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); + BroadcastMessage(SceneTransformChanged(*this)); + } + void ViewportController::AddMeasureTool(boost::shared_ptr measureTool) { ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) diff -r 4d1f57773b5b -r d6c029d15aaa Framework/Scene2DViewport/ViewportController.h --- a/Framework/Scene2DViewport/ViewportController.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.h Fri Jul 19 15:15:13 2019 +0200 @@ -120,6 +120,7 @@ /** Forwarded to the underlying scene, and broadcasted to the observers */ void FitContent(unsigned int canvasWidth, unsigned int canvasHeight); + void FitContent(); /** Adds a new measure tool */ void AddMeasureTool(boost::shared_ptr measureTool); diff -r 4d1f57773b5b -r d6c029d15aaa Platforms/Wasm/Defaults.cpp --- a/Platforms/Wasm/Defaults.cpp Fri Jul 19 10:54:03 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -41,6 +41,41 @@ extern "C" { #endif +#if 0 + // rewrite malloc/free in order to monitor allocations. We actually only monitor large allocations (like images ...) + + size_t bigChunksTotalSize = 0; + std::map allocatedBigChunks; + + extern void* emscripten_builtin_malloc(size_t bytes); + extern void emscripten_builtin_free(void* mem); + + void * __attribute__((noinline)) malloc(size_t size) + { + void *ptr = emscripten_builtin_malloc(size); + if (size > 100000) + { + bigChunksTotalSize += size; + printf("++ Allocated %zu bytes, got %p. (%zu MB consumed by big chunks)\n", size, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks[ptr] = size; + } + return ptr; + } + + void __attribute__((noinline)) free(void *ptr) + { + emscripten_builtin_free(ptr); + + std::map::iterator it = allocatedBigChunks.find(ptr); + if (it != allocatedBigChunks.end()) + { + bigChunksTotalSize -= it->second; + printf("-- Freed %zu bytes at %p. (%zu MB consumed by big chunks)\n", it->second, ptr, bigChunksTotalSize/(1024*1024)); + allocatedBigChunks.erase(it); + } + } +#endif // 0 + using namespace OrthancStone; // when WASM needs a C++ viewport @@ -275,7 +310,7 @@ float x2, float y2) { - printf("touch start with %d touches\n", touchCount); + // printf("touch start with %d touches\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -291,7 +326,7 @@ float x2, float y2) { - printf("touch move with %d touches\n", touchCount); + // printf("touch move with %d touches\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -307,7 +342,7 @@ float x2, float y2) { - printf("touch end with %d touches remaining\n", touchCount); + // printf("touch end with %d touches remaining\n", touchCount); std::vector touches; GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); @@ -362,14 +397,14 @@ { static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) - printf("SendSerializedMessageToStoneApplication\n"); - printf("%s", message); + //printf("SendSerializedMessageToStoneApplication\n"); + //printf("%s", message); if (applicationWasmAdapter.get() != NULL) { applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message)); return output.c_str(); } - printf("This Stone application does not have a Web Adapter"); + printf("This Stone application does not have a Web Adapter, unable to send messages"); return NULL; } diff -r 4d1f57773b5b -r d6c029d15aaa Platforms/Wasm/logger.ts --- a/Platforms/Wasm/logger.ts Fri Jul 19 10:54:03 2019 +0200 +++ b/Platforms/Wasm/logger.ts Fri Jul 19 15:15:13 2019 +0200 @@ -73,7 +73,7 @@ private getOutput(source: LogSource, args: any[]): any[] { var prefix = this.getPrefix(); - var prefixAndSource = []; + var prefixAndSource = Array(); if (prefix != null) { prefixAndSource = [prefix]; @@ -94,7 +94,7 @@ return [...prefixAndSource, ...args]; } - protected getPrefix(): string { + protected getPrefix(): string | null { return null; } } diff -r 4d1f57773b5b -r d6c029d15aaa Platforms/Wasm/wasm-application-runner.ts --- a/Platforms/Wasm/wasm-application-runner.ts Fri Jul 19 10:54:03 2019 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Fri Jul 19 15:15:13 2019 +0200 @@ -1,5 +1,5 @@ -import Stone = require('./stone-framework-loader'); -import StoneViewport = require('./wasm-viewport'); +import * as Stone from './stone-framework-loader' +import * as StoneViewport from './wasm-viewport' import * as Logger from './logger' if (!('WebAssembly' in window)) { @@ -130,11 +130,6 @@ Logger.defaultLogger.debug("Connecting C++ methods to JS methods - done"); - // Prevent scrolling - document.body.addEventListener('touchmove', function (event) { - event.preventDefault(); - }, { passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface - _InitializeWasmApplication(orthancBaseUrl); }); } diff -r 4d1f57773b5b -r d6c029d15aaa Platforms/Wasm/wasm-viewport.ts --- a/Platforms/Wasm/wasm-viewport.ts Fri Jul 19 10:54:03 2019 +0200 +++ b/Platforms/Wasm/wasm-viewport.ts Fri Jul 19 15:15:13 2019 +0200 @@ -1,4 +1,4 @@ -import wasmApplicationRunner = require('./wasm-application-runner'); +import * as wasmApplicationRunner from './wasm-application-runner' import * as Logger from './logger' var isPendingRedraw = false; @@ -10,14 +10,17 @@ Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; - WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + let viewport = WasmViewport.GetFromCppViewport(cppViewportHandle); + if (viewport) { + viewport.Redraw(); + } }); } } (window).ScheduleWebViewportRedraw = ScheduleWebViewportRedraw; -declare function UTF8ToString(any): string; +declare function UTF8ToString(v: any): string; function CreateWasmViewport(htmlCanvasId: string) : any { var cppViewportHandle = wasmApplicationRunner.CreateCppViewport(); @@ -38,7 +41,7 @@ private module_ : any; private canvasId_ : string; private htmlCanvas_ : HTMLCanvasElement; - private context_ : CanvasRenderingContext2D; + private context_ : CanvasRenderingContext2D | null; private imageData_ : any = null; private renderingBuffer_ : any = null; @@ -96,20 +99,20 @@ return this.pimpl_; } - public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport | null { if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) { return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } - public static GetFromCanvasId(canvasId: string) : WasmViewport { + public static GetFromCanvasId(canvasId: string) : WasmViewport | null { if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) { return WasmViewport.viewportsMapByCanvasId_[canvasId]; } Logger.defaultLogger.error("WasmViewport not found !"); - return undefined; + return null; } public static ResizeAll() { @@ -135,7 +138,9 @@ this.renderingBuffer_, this.imageData_.width * this.imageData_.height * 4)); - this.context_.putImageData(this.imageData_, 0, 0); + if (this.context_) { + this.context_.putImageData(this.imageData_, 0, 0); + } } } @@ -147,25 +152,27 @@ } // width/height is defined by the parent width/height - this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; - this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; + if (this.htmlCanvas_.parentElement) { + this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; + this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; - Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); - if (this.imageData_ === null) { - this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); - - if (this.renderingBuffer_ != null) { - this.module_._free(this.renderingBuffer_); + if (this.imageData_ === null && this.context_) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } else { + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); } - this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); - } else { - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.Redraw(); } - - this.Redraw(); } public Initialize() { @@ -211,7 +218,7 @@ }); window.addEventListener('keydown', function(event) { - var keyChar = event.key; + var keyChar: string | null = event.key; var keyCode = event.keyCode if (keyChar.length == 1) { keyCode = 0; // maps to OrthancStone::KeyboardKeys_Generic @@ -328,7 +335,7 @@ this.touchZoom_ = false; } - public GetTouchTranslation(event) { + public GetTouchTranslation(event: any) { var touch = event.targetTouches[0]; return [ touch.pageX, @@ -336,7 +343,7 @@ ]; } - public GetTouchZoom(event) { + public GetTouchZoom(event: any) { var touch1 = event.targetTouches[0]; var touch2 = event.targetTouches[1]; var dx = (touch1.pageX - touch2.pageX); diff -r 4d1f57773b5b -r d6c029d15aaa Resources/CMake/OrthancStoneConfiguration.cmake diff -r 4d1f57773b5b -r d6c029d15aaa Resources/CodeGeneration/template.in.h.j2 --- a/Resources/CodeGeneration/template.in.h.j2 Fri Jul 19 10:54:03 2019 +0200 +++ b/Resources/CodeGeneration/template.in.h.j2 Fri Jul 19 15:15:13 2019 +0200 @@ -51,7 +51,7 @@ inline Json::Value _StoneSerializeValue(int64_t value) { - Json::Value result(value); + Json::Value result(static_cast(value)); return result; } @@ -79,7 +79,7 @@ inline Json::Value _StoneSerializeValue(uint64_t value) { - Json::Value result(value); + Json::Value result(static_cast(value)); return result; } diff -r 4d1f57773b5b -r d6c029d15aaa Samples/MultiPlatform/BasicScene/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/MultiPlatform/BasicScene/BasicScene.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,275 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "BasicScene.h" + +// From Stone +#include "Framework/Scene2D/Scene2D.h" +#include "Framework/Scene2D/ColorTextureSceneLayer.h" +#include "Framework/Scene2D/PolylineSceneLayer.h" +#include "Framework/Scene2D/TextSceneLayer.h" + +#include "Framework/Scene2D/PanSceneTracker.h" +#include "Framework/Scene2D/ZoomSceneTracker.h" +#include "Framework/Scene2D/RotateSceneTracker.h" + +#include "Framework/Scene2D/CairoCompositor.h" + +// From Orthanc framework +#include +#include +#include + +using namespace OrthancStone; + +const unsigned int BASIC_SCENE_FONT_SIZE = 32; +const int BASIC_SCENE_LAYER_POSITION = 150; + +void PrepareScene(Scene2D& scene) +{ + //Scene2D& scene(*controller->GetScene()); + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene.SetLayer(12, new ColorTextureSceneLayer(i)); + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::auto_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::auto_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + PolylineSceneLayer::Chain chain; + chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); + chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); + chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); + layer->AddChain(chain, true, 255, 0, 0); + + chain.clear(); + chain.push_back(ScenePoint2D(-5, -5)); + chain.push_back(ScenePoint2D(5, -5)); + chain.push_back(ScenePoint2D(5, 5)); + chain.push_back(ScenePoint2D(-5, 5)); + layer->AddChain(chain, true, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + chain.push_back(ScenePoint2D(4, -4 + dy)); + chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); + chain.push_back(ScenePoint2D(4, 2)); + layer->AddChain(chain, false, 0, 0, 255); + + // layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::auto_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + +#if ORTHANC_SANDBOXED == 0 +void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight) +{ + using namespace OrthancStone; + // Take a screenshot, then save it as PNG file + CairoCompositor compositor(scene, canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + compositor.Refresh(); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); +} +#endif + +void ShowCursorInfo(Scene2D& scene, const PointerEvent& pointerEvent) +{ + ScenePoint2D p = pointerEvent.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene.HasLayer(BASIC_SCENE_LAYER_POSITION)) + { + TextSceneLayer& layer = + dynamic_cast(scene.GetLayer(BASIC_SCENE_LAYER_POSITION)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr + layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene.SetLayer(BASIC_SCENE_LAYER_POSITION, layer.release()); + } +} + + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + default: + return false; + } + return true; + } + else if (event.type == GUIADAPTER_EVENT_MOUSEDOWN) + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, viewportController_->GetViewport().GetCanvasHeight())); + } + } + else if (event.type == GUIADAPTER_EVENT_MOUSEMOVE) + { + if (showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + ShowCursorInfo(scene, pointerEvent); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + if (guiEvent.type == GUIADAPTER_EVENT_KEYDOWN) + { + switch (guiEvent.sym[0]) + { + case 's': + { + //viewportController_->FitContent(viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); + viewportController_->FitContent(); + return true; + }; +#if ORTHANC_SANDBOXED == 0 + case 'c': + { + Scene2D& scene(viewportController_->GetScene()); + TakeScreenshot("screenshot.png", scene, viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); + return true; + } +#endif + case 'd': + { + showCursorInfo_ = !showCursorInfo_; + if (!showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + scene.DeleteLayer(BASIC_SCENE_LAYER_POSITION); + } + + return true; + } + } + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/MultiPlatform/BasicScene/BasicScene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/MultiPlatform/BasicScene/BasicScene.h Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,55 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include +#include "Framework/Scene2DViewport/ViewportController.h" +#include "Framework/Scene2D/Scene2D.h" + +extern const unsigned int BASIC_SCENE_FONT_SIZE; +extern const int BASIC_SCENE_LAYER_POSITION; + +extern void PrepareScene(OrthancStone::Scene2D& scene); +extern void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight); + + +#include "Applications/Generic/Scene2DInteractor.h" +#include "Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr currentTracker_; + bool showCursorInfo_; +public: + BasicScene2DInteractor(boost::shared_ptr viewportController) : + Scene2DInteractor(viewportController), + showCursorInfo_(false) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent) override; + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent) override; +}; + diff -r 4d1f57773b5b -r d6c029d15aaa Samples/MultiPlatform/BasicScene/mainQt.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/MultiPlatform/BasicScene/mainQt.cpp Fri Jul 19 15:15:13 2019 +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-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#define GLEW_STATIC 1 +// From Stone +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "../../Applications/Sdl/SdlWindow.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" + +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + +#include +#include +#include "EmbeddedResources.h" + +#include +#include +#include + +#include "BasicScene.h" + + +using namespace OrthancStone; + + + +static void GLAPIENTRY OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam ) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), + type, severity, message ); + } +} + +extern void InitGL(); + +#include +#include "BasicSceneWindow.h" + +int main(int argc, char* argv[]) +{ + QApplication a(argc, argv); + + OrthancStone::Samples::BasicSceneWindow window; + window.show(); + window.GetOpenGlWidget().Init(); + + MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); + boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), window.GetOpenGlWidget()); + PrepareScene(controller->GetScene()); + + window.GetOpenGlWidget().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + + boost::shared_ptr interactor(new BasicScene2DInteractor(controller)); + window.GetOpenGlWidget().SetInteractor(interactor); + + controller->FitContent(); + + return a.exec(); +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/MultiPlatform/BasicScene/mainSdl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/MultiPlatform/BasicScene/mainSdl.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,199 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +// From Stone +#include "Framework/Viewport/SdlViewport.h" +#include "Framework/Scene2D/OpenGLCompositor.h" +#include "Framework/Scene2DViewport/UndoStack.h" +#include "Framework/StoneInitialization.h" +#include "Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include + +#include +#include + +#include +#include + + +#include "BasicScene.h" + +using namespace OrthancStone; + +boost::shared_ptr interactor; + +void HandleApplicationEvent(boost::shared_ptr controller, + const SDL_Event& event) +{ + using namespace OrthancStone; + Scene2D& scene(controller->GetScene()); + if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) + { + // TODO: this code is copy/pasted from GuiAdapter::Run() -> find the right place + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + bool ctrlPressed(false); + bool shiftPressed(false); + bool altPressed(false); + + if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT]) + altPressed = true; + + GuiAdapterMouseEvent guiEvent; + ConvertFromPlatform(guiEvent, ctrlPressed, shiftPressed, altPressed, event); + PointerEvent pointerEvent; + pointerEvent.AddPosition(controller->GetViewport().GetPixelCenterCoordinates(event.button.x, event.button.y)); + + interactor->OnMouseEvent(guiEvent, pointerEvent); + return; + } + else if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.repeat == 0 /* Ignore key bounce */) + { + GuiAdapterKeyboardEvent guiEvent; + ConvertFromPlatform(guiEvent, event); + + interactor->OnKeyboardEvent(guiEvent); + } + +} + + +static void GLAPIENTRY +OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam ) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), + type, severity, message ); + } +} + + +void Run(boost::shared_ptr controller) +{ + SdlViewport& sdlViewport = dynamic_cast(controller->GetViewport()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + controller->GetViewport().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + + controller->GetViewport().Refresh(); + controller->FitContent(); + + + bool stop = false; + while (!stop) + { + controller->GetViewport().Refresh(); + + SDL_Event event; + while (!stop && + SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + sdlViewport.UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + sdlViewport.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + + HandleApplicationEvent(controller, event); + } + + SDL_Delay(1); + } + interactor.reset(); +} + + + + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + using namespace OrthancStone; + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { + SdlOpenGLViewport viewport("Hello", 1024, 768); + MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); + boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), viewport); + interactor.reset(new BasicScene2DInteractor(controller)); + PrepareScene(controller->GetScene()); + Run(controller); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/BasicSceneWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "BasicSceneWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + BasicSceneWindow::BasicSceneWindow( + QWidget *parent) : + ui_(new Ui::BasicSceneWindow) + { + ui_->setupUi(this); + } + + BasicSceneWindow::~BasicSceneWindow() + { + delete ui_; + } + + QStoneOpenGlWidget& BasicSceneWindow::GetOpenGlWidget() + { + return *(ui_->centralWidget); + } + + } +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/BasicSceneWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.h Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,53 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once +#include +#include +// #include "../../Qt/QCairoWidget.h" +// #include "../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class BasicSceneWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + //class SampleSingleCanvasApplicationBase; + + class BasicSceneWindow : public QMainWindow + { + Q_OBJECT + + private: + Ui::BasicSceneWindow* ui_; + //SampleSingleCanvasApplicationBase& stoneSampleApplication_; + + public: + explicit BasicSceneWindow(QWidget *parent = 0); + ~BasicSceneWindow(); + + QStoneOpenGlWidget& GetOpenGlWidget(); + }; + } +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/BasicSceneWindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/BasicSceneWindow.ui Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,84 @@ + + + BasicSceneWindow + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + + 0 + 0 + 903 + 21 + + + + + Test + + + + + + + + + QStoneOpenGlWidget + QWidget +
QStoneOpenGlWidget.h
+
+
+ + +
diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/CMakeLists.txt Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 2.8.3) + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_QT ON) +SET(ENABLE_SDL OFF) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +list(APPEND BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.cpp + ) + +ORTHANC_QT_WRAP_UI(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.ui + ) + +ORTHANC_QT_WRAP_CPP(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.h + QStoneOpenGlWidget.h + ) + +add_executable(MpBasicScene + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.cpp + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/mainQt.cpp + QStoneOpenGlWidget.cpp + ${BASIC_SCENE_APPLICATIONS_SOURCES} + ) + +target_include_directories(MpBasicScene PUBLIC ${CMAKE_SOURCE_DIR} ${ORTHANC_STONE_ROOT}) +target_link_libraries(MpBasicScene OrthancStone) diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/QStoneOpenGlWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,171 @@ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "QStoneOpenGlWidget.h" + +#include + +using namespace OrthancStone; + +void QStoneOpenGlWidget::initializeGL() +{ + glewInit(); +} + +void QStoneOpenGlWidget::MakeCurrent() +{ + this->makeCurrent(); +} + +void QStoneOpenGlWidget::resizeGL(int w, int h) +{ + +} + +void QStoneOpenGlWidget::paintGL() +{ + if (compositor_) + { + compositor_->Refresh(); + } + doneCurrent(); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterMouseEvent& guiEvent, + PointerEvent& pointerEvent, + const QMouseEvent& qtEvent, + const IViewport& viewport) +{ + guiEvent.targetX = qtEvent.x(); + guiEvent.targetY = qtEvent.y(); + pointerEvent.AddPosition(viewport.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY)); + + switch (qtEvent.button()) + { + case Qt::LeftButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; break; + case Qt::MiddleButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_MIDDLE; break; + case Qt::RightButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_RIGHT; break; + default: + guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; + } + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } +} + +void QStoneOpenGlWidget::mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + OrthancStone::GuiAdapterMouseEvent guiEvent; + PointerEvent pointerEvent; + ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *this); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + sceneInteractor_->OnMouseEvent(guiEvent, pointerEvent); + } + + // force redraw of the OpenGL widget + update(); +} + +void QStoneOpenGlWidget::mousePressEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); +} + +void QStoneOpenGlWidget::mouseMoveEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEMOVE); +} + +void QStoneOpenGlWidget::mouseReleaseEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEUP); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterKeyboardEvent& guiEvent, + const QKeyEvent& qtEvent) +{ + if (qtEvent.text().length() > 0) + { + guiEvent.sym[0] = qtEvent.text()[0].cell(); + } + else + { + guiEvent.sym[0] = 0; + } + guiEvent.sym[1] = 0; + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } + +} + + +bool QStoneOpenGlWidget::keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + bool handled = false; + OrthancStone::GuiAdapterKeyboardEvent guiEvent; + ConvertFromPlatform(guiEvent, *qtEvent); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + handled = sceneInteractor_->OnKeyboardEvent(guiEvent); + + if (handled) + { + // force redraw of the OpenGL widget + update(); + } + } + return handled; +} + +void QStoneOpenGlWidget::keyPressEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYDOWN); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::keyReleaseEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYUP); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::wheelEvent(QWheelEvent *qtEvent) +{ + OrthancStone::GuiAdapterWheelEvent guiEvent; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + + // force redraw of the OpenGL widget + update(); +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/QStoneOpenGlWidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/QStoneOpenGlWidget.h Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,90 @@ +#pragma once +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include +#include +#include + +#include +#include "../../Framework/OpenGL/IOpenGLContext.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Viewport/ViewportBase.h" +#include "../../Applications/Generic/Scene2DInteractor.h" + +namespace OrthancStone +{ + class QStoneOpenGlWidget : + public QOpenGLWidget, + public OpenGL::IOpenGLContext, + public ViewportBase + { + std::unique_ptr compositor_; + boost::shared_ptr sceneInteractor_; + QOpenGLContext openGlContext_; + + public: + QStoneOpenGlWidget(QWidget *parent) : + QOpenGLWidget(parent), + ViewportBase("QtStoneOpenGlWidget") // TODO: we shall be able to define a name but construction time is too early ! + { + setFocusPolicy(Qt::StrongFocus); // to enable keyPressEvent + setMouseTracking(true); // to enable mouseMoveEvent event when no button is pressed + } + + void Init() + { + QSurfaceFormat requestedFormat; + requestedFormat.setVersion( 2, 0 ); + openGlContext_.setFormat( requestedFormat ); + openGlContext_.create(); + openGlContext_.makeCurrent(context()->surface()); + + compositor_.reset(new OpenGLCompositor(*this, GetScene())); + } + + protected: + + //**** QWidget overrides + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void wheelEvent(QWheelEvent* event) override; + + //**** IOpenGLContext overrides + + virtual void MakeCurrent() override; + virtual void SwapBuffer() override {} + + virtual unsigned int GetCanvasWidth() const override + { + return this->width(); + } + + virtual unsigned int GetCanvasHeight() const override + { + return this->height(); + } + + public: + + void SetInteractor(boost::shared_ptr sceneInteractor) + { + sceneInteractor_ = sceneInteractor; + } + + virtual ICompositor& GetCompositor() + { + return *compositor_; + } + + protected: + void mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + bool keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + + }; +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/Scene2DInteractor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,72 @@ +#include "Scene2DInteractor.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + + +namespace OrthancStone +{ + +} + +using namespace OrthancStone; + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + } + return true; + } + else + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT && compositor_.get() != NULL) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, compositor_->GetHeight())); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + switch (guiEvent.sym[0]) + { + case 's': + { + viewportController_->FitContent(compositor_->GetWidth(), compositor_->GetHeight()); + return true; + }; + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +} diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Qt/Scene2DInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Qt/Scene2DInteractor.h Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,19 @@ +#pragma once + +#include "../../Applications/Generic/Scene2DInteractor.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr currentTracker_; +public: + BasicScene2DInteractor(boost::shared_ptr viewportController) : + Scene2DInteractor(viewportController) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent); + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent); +}; + diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/BasicScene.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/CMakeLists.txt --- a/Samples/Sdl/CMakeLists.txt Fri Jul 19 10:54:03 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Fri Jul 19 15:15:13 2019 +0200 @@ -71,7 +71,7 @@ target_link_libraries(BasicScene OrthancStone) # -# BasicScene +# TrackerSample # LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") @@ -108,3 +108,22 @@ ) target_link_libraries(FusionMprSdl OrthancStone) + +# +# Multiplatform Basic Scene +# + +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.cpp") +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.h") +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/mainSdl.cpp") + +if (MSVC AND MSVC_VERSION GREATER 1700) + LIST(APPEND MP_BASIC_SCENE_SOURCE "cpp.hint") +endif() + +add_executable(MpBasicScene + ${MP_BASIC_SCENE_SOURCE} + ) + +target_include_directories(MpBasicScene PUBLIC ${ORTHANC_STONE_ROOT}) +target_link_libraries(MpBasicScene OrthancStone) diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/FusionMprSdl.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/RadiographyEditor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/RadiographyEditor.cpp Fri Jul 19 15:15:13 2019 +0200 @@ -0,0 +1,267 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "../Shared/RadiographyEditorApp.h" + +// From Stone +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + +#include +#include + + +#include +#include + +#include +#include + +using namespace OrthancStone; + +namespace OrthancStone +{ + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + } + } + + + class ReaderLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::shared_lock lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} + +class OpenGlSdlCompositorFactory : public ICompositorFactory +{ + OpenGL::IOpenGLContext& openGlContext_; + +public: + OpenGlSdlCompositorFactory(OpenGL::IOpenGLContext& openGlContext) : + openGlContext_(openGlContext) + {} + + ICompositor* GetCompositor(const Scene2D& scene) + { + + OpenGLCompositor* compositor = new OpenGLCompositor(openGlContext_, scene); + compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor->SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + return compositor; + } +}; + +static void GLAPIENTRY +OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } +} + + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + using namespace OrthancStone; + + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); + + try + { + OrthancStone::NativeApplicationContext context; + OrthancStone::NativeApplicationContext::WriterLock lock(context); + OrthancStone::ThreadedOracle oracle(context); + + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + std::auto_ptr compositorFactory(new OpenGlSdlCompositorFactory(window)); + boost::shared_ptr app(new RadiographyEditorApp(oracle, lock.GetOracleObservable(), compositorFactory.release())); + app->PrepareScene(); + app->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + bool stopApplication = false; + + while (!stopApplication) + { + app->Refresh(); + + SDL_Event event; + while (!stopApplication && SDL_PollEvent(&event)) + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (event.key.keysym.mod & KMOD_CTRL) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Control)); + if (event.key.keysym.mod & KMOD_ALT) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Alt)); + if (event.key.keysym.mod & KMOD_SHIFT) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Shift)); + + OrthancStone::MouseButton button; + if (event.button.button == SDL_BUTTON_LEFT) + button = OrthancStone::MouseButton_Left; + else if (event.button.button == SDL_BUTTON_MIDDLE) + button = OrthancStone::MouseButton_Middle; + else if (event.button.button == SDL_BUTTON_RIGHT) + button = OrthancStone::MouseButton_Right; + + if (event.type == SDL_QUIT) + { + stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + app->DisableTracker(); // was: tracker.reset(NULL); + app->UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + stopApplication = true; + break; + default: + { + app->OnKeyPressed(event.key.keysym.sym, modifiers); + } + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + app->OnMouseDown(event.button.x, event.button.y, modifiers, button); + } + else if (event.type == SDL_MOUSEMOTION) + { + app->OnMouseMove(event.button.x, event.button.y, modifiers); + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + app->OnMouseUp(event.button.x, event.button.y, modifiers, button); + } + } + SDL_Delay(1); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + + diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/TrackerSampleApp.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Samples/Sdl/TrackerSampleApp.h --- a/Samples/Sdl/TrackerSampleApp.h Fri Jul 19 10:54:03 2019 +0200 +++ b/Samples/Sdl/TrackerSampleApp.h Fri Jul 19 15:15:13 2019 +0200 @@ -142,7 +142,7 @@ GuiTool currentTool_; boost::shared_ptr undoStack_; - SdlViewport viewport_; + SdlOpenGLViewport viewport_; }; } diff -r 4d1f57773b5b -r d6c029d15aaa Samples/WebAssembly/BasicScene.cpp diff -r 4d1f57773b5b -r d6c029d15aaa Samples/WebAssembly/CMakeLists.txt --- a/Samples/WebAssembly/CMakeLists.txt Fri Jul 19 10:54:03 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Fri Jul 19 15:15:13 2019 +0200 @@ -79,6 +79,8 @@ if (ON) add_executable(BasicScene BasicScene.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp ) target_link_libraries(BasicScene OrthancStone) diff -r 4d1f57773b5b -r d6c029d15aaa Samples/WebAssembly/NOTES.txt diff -r 4d1f57773b5b -r d6c029d15aaa Samples/WebAssembly/dev.h