changeset 2312:ae031ceb217e refactoring

StoneApplication now manages the lifetime of the application
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Sep 2026 17:52:35 +0200
parents 448a74566925
children 46e57967b285
files Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp OrthancStone/Sources/Loaders/OracleScheduler.cpp OrthancStone/Sources/Loaders/OracleScheduler.h OrthancStone/Sources/StoneApplication.cpp OrthancStone/Sources/StoneApplication.h
diffstat 5 files changed, 346 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Sep 29 12:10:57 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Sep 29 17:52:35 2026 +0200
@@ -60,6 +60,7 @@
 #include "../../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
 #include "../../../../OrthancStone/Sources/StoneApplication.h"
 
+
 class Toto : public OrthancStone::IOracleClient
 {
 public:
@@ -89,7 +90,203 @@
   }
 };
 
-static boost::shared_ptr<Toto> toto_(new Toto);
+static boost::shared_ptr<Toto> toto_(new Toto);   // TODO REMOVE
+
+
+namespace OrthancStone
+{
+  class SingleWindowSdlApplication : public StoneApplication
+  {
+  private:
+    boost::shared_ptr<SdlViewport> viewport_;
+
+  protected:
+    virtual void RunInternal(const boost::shared_ptr<Context>& context)
+    {
+      CreateComponents(context, viewport_);
+
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      // SDL event loop
+      bool stop = false;
+      while (!stop)
+      {
+        bool paint = false;
+        SDL_Event event;
+
+        while (SDL_PollEvent(&event))
+        {
+          if (event.type == SDL_QUIT)
+          {
+            stop = true;
+            break;
+          }
+          else if (viewport_->IsRefreshEvent(event))
+          {
+            paint = true;
+          }
+          else if (event.type == SDL_WINDOWEVENT &&
+                   (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+                    event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
+          {
+            viewport_->UpdateSize(event.window.data1, event.window.data2);
+          }
+          else if (event.type == SDL_WINDOWEVENT &&
+                   (event.window.event == SDL_WINDOWEVENT_SHOWN ||
+                    event.window.event == SDL_WINDOWEVENT_EXPOSED))
+          {
+            std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+            lock->RefreshCanvasSize();
+          }
+          else if (event.type == SDL_KEYDOWN &&
+                   event.key.repeat == 0 /* Ignore key bounce */)
+          {
+            switch (event.key.keysym.sym)
+            {
+              case SDLK_f:
+                viewport_->ToggleMaximize();
+                break;
+
+              case SDLK_q:
+                stop = true;
+                break;
+
+              default:
+                HandleKeyDown(context, event.key);
+            }
+          }
+          else if (event.type == SDL_MOUSEBUTTONDOWN ||
+                   event.type == SDL_MOUSEMOTION ||
+                   event.type == SDL_MOUSEBUTTONUP)
+          {
+            std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+            if (lock->HasCompositor())
+            {
+              OrthancStone::PointerEvent p;
+              OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), event, keyboardState, scancodeCount);
+
+              switch (event.type)
+              {
+                case SDL_MOUSEBUTTONDOWN:
+                  // TODO
+                  break;
+
+                case SDL_MOUSEMOTION:
+                  // TODO
+                  break;
+
+                case SDL_MOUSEBUTTONUP:
+                  lock->GetController().HandleMouseRelease(p);
+                  lock->Invalidate();
+                  break;
+
+                default:
+                  throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+            }
+          }
+        }
+
+        if (paint)
+        {
+          viewport_->Paint();
+        }
+
+        // Small delay to avoid using 100% of CPU
+        SDL_Delay(1);
+      }
+    }
+
+    virtual void CreateComponents(const boost::shared_ptr<Context>& context,
+                                  const boost::shared_ptr<IViewport>& viewport) = 0;
+
+    virtual void HandleKeyDown(const boost::shared_ptr<Context>& context,
+                               const SDL_KeyboardEvent& key) = 0;
+
+  public:
+    SingleWindowSdlApplication(const Configuration& configuration,
+                               const std::string& title,
+                               unsigned int width,
+                               unsigned int height) :
+      StoneApplication(configuration)
+    {
+#if SAMPLE_USE_OPENGL == 1
+      viewport_ = OrthancStone::SdlOpenGLViewport::Create(title, width, height);
+#else
+      viewport_ = OrthancStone::SdlCairoViewport::Create(title, width, height);
+#endif
+
+      std::string font;
+      Orthanc::EmbeddedResources::GetFileResource(font, Orthanc::EmbeddedResources::UBUNTU_FONT);
+
+      {
+        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+        lock->GetCompositor().SetFont(0, font, 16, Orthanc::Encoding_Latin1);
+      }
+    }
+  };
+
+
+  class SimpleViewerApp : public SingleWindowSdlApplication
+  {
+  private:
+    boost::shared_ptr<Toto>        toto_;
+
+  protected:
+    virtual void CreateComponents(const boost::shared_ptr<Context>& context,
+                                  const boost::shared_ptr<IViewport>& viewport) ORTHANC_OVERRIDE
+    {
+      toto_.reset(new Toto);
+    }
+
+    virtual void HandleKeyDown(const boost::shared_ptr<Context>& context,
+                               const SDL_KeyboardEvent& key) ORTHANC_OVERRIDE
+    {
+      switch (key.keysym.sym)
+      {
+        case SDLK_b:
+        {
+          // TODO Refactoring
+          OrthancStone::IEnvironment& environment = context->GetEnvironment();
+          OrthancStone::IOracle& oracle = context->GetOracle();
+
+          oracle.Submit(environment, toto_, new OrthancStone::SleepOracleCommand(1000));
+
+          {
+            std::unique_ptr<OrthancStone::HttpCommand> command(new OrthancStone::HttpCommand);
+            command->SetUrl("http://ip-api.com/json/");
+            oracle.Submit(environment, toto_, command.release());
+          }
+
+          {
+            std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+            command->SetUri("/system/");
+            oracle.Submit(environment, toto_, command.release());
+          }
+
+          for (unsigned int i = 0; i < 10; i++)
+          {
+            DicomSource source;
+            source.SetDicomDirSource();
+            oracle.Submit(environment, toto_, new OrthancStone::ParseDicomFromFileCommand(source, "hand.dcm"));
+          }
+          break;
+        }
+
+        default:
+          break;
+      }
+    }
+
+  public:
+    SimpleViewerApp(const Configuration& configuration) :
+      SingleWindowSdlApplication(configuration, "Stone of Orthanc", 800, 600)
+    {
+    }
+  };
+}
 // END TODO Refactoring
 
 
@@ -204,7 +401,23 @@
     //Orthanc::Logging::EnableInfoLevel(true);
     //Orthanc::Logging::EnableTraceLevel(true);
 
+    Orthanc::WebServiceParameters orthancWebService;
+    orthancWebService.SetUrl(orthancUrl);
+
+    OrthancStone::StoneApplication::Configuration configuration;
+    configuration.SetRemoteOrthancParameters(orthancWebService);
+    configuration.SetDicomCacheSize(128 * 1024 * 1024);  // TODO Refactoring - Remove this
+    configuration.SetRootDirectory("/tmp");  // TODO Refactoring - Remove this
+
+    if (false)
     {
+      OrthancStone::SimpleViewerApp app(configuration);
+      app.Run();
+    }
+    else
+    {
+      OrthancStone::StoneApplication::Initialize(configuration);
+
 #if SAMPLE_USE_OPENGL == 1
       boost::shared_ptr<OrthancStone::SdlViewport> viewport =
         OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600);
@@ -213,20 +426,6 @@
         OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600);
 #endif
 
