Mercurial > hg > orthanc-stone
changeset 2326:f5d2e4b76439 refactoring
SdlSimpleViewer using the new observer/observable pattern
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -20,6 +20,8 @@ **/ +#include "../../../../OrthancStone/Sources/Messages/TypedObserver.h" + #include "../../../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h" #include "../../../../OrthancStone/Sources/Loaders/SeriesFramesLoader.h" #include "../../../../OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h" @@ -75,8 +77,7 @@ LOG(ERROR) << "error!"; } }; - -static boost::shared_ptr<Toto> toto_(new Toto); // TODO REMOVE +// END TODO Refactoring namespace OrthancStone @@ -93,13 +94,13 @@ virtual bool HandleKeyDown(char key) = 0; - virtual void HandleMouseDown(OrthancStone::IViewport::ILock& lock, - const OrthancStone::PointerEvent& p) = 0; + virtual void HandleMouseDown(IViewport::ILock& lock, + const PointerEvent& p) = 0; - virtual bool HandleMouseMove(OrthancStone::IViewport::ILock& lock, - const OrthancStone::PointerEvent& p) = 0; + virtual bool HandleMouseMove(IViewport::ILock& lock, + const PointerEvent& p) = 0; - virtual void Render(OrthancStone::IViewport::ILock& lock) = 0; + virtual void Render(IViewport::ILock& lock) = 0; }; @@ -146,7 +147,7 @@ (event.window.event == SDL_WINDOWEVENT_SHOWN || event.window.event == SDL_WINDOWEVENT_EXPOSED)) { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); lock->RefreshCanvasSize(); } else if (event.type == SDL_TEXTINPUT) @@ -174,11 +175,11 @@ event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONUP) { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); if (lock->HasCompositor()) { - OrthancStone::PointerEvent p; + PointerEvent p; OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), event, keyboardState, scancodeCount); switch (event.type) @@ -206,7 +207,7 @@ if (paint) { { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); core_->Render(*lock); } @@ -235,18 +236,18 @@ if (useOpenGL) { - viewport_ = OrthancStone::SdlOpenGLViewport::Create(title, width, height); + viewport_ = SdlOpenGLViewport::Create(title, width, height); } else { - viewport_ = OrthancStone::SdlCairoViewport::Create(title, width, height); + viewport_ = SdlCairoViewport::Create(title, width, height); } std::string font; Orthanc::EmbeddedResources::GetFileResource(font, Orthanc::EmbeddedResources::UBUNTU_FONT); { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); lock->GetCompositor().SetFont(0, font, 16, Orthanc::Encoding_Latin1); } } @@ -254,7 +255,9 @@ class SimpleViewerApp : public ISingleViewportApplicationCore, - public ObserverBase<SimpleViewerApp> // TODO Refactoring - Remove + public boost::enable_shared_from_this<SimpleViewerApp>, + public New::TypedObserver<FrameDecodedMessage>, + public New::TypedObserver<DicomResourcesLoadedMessage> { private: std::string instanceId_; @@ -262,13 +265,15 @@ boost::shared_ptr<StoneApplication::Context> context_; boost::shared_ptr<IViewport> viewport_; AnnotationsSceneLayer annotations_; - OrthancStone::DefaultViewportInteractor interactor_; + DefaultViewportInteractor interactor_; boost::shared_ptr<Toto> toto_; boost::shared_ptr<DicomResourcesLoader> dicomLoader_; boost::shared_ptr<SeriesFramesLoader> framesLoader_; - OrthancStone::Units units_; + Units units_; - void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + public: + virtual void Handle(const New::IObservable& observable, + const FrameDecodedMessage& message) ORTHANC_OVERRIDE { LOG(INFO) << "Frame decoded! " << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() @@ -287,14 +292,15 @@ } } - void Handle(const DicomResourcesLoader::SuccessMessage& message) + virtual void Handle(const New::IObservable& observable, + const DicomResourcesLoadedMessage& message) ORTHANC_OVERRIDE { if (message.GetResources()->GetSize() != 1) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - OrthancStone::DicomInstanceParameters parameters(message.GetResources()->GetResource(0)); + DicomInstanceParameters parameters(message.GetResources()->GetResource(0)); if (parameters.HasPixelSpacing()) { /** @@ -309,7 +315,7 @@ // std::cout << message.GetResources()->GetSourceJson(0).toStyledString(); LOG(INFO) << "Using millimeters units, as the DICOM instance contains the PixelSpacing tag"; - units_ = OrthancStone::Units_Millimeters; + units_ = Units_Millimeters; } else { @@ -319,11 +325,12 @@ //message.GetResources()->GetResource(0).Print(stdout); { - std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_->GetEnvironment().AcquireLock()); + std::unique_ptr<IEnvironment::ILock> lock(context_->GetEnvironment().AcquireLock()); framesLoader_ = SeriesFramesLoader::Create(*context_, *message.GetResources()); - Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &SimpleViewerApp::Handle); + //Register<FrameDecodedMessage>(*framesLoader_, &SimpleViewerApp::Handle); + framesLoader_->Register(shared_from_this()); assert(message.HasUserPayload()); @@ -348,9 +355,12 @@ dicomLoader_ = DicomResourcesLoader::Create(*context); - Register<DicomResourcesLoader::SuccessMessage>(*dicomLoader_, &SimpleViewerApp::Handle); + //Register<DicomResourcesLoadedMessage>(*dicomLoader_, &SimpleViewerApp::Handle); - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit); + //New::TypedObservable<DicomResourcesLoadedMessage>::Bind(*dicomLoader_, shared_from_this()); + dicomLoader_->Register(shared_from_this()); + + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Edit); annotations_.SetProbedLayer(0); interactor_.SetWindowingLayer(0); @@ -358,13 +368,13 @@ toto_.reset(new Toto); { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock()); - lock->GetController().SetUndoStack(boost::make_shared<OrthancStone::UndoStack>()); + std::unique_ptr<IViewport::ILock> lock(viewport->Lock()); + lock->GetController().SetUndoStack(boost::make_shared<UndoStack>()); } { - // std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context->GetEnvironment().AcquireLock()); - OrthancStone::DicomSource source; + // std::unique_ptr<IEnvironment::ILock> lock(context->GetEnvironment().AcquireLock()); + DicomSource source; dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, Orthanc::ResourceType_Instance, instanceId_, new Orthanc::SingleValueObject<unsigned int>(frameIndex_)); @@ -378,19 +388,19 @@ case 'b': { // TODO Refactoring - OrthancStone::IEnvironment& environment = context_->GetEnvironment(); - OrthancStone::IOracle& oracle = context_->GetOracle(); + IEnvironment& environment = context_->GetEnvironment(); + IOracle& oracle = context_->GetOracle(); - oracle.Submit(environment, toto_, new OrthancStone::SleepOracleCommand(1000)); + oracle.Submit(environment, toto_, new SleepOracleCommand(1000)); { - std::unique_ptr<OrthancStone::HttpCommand> command(new OrthancStone::HttpCommand); + std::unique_ptr<HttpCommand> command(new HttpCommand); command->SetUrl("http://ip-api.com/json/"); oracle.Submit(environment, toto_, command.release()); } { - std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/system/"); oracle.Submit(environment, toto_, command.release()); } @@ -399,7 +409,7 @@ { DicomSource source; source.SetDicomDirSource(); - oracle.Submit(environment, toto_, new OrthancStone::ParseDicomFromFileCommand(source, "hand.dcm")); + oracle.Submit(environment, toto_, new ParseDicomFromFileCommand(source, "hand.dcm")); } break; } @@ -414,7 +424,7 @@ case 'u': { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); if (lock->GetController().CanUndo()) { lock->GetController().Undo(); @@ -424,7 +434,7 @@ case 'U': { - std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); if (lock->GetController().CanRedo()) { lock->GetController().Redo(); @@ -433,35 +443,35 @@ } case 'c': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Circle); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Circle); break; case 'm': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Edit); break; case 'd': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Remove); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Remove); break; case 'l': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Length); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Length); break; case 'a': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Angle); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_Angle); break; case 'p': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_PixelProbe); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_PixelProbe); break; case 'e': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_EllipseProbe); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_EllipseProbe); break; case 'r': - annotations_.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_RectangleProbe); + annotations_.SetActiveTool(AnnotationsSceneLayer::Tool_RectangleProbe); break; default: @@ -471,14 +481,14 @@ return false; // No need to repaint } - virtual void HandleMouseDown(OrthancStone::IViewport::ILock& lock, - const OrthancStone::PointerEvent& p) ORTHANC_OVERRIDE + virtual void HandleMouseDown(IViewport::ILock& lock, + const PointerEvent& p) ORTHANC_OVERRIDE { annotations_.SetUnits(units_); - boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; + boost::shared_ptr<IFlexiblePointerTracker> t; - if (p.GetMouseButton() == OrthancStone::MouseButton_Left) + if (p.GetMouseButton() == MouseButton_Left) { t.reset(annotations_.CreateTracker(p.GetMainPosition(), lock.GetController().GetScene())); } @@ -497,8 +507,8 @@ lock.Invalidate(); } - virtual bool HandleMouseMove(OrthancStone::IViewport::ILock& lock, - const OrthancStone::PointerEvent& p) ORTHANC_OVERRIDE + virtual bool HandleMouseMove(IViewport::ILock& lock, + const PointerEvent& p) ORTHANC_OVERRIDE { if (lock.GetController().HandleMouseMove(p)) { @@ -519,7 +529,7 @@ return false; } - virtual void Render(OrthancStone::IViewport::ILock& lock) + virtual void Render(IViewport::ILock& lock) { annotations_.Render(lock.GetController().GetScene()); } @@ -530,12 +540,11 @@ instanceId_(instanceId), frameIndex_(frameIndex), annotations_(10), - units_(OrthancStone::Units_Pixels) + units_(Units_Pixels) { } }; } -// END TODO Refactoring
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Wed Sep 30 20:06:19 2026 +0200 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Wed Sep 30 21:15:01 2026 +0200 @@ -49,7 +49,7 @@ { } - void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + void Handle(const FrameDecodedMessage& message) { LOG(INFO) << "Frame decoded! " << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() @@ -67,7 +67,7 @@ } } - void Handle(const DicomResourcesLoader::SuccessMessage& message) + void Handle(const DicomResourcesLoadedMessage& message) { if (message.GetResources()->GetSize() != 1) { @@ -80,7 +80,7 @@ std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock()); framesLoader_ = SeriesFramesLoader::Create(context_, *message.GetResources()); - Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &SingleFrameViewerApplication::Handle); + Register<FrameDecodedMessage>(*framesLoader_, &SingleFrameViewerApplication::Handle); assert(message.HasUserPayload()); const Orthanc::SingleValueObject<unsigned int>& payload = @@ -102,7 +102,7 @@ application->dicomLoader_ = DicomResourcesLoader::Create(context); - application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SingleFrameViewerApplication::Handle); + application->Register<DicomResourcesLoadedMessage>(*application->dicomLoader_, &SingleFrameViewerApplication::Handle); return application; } @@ -208,7 +208,7 @@ } } - void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message) + void HandleLoadedResources(const DicomResourcesLoadedMessage& message) { LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); @@ -313,7 +313,7 @@ application->Register<OrthancRestApiCommand::SuccessMessage>( context.GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); - application->Register<DicomResourcesLoader::SuccessMessage>( + application->Register<DicomResourcesLoadedMessage>( *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); application->Register<SeriesThumbnailsLoader::SuccessMessage>(
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -780,7 +780,7 @@ { } - void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) + void Handle(const OrthancStone::DicomResourcesLoadedMessage& message) { const Orthanc::SingleValueObject<Orthanc::ResourceType>& payload = dynamic_cast<const Orthanc::SingleValueObject<Orthanc::ResourceType>&>(message.GetUserPayload()); @@ -1138,7 +1138,7 @@ loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(context, PRIORITY_LOW); loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(context); - loader->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>( + loader->Register<OrthancStone::DicomResourcesLoadedMessage>( *loader->resourcesLoader_, &ResourcesLoader::Handle); loader->Register<OrthancStone::SeriesThumbnailsLoader::SuccessMessage>( @@ -2346,7 +2346,7 @@ return *viewport_; } - virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const + virtual void Handle(const OrthancStone::DicomResourcesLoadedMessage& message) const { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } @@ -2370,7 +2370,7 @@ { } - virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE + virtual void Handle(const OrthancStone::DicomResourcesLoadedMessage& message) const ORTHANC_OVERRIDE { if (message.GetResources()->GetSize() != 1) { @@ -3331,7 +3331,7 @@ return true; } - void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) + void Handle(const OrthancStone::DicomResourcesLoadedMessage& message) { dynamic_cast<const ICommand&>(message.GetUserPayload()).Handle(message); } @@ -3446,7 +3446,7 @@ { viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(context); - viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>( + viewport->Register<OrthancStone::DicomResourcesLoadedMessage>( *viewport->loader_, &ViewerViewport::Handle); viewport->Register<OrthancStone::HttpCommand::SuccessMessage>(
--- a/OrthancStone/Sources/Loaders/DicomResourcesLoader.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/DicomResourcesLoader.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -95,8 +95,10 @@ void BroadcastSuccess() { - SuccessMessage message(*loader_, target_, priority_, source_, userPayload_.get()); + DicomResourcesLoadedMessage message(*loader_, target_, priority_, source_, userPayload_.get()); loader_->BroadcastMessage(message); + + loader_->Notify(message); } boost::shared_ptr<DicomResourcesLoader> GetLoader() @@ -593,7 +595,7 @@ - const Orthanc::IDynamicObject& DicomResourcesLoader::SuccessMessage::GetUserPayload() const + const Orthanc::IDynamicObject& DicomResourcesLoadedMessage::GetUserPayload() const { if (userPayload_ == NULL) { @@ -609,13 +611,13 @@ boost::shared_ptr<DicomResourcesLoader> DicomResourcesLoader::Create(StoneApplication::Context& stone) { boost::shared_ptr<DicomResourcesLoader> result(new DicomResourcesLoader(stone)); - result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); - result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); - result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); - result->Register<ReadFileCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->ObserverBase<DicomResourcesLoader>::Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->ObserverBase<DicomResourcesLoader>::Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->ObserverBase<DicomResourcesLoader>::Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->ObserverBase<DicomResourcesLoader>::Register<ReadFileCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); #if ORTHANC_ENABLE_DCMTK == 1 - result->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->ObserverBase<DicomResourcesLoader>::Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); #endif return result;
--- a/OrthancStone/Sources/Loaders/DicomResourcesLoader.h Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/DicomResourcesLoader.h Wed Sep 30 21:15:01 2026 +0200 @@ -29,6 +29,7 @@ # error The macro ORTHANC_ENABLE_DCMTK must be defined #endif +#include "../Messages/TypedObservable.h" #include "../Oracle/HttpCommand.h" #include "../Oracle/OracleCommandExceptionMessage.h" #include "../Oracle/OrthancRestApiCommand.h" @@ -49,11 +50,67 @@ #if ORTHANC_ENABLE_DCMTK == 1 class ParseDicomFromFileCommand; #endif - + + class DicomResourcesLoader; + + // TODO Refactoring - Move to a separate file + class DicomResourcesLoadedMessage : public OriginMessage<DicomResourcesLoader> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + boost::shared_ptr<LoadedDicomResources> resources_; + int priority_; + const DicomSource& source_; + const Orthanc::IDynamicObject* userPayload_; + + public: + DicomResourcesLoadedMessage(const DicomResourcesLoader& origin, + boost::shared_ptr<LoadedDicomResources> resources, + int priority, + const DicomSource& source, + const Orthanc::IDynamicObject* userPayload) : + OriginMessage(origin), + resources_(resources), + priority_(priority), + source_(source), + userPayload_(userPayload) + { + } + + int GetPriority() const + { + return priority_; + } + + const boost::shared_ptr<LoadedDicomResources> GetResources() const + { + return resources_; + } + + const DicomSource& GetDicomSource() const + { + return source_; + } + + bool HasUserPayload() const + { + return userPayload_ != NULL; + } + + const Orthanc::IDynamicObject& GetUserPayload() const; + }; + + class DicomResourcesLoader : public ObserverBase<DicomResourcesLoader>, - public IObservable + public IObservable, + public New::IObservable, + public New::TypedObservable<DicomResourcesLoadedMessage> { + public: + using New::TypedObservable<DicomResourcesLoadedMessage>::Register; // TODO Refactoring - Presumably not needed + private: class Handler; class StringHandler; @@ -103,54 +160,6 @@ public: - class SuccessMessage : public OriginMessage<DicomResourcesLoader> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - boost::shared_ptr<LoadedDicomResources> resources_; - int priority_; - const DicomSource& source_; - const Orthanc::IDynamicObject* userPayload_; - - public: - SuccessMessage(const DicomResourcesLoader& origin, - boost::shared_ptr<LoadedDicomResources> resources, - int priority, - const DicomSource& source, - const Orthanc::IDynamicObject* userPayload) : - OriginMessage(origin), - resources_(resources), - priority_(priority), - source_(source), - userPayload_(userPayload) - { - } - - int GetPriority() const - { - return priority_; - } - - const boost::shared_ptr<LoadedDicomResources> GetResources() const - { - return resources_; - } - - const DicomSource& GetDicomSource() const - { - return source_; - } - - bool HasUserPayload() const - { - return userPayload_ != NULL; - } - - const Orthanc::IDynamicObject& GetUserPayload() const; - }; - - static boost::shared_ptr<DicomResourcesLoader> Create(StoneApplication::Context& stone); void ScheduleGetDicomWeb(boost::shared_ptr<LoadedDicomResources> target,
--- a/OrthancStone/Sources/Loaders/DicomVolumeLoader.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/DicomVolumeLoader.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -84,7 +84,7 @@ } - void DicomVolumeLoader::Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + void DicomVolumeLoader::Handle(const FrameDecodedMessage& message) { if (remaining_ == 0 || !message.HasUserPayload())
--- a/OrthancStone/Sources/Loaders/DicomVolumeLoader.h Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/DicomVolumeLoader.h Wed Sep 30 21:15:01 2026 +0200 @@ -43,7 +43,7 @@ DicomVolumeLoader(boost::shared_ptr<SeriesFramesLoader>& framesLoader, bool computeRange); - void Handle(const SeriesFramesLoader::FrameLoadedMessage& message); + void Handle(const FrameDecodedMessage& message); public: class VolumeReadyMessage : public OriginMessage<DicomVolumeLoader>
--- a/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -145,8 +145,9 @@ << image.GetHeight() << ", " << Orthanc::EnumerationToString(image.GetFormat()) << ", quality " << payload.GetQuality(); - FrameLoadedMessage message(*this, frameIndex, payload.GetQuality(), image, instance, parameters, payload.GetUserPayload()); + FrameDecodedMessage message(*this, frameIndex, payload.GetQuality(), image, instance, parameters, payload.GetUserPayload()); BroadcastMessage(message); + Notify(message); } @@ -291,7 +292,7 @@ } - Orthanc::IDynamicObject& SeriesFramesLoader::FrameLoadedMessage::GetUserPayload() const + Orthanc::IDynamicObject& FrameDecodedMessage::GetUserPayload() const { if (userPayload_) { @@ -321,13 +322,13 @@ boost::shared_ptr<SeriesFramesLoader> loader( new SeriesFramesLoader(context, instances, dicomDirPath, dicomDir)); - loader->Register<GetOrthancImageCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); - loader->Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); - loader->Register<HttpCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); - loader->Register<OrthancRestApiCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->ObserverBase<SeriesFramesLoader>::Register<GetOrthancImageCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->ObserverBase<SeriesFramesLoader>::Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->ObserverBase<SeriesFramesLoader>::Register<HttpCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->ObserverBase<SeriesFramesLoader>::Register<OrthancRestApiCommand::SuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); #if ORTHANC_ENABLE_DCMTK == 1 - loader->Register<ParseDicomSuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->ObserverBase<SeriesFramesLoader>::Register<ParseDicomSuccessMessage>(context.GetOracleObservable(), &SeriesFramesLoader::Handle); #endif return loader;
--- a/OrthancStone/Sources/Loaders/SeriesFramesLoader.h Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.h Wed Sep 30 21:15:01 2026 +0200 @@ -34,11 +34,83 @@ #include "SeriesOrderedFrames.h" namespace OrthancStone -{ +{ + class SeriesFramesLoader; + + // TODO Refactoring - Move to a separate file + class FrameDecodedMessage : public OriginMessage<SeriesFramesLoader> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + size_t frameIndex_; + unsigned int quality_; + const Orthanc::ImageAccessor& image_; + const Orthanc::DicomMap& instance_; + const DicomInstanceParameters& parameters_; + Orthanc::IDynamicObject* userPayload_; // Ownership is maintained by the caller + + public: + FrameDecodedMessage(const SeriesFramesLoader& loader, + size_t frameIndex, + unsigned int quality, + const Orthanc::ImageAccessor& image, + const Orthanc::DicomMap& instance, + const DicomInstanceParameters& parameters, + Orthanc::IDynamicObject* userPayload) : + OriginMessage(loader), + frameIndex_(frameIndex), + quality_(quality), + image_(image), + instance_(instance), + parameters_(parameters), + userPayload_(userPayload) + { + } + + size_t GetFrameIndex() const + { + return frameIndex_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + const Orthanc::ImageAccessor& GetImage() const + { + return image_; + } + + const Orthanc::DicomMap& GetInstance() const + { + return instance_; + } + + const DicomInstanceParameters& GetInstanceParameters() const + { + return parameters_; + } + + bool HasUserPayload() const + { + return userPayload_ != NULL; + } + + Orthanc::IDynamicObject& GetUserPayload() const; + }; + + class SeriesFramesLoader : public ObserverBase<SeriesFramesLoader>, - public IObservable + public IObservable, + public New::IObservable, + public New::TypedObservable<FrameDecodedMessage> { + public: + using New::TypedObservable<FrameDecodedMessage>::Register; // TODO Refactoring - Presumably not needed + private: class Payload; @@ -77,70 +149,6 @@ void Handle(const HttpCommand::SuccessMessage& message); public: - class FrameLoadedMessage : public OriginMessage<SeriesFramesLoader> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - size_t frameIndex_; - unsigned int quality_; - const Orthanc::ImageAccessor& image_; - const Orthanc::DicomMap& instance_; - const DicomInstanceParameters& parameters_; - Orthanc::IDynamicObject* userPayload_; // Ownership is maintained by the caller - - public: - FrameLoadedMessage(const SeriesFramesLoader& loader, - size_t frameIndex, - unsigned int quality, - const Orthanc::ImageAccessor& image, - const Orthanc::DicomMap& instance, - const DicomInstanceParameters& parameters, - Orthanc::IDynamicObject* userPayload) : - OriginMessage(loader), - frameIndex_(frameIndex), - quality_(quality), - image_(image), - instance_(instance), - parameters_(parameters), - userPayload_(userPayload) - { - } - - size_t GetFrameIndex() const - { - return frameIndex_; - } - - unsigned int GetQuality() const - { - return quality_; - } - - const Orthanc::ImageAccessor& GetImage() const - { - return image_; - } - - const Orthanc::DicomMap& GetInstance() const - { - return instance_; - } - - const DicomInstanceParameters& GetInstanceParameters() const - { - return parameters_; - } - - bool HasUserPayload() const - { - return userPayload_ != NULL; - } - - Orthanc::IDynamicObject& GetUserPayload() const; - }; - - static boost::shared_ptr<SeriesFramesLoader> Create(StoneApplication::Context& context, LoadedDicomResources& instances);
--- a/OrthancStone/Sources/Loaders/SeriesMetadataLoader.cpp Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/SeriesMetadataLoader.cpp Wed Sep 30 21:15:01 2026 +0200 @@ -52,7 +52,7 @@ } - void SeriesMetadataLoader::Handle(const DicomResourcesLoader::SuccessMessage& message) + void SeriesMetadataLoader::Handle(const DicomResourcesLoadedMessage& message) { assert(message.GetResources()); @@ -194,7 +194,7 @@ boost::shared_ptr<DicomResourcesLoader> loader(DicomResourcesLoader::Create(context)); boost::shared_ptr<SeriesMetadataLoader> obj(new SeriesMetadataLoader(loader)); - obj->Register<DicomResourcesLoader::SuccessMessage>(*loader, &SeriesMetadataLoader::Handle); + obj->Register<DicomResourcesLoadedMessage>(*loader, &SeriesMetadataLoader::Handle); return obj; }
--- a/OrthancStone/Sources/Loaders/SeriesMetadataLoader.h Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Loaders/SeriesMetadataLoader.h Wed Sep 30 21:15:01 2026 +0200 @@ -55,7 +55,7 @@ bool IsScheduledWithHigherPriority(const std::string& seriesInstanceUid, int priority) const; - void Handle(const DicomResourcesLoader::SuccessMessage& message); + void Handle(const DicomResourcesLoadedMessage& message); public: class SuccessMessage : public OriginMessage<SeriesMetadataLoader>
--- a/OrthancStone/Sources/Messages/IObservable.h Wed Sep 30 20:06:19 2026 +0200 +++ b/OrthancStone/Sources/Messages/IObservable.h Wed Sep 30 21:15:01 2026 +0200 @@ -26,6 +26,7 @@ #include "../StoneEnumerations.h" #include "ICallable.h" #include "IObserver.h" +#include "TypedObservable.h" #include <set> #include <map> @@ -53,4 +54,22 @@ void EmitMessage(boost::weak_ptr<IObserver> observer, const IMessage& message); }; + + + namespace New + { + class IObservable : public boost::noncopyable + { + public: + virtual ~IObservable() + { + } + + template <typename Message> + void Notify(const Message& message) + { + dynamic_cast< TypedObservable<Message>& >(*this).Dispatch(*this, message); + } + }; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Messages/TypedObservable.h Wed Sep 30 21:15:01 2026 +0200 @@ -0,0 +1,77 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "TypedObserver.h" + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <list> + +namespace OrthancStone +{ + namespace New + { + template <typename Message> + class TypedObservable : public boost::noncopyable + { + private: + typedef std::list< boost::weak_ptr< TypedObserver<Message> > > Content; + + Content content_; + + public: + void Register(const boost::shared_ptr< TypedObserver<Message> >& observer) + { + content_.push_back(observer); + } + + void Dispatch(const IObservable& observable, + const Message& message) + { + Content active; + + for (typename Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + boost::shared_ptr< TypedObserver<Message> > locked = it->lock(); + + if (locked) + { + locked->Handle(observable, message); + active.push_back(locked); + } + } + + content_.swap(active); + } + + template <typename Observable> + static void Bind(Observable& observable, + const boost::shared_ptr< TypedObserver<Message> >& observer) + { + static_cast< TypedObservable<Message>& >(observable).Register(observer); + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Messages/TypedObserver.h Wed Sep 30 21:15:01 2026 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace OrthancStone +{ + namespace New + { + class IObservable; + + template <typename Message> + class TypedObserver : public boost::noncopyable + { + public: + virtual ~TypedObserver() + { + } + + virtual void Handle(const IObservable& source, + const Message& message) = 0; + }; + } +}