-#if SAMPLE_USE_ANNOTATIONS_LAYER != 1
-      boost::shared_ptr<OrthancStone::UndoStack> undoStack(new OrthancStone::UndoStack);
-#endif
-
-      Orthanc::WebServiceParameters orthancWebService;
-      orthancWebService.SetUrl(orthancUrl);
-
-      OrthancStone::StoneApplication::Configuration configuration;
-      configuration.SetRemoteOrthancParameters(orthancWebService);
-      configuration.SetDicomCacheSize(128 * 1024 * 1024);  // TODO Refactoring - Remove this
-      configuration.SetRootDirectory("/tmp");  // TODO Refactoring - Remove this
-
-      OrthancStone::StoneApplication::Initialize(configuration);
-
       OrthancStone::GenericLoadersContext context(1, 4, 1);
 
       {
@@ -238,7 +437,7 @@
           lock->GetCompositor().SetFont(0, font, 16, Orthanc::Encoding_Latin1);
 
 #if SAMPLE_USE_ANNOTATIONS_LAYER != 1
-          lock->GetController().SetUndoStack(undoStack);
+          lock->GetController().SetUndoStack(new OrthancStone::UndoStack);
 #endif
         }
 
@@ -596,6 +795,8 @@
           }
         }
       }
+
+      OrthancStone::StoneApplication::Finalize();
     }
   }
   catch (Orthanc::OrthancException& e)
@@ -620,7 +821,6 @@
   }
 
   OrthancStone::SdlWindow::GlobalFinalize();
-  OrthancStone::StoneApplication::Finalize();
   OrthancStone::StoneFinalize();
 
   return status;
--- a/OrthancStone/Sources/Loaders/OracleScheduler.cpp	Tue Sep 29 12:10:57 2026 +0200
+++ b/OrthancStone/Sources/Loaders/OracleScheduler.cpp	Tue Sep 29 17:52:35 2026 +0200
@@ -424,7 +424,7 @@
   }  
 
   
-  OracleScheduler::OracleScheduler(StoneApplication& application,
+  OracleScheduler::OracleScheduler(StoneApplication::Context& application,
                                    unsigned int maxHighPriority,
                                    unsigned int maxStandardPriority,
                                    unsigned int maxLowPriority) :
@@ -449,7 +449,7 @@
   }
 
     
-  boost::shared_ptr<OracleScheduler> OracleScheduler::Create(StoneApplication& application,
+  boost::shared_ptr<OracleScheduler> OracleScheduler::Create(StoneApplication::Context& application,
                                                              unsigned int maxHighPriority,
                                                              unsigned int maxStandardPriority,
                                                              unsigned int maxLowPriority)
--- a/OrthancStone/Sources/Loaders/OracleScheduler.h	Tue Sep 29 12:10:57 2026 +0200
+++ b/OrthancStone/Sources/Loaders/OracleScheduler.h	Tue Sep 29 17:52:35 2026 +0200
@@ -66,7 +66,7 @@
 
     typedef std::multimap<int, ScheduledCommand*>  Queue;
 
-    StoneApplication&  application_;
+    StoneApplication::Context&  application_;
     Queue          standardPriorityQueue_;
     Queue          highPriorityQueue_;
     Queue          lowPriorityQueue_;
@@ -112,18 +112,18 @@
 
     void Handle(const OracleCommandExceptionMessage& message);
 
-    OracleScheduler(StoneApplication& application,
+    OracleScheduler(StoneApplication::Context& application,
                     unsigned int maxHighPriority,
                     unsigned int maxStandardPriority,
                     unsigned int maxLowPriority);
     
   public:
-    static boost::shared_ptr<OracleScheduler> Create(StoneApplication& application)
+    static boost::shared_ptr<OracleScheduler> Create(StoneApplication::Context& application)
     {
       return Create(application, 1, 4, 1);
     }
 
-    static boost::shared_ptr<OracleScheduler> Create(StoneApplication& application,
+    static boost::shared_ptr<OracleScheduler> Create(StoneApplication::Context& application,
                                                      unsigned int maxHighPriority,
                                                      unsigned int maxStandardPriority,
                                                      unsigned int maxLowPriority);
--- a/OrthancStone/Sources/StoneApplication.cpp	Tue Sep 29 12:10:57 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.cpp	Tue Sep 29 17:52:35 2026 +0200
@@ -23,6 +23,8 @@
 
 #include "StoneApplication.h"
 
+#include "StoneException.h"
+
 #include <Compatibility.h>
 #include <Logging.h>
 #include <MultiThreading/Mutex.h>
@@ -117,7 +119,7 @@
 
 
 #if ORTHANC_STONE_TARGET_PLATFORM_WASM == 1
-  class StoneApplication::PImpl
+  class StoneApplication::Context::PImpl
   {
   private:
     class Emitter : public IMessageEmitter  // TODO Refactoring - Remove this
@@ -180,7 +182,7 @@
 
 
 #if ORTHANC_STONE_TARGET_PLATFORM_NATIVE == 1
-  class StoneApplication::PImpl
+  class StoneApplication::Context::PImpl
   {
   private:
     class Emitter : public IMessageEmitter  // TODO Refactoring - Remove this
@@ -262,10 +264,7 @@
 #endif
 
 
-  static Orthanc::Mutex                     applicationMutex_;
-  static std::unique_ptr<StoneApplication>  application_;
-
-  StoneApplication::StoneApplication(const Configuration& configuration)
+  StoneApplication::Context::Context(const Configuration& configuration)
   {
 #if ORTHANC_STONE_TARGET_PLATFORM_WASM == 1
     pimpl_ = new PImpl(configuration);
@@ -277,14 +276,86 @@
   }
 
 
-  StoneApplication::~StoneApplication()
+  StoneApplication::Context::~Context()
   {
     assert(pimpl_ != NULL);
     delete pimpl_;
   }
 
 
-  StoneApplication& StoneApplication::GetInstance()
+  IEnvironment& StoneApplication::Context::GetEnvironment()
+  {
+    assert(pimpl_ != NULL);
+    return pimpl_->GetEnvironment();
+  }
+
+
+  IOracle& StoneApplication::Context::GetOracle()
+  {
+    assert(pimpl_ != NULL);
+    return pimpl_->GetOracle();
+  }
+
+
+  void StoneApplication::Context::EmitMessage(boost::weak_ptr<IObserver> observer,
+                                              const IMessage& message)
+  {
+    assert(pimpl_ != NULL);
+    return pimpl_->GetMessageEmitter().EmitMessage(observer, message);
+  }
+
+
+  IObservable& StoneApplication::Context::GetOracleObservable()
+  {
+    assert(pimpl_ != NULL);
+    return pimpl_->GetOracleObservable();
+  }
+
+
+  bool StoneApplication::Run()
+  {
+    try
+    {
+      boost::shared_ptr<Context> context(new Context(configuration_));
+
+      assert(context.get() != NULL);
+      assert(context->pimpl_ != NULL);
+
+      context->pimpl_->Start();
+
+      RunInternal(context);
+
+      context->pimpl_->Stop();
+
+      return true;
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "OrthancException: " << e.What();
+      return false;
+    }
+    catch (StoneException& e)
+    {
+      LOG(ERROR) << "StoneException: " << e.What();
+      return false;
+    }
+    catch (std::runtime_error& e)
+    {
+      LOG(ERROR) << "Runtime error: " << e.what();
+      return false;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Native exception";
+      return false;
+    }
+  }
+
+
+  static Orthanc::Mutex                              applicationMutex_;  // TODO Refactoring - Remove this
+  static std::unique_ptr<StoneApplication::Context>  application_;  // TODO Refactoring - Remove this
+
+  StoneApplication::Context& StoneApplication::GetInstance()
   {
     Orthanc::Mutex::ScopedLock lock(applicationMutex_);
 
@@ -303,7 +374,7 @@
 
     if (application_.get() == NULL)
     {
-      application_.reset(new StoneApplication(configuration));
+      application_.reset(new StoneApplication::Context(configuration));
       application_->pimpl_->Start();
     }
     else
@@ -323,33 +394,4 @@
       application_.reset(NULL);
     }
   }
-
-
-  IEnvironment& StoneApplication::GetEnvironment()
-  {
-    assert(pimpl_ != NULL);
-    return pimpl_->GetEnvironment();
-  }
-
-
-  IOracle& StoneApplication::GetOracle()
-  {
-    assert(pimpl_ != NULL);
-    return pimpl_->GetOracle();
-  }
-
-
-  void StoneApplication::EmitMessage(boost::weak_ptr<IObserver> observer,
-                                     const IMessage& message)
-  {
-    assert(pimpl_ != NULL);
-    return pimpl_->GetMessageEmitter().EmitMessage(observer, message);
-  }
-
-
-  IObservable& StoneApplication::GetOracleObservable()
-  {
-    assert(pimpl_ != NULL);
-    return pimpl_->GetOracleObservable();
-  }
 }
--- a/OrthancStone/Sources/StoneApplication.h	Tue Sep 29 12:10:57 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.h	Tue Sep 29 17:52:35 2026 +0200
@@ -34,7 +34,7 @@
 
 namespace OrthancStone
 {
-  class StoneApplication : public IMessageEmitter  // TODO Refactoring - Remove this
+  class StoneApplication : public boost::noncopyable
   {
   public:
     class Configuration
@@ -102,30 +102,55 @@
       }
     };
 
-  private:
-    class Emitter;  // TODO Refactoring - Remove this
+
+    class Context : public IMessageEmitter  // TODO Refactoring - Remove this
+    {
+      friend class StoneApplication;
+
+    private:
+      class Emitter;  // TODO Refactoring - Remove this
+
+      class PImpl;
+      PImpl* pimpl_;
+
+    public:
+      Context(const Configuration& configuration);
+
+      ~Context();
 
-    class PImpl;
-    PImpl* pimpl_;
+      IEnvironment& GetEnvironment();
+
+      IOracle& GetOracle();
+
+      virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
+                               const IMessage& message) ORTHANC_OVERRIDE;
 
-    StoneApplication(const Configuration& configuration);
+      IObservable& GetOracleObservable();  // TODO Refactoring - Remove this
+    };
+
+
+  private:
+    Configuration  configuration_;
+
+  protected:
+    virtual void RunInternal(const boost::shared_ptr<Context>& context) = 0;
 
   public:
-    static void Initialize(const Configuration& configuration);
-
-    static StoneApplication& GetInstance();
-
-    static void Finalize();
-
-    ~StoneApplication();
+    StoneApplication(const Configuration& configuration) :
+      configuration_(configuration)
+    {
+    }
 
-    IEnvironment& GetEnvironment();
+    virtual ~StoneApplication()
+    {
+    }
 
-    IOracle& GetOracle();
+    bool Run();
 
-    virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
-                             const IMessage& message) ORTHANC_OVERRIDE;
+    static void Initialize(const Configuration& configuration);  // TODO Refactoring - Remove this
 
-    IObservable& GetOracleObservable();  // TODO Refactoring - Remove this
+    static StoneApplication::Context& GetInstance();  // TODO Refactoring - Remove this
+
+    static void Finalize();  // TODO Refactoring - Remove this
   };
 }