diff Framework/Widgets/WorldSceneWidget.cpp @ 0:351ab0da0150

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Oct 2016 15:34:11 +0200
parents
children ff1e935768e7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Fri Oct 14 15:34:11 2016 +0200
@@ -0,0 +1,461 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WorldSceneWidget.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  static void MapMouseToScene(double& sceneX,
+                              double& sceneY,
+                              const ViewportGeometry& view,
+                              int mouseX,
+                              int mouseY)
+  {
+    // Take the center of the pixel
+    double x, y;
+    x = static_cast<double>(mouseX) + 0.5;
+    y = static_cast<double>(mouseY) + 0.5;
+
+    view.MapDisplayToScene(sceneX, sceneY, x, y);
+  }
+
+
+  struct WorldSceneWidget::ViewChangeFunctor
+  {
+    const ViewportGeometry& view_;
+
+    ViewChangeFunctor(const ViewportGeometry& view) :
+      view_(view)
+    {
+    }
+
+    void operator() (IWorldObserver& observer,
+                     const WorldSceneWidget& source)
+    {
+      observer.NotifyViewChange(source, view_);
+    }
+  };
+
+
+  struct WorldSceneWidget::SizeChangeFunctor
+  {
+    ViewportGeometry& view_;
+
+    SizeChangeFunctor(ViewportGeometry& view) :
+      view_(view)
+    {
+    }
+
+    void operator() (IWorldObserver& observer,
+                     const WorldSceneWidget& source)
+    {
+      observer.NotifySizeChange(source, view_);
+    }
+  };
+
+
+  class WorldSceneWidget::SceneMouseTracker : public IMouseTracker
+  {
+  private:
+    ViewportGeometry                       view_;
+    std::auto_ptr<IWorldSceneMouseTracker>  tracker_;
+      
+  public:
+    SceneMouseTracker(const ViewportGeometry& view,
+                      IWorldSceneMouseTracker* tracker) :
+      view_(view),
+      tracker_(tracker)
+    {
+      assert(tracker != NULL);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& target)
+    {
+      CairoSurface surface(target);
+      CairoContext context(surface); 
+      view_.ApplyTransform(context);
+      tracker_->Render(context, view_.GetZoom());
+    }
+
+    virtual void MouseUp() 
+    {
+      tracker_->MouseUp();
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      double sceneX, sceneY;
+      MapMouseToScene(sceneX, sceneY, view_, x, y);
+      tracker_->MouseMove(sceneX, sceneY);
+    }
+  };
+
+
+  class WorldSceneWidget::PanMouseTracker : public IMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;  
+    double             previousPanX_;
+    double             previousPanY_;
+    double             downX_;
+    double             downY_;
+
+  public:
+    PanMouseTracker(WorldSceneWidget& that,
+                    int x,
+                    int y) :
+      that_(that),
+      downX_(x),
+      downY_(y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      locker.GetValue().GetPan(previousPanX_, previousPanY_);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+    }
+
+    virtual void MouseUp() 
+    {
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      locker.GetValue().SetPan(previousPanX_ + x - downX_,
+                               previousPanY_ + y - downY_);
+
+      ViewChangeFunctor functor(locker.GetValue());
+      that_.observers_.Notify(&that_, functor);
+    }
+  };
+
+
+  class WorldSceneWidget::ZoomMouseTracker : public IMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;  
+    int                downX_;
+    int                downY_;
+    double             centerX_;
+    double             centerY_;
+    double             oldZoom_;
+
+  public:
+    ZoomMouseTracker(WorldSceneWidget&  that,
+                     int x,
+                     int y) :
+      that_(that),
+      downX_(x),
+      downY_(y)
+    {
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+      oldZoom_ = locker.GetValue().GetZoom();
+      MapMouseToScene(centerX_, centerY_, locker.GetValue(), downX_, downY_);
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+    }
+
+    virtual void MouseUp() 
+    {
+    }
+
+    virtual void MouseMove(int x, 
+                           int y)
+    {
+      static const double MIN_ZOOM = -4;
+      static const double MAX_ZOOM = 4;
+
+      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
+
+      if (locker.GetValue().GetDisplayHeight() <= 3)
+      {
+        return;   // Cannot zoom on such a small image
+      }
+
+      double dy = (static_cast<double>(y - downY_) / 
+                   static_cast<double>(locker.GetValue().GetDisplayHeight() - 1)); // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      locker.GetValue().SetZoom(oldZoom_ * z);
+
+      // Correct the pan so that the original click point is kept at
+      // the same location on the display
+      double panX, panY;
+      locker.GetValue().GetPan(panX, panY);
+
+      int tx, ty;
+      locker.GetValue().MapSceneToDisplay(tx, ty, centerX_, centerY_);
+      locker.GetValue().SetPan(panX + static_cast<double>(downX_ - tx),
+                               panY + static_cast<double>(downY_ - ty));
+
+      ViewChangeFunctor functor(locker.GetValue());
+      that_.observers_.Notify(&that_, functor);
+    }
+  };
+
+
+  void WorldSceneWidget::UpdateStep()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  bool WorldSceneWidget::RenderCairo(CairoContext& context)
+  {
+    ViewportGeometry view;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      view = locker.GetValue();
+    }
+
+    view.ApplyTransform(context);
+
+    return RenderScene(context, view);
+  }
+
+
+  void WorldSceneWidget::RenderMouseOverCairo(CairoContext& context,
+                                              int x,
+                                              int y)
+  {
+    ViewportGeometry view = GetView();
+    view.ApplyTransform(context);
+
+    double sceneX, sceneY;
+    MapMouseToScene(sceneX, sceneY, view, x, y);
+    RenderSceneMouseOver(context, view, sceneX, sceneY);
+  }
+
+
+  void WorldSceneWidget::SetSceneExtent(SharedValue<ViewportGeometry>::Locker& locker)
+  {
+    double x1, y1, x2, y2;
+    GetSceneExtent(x1, y1, x2, y2);
+    locker.GetValue().SetSceneExtent(x1, y1, x2, y2);
+  }
+
+
+  void WorldSceneWidget::SetSize(unsigned int width,
+                                 unsigned int height)
+  {
+    CairoWidget::SetSize(width, height);
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      locker.GetValue().SetDisplaySize(width, height);
+
+      if (observers_.IsEmpty())
+      {
+        // Without a size observer, use the default view
+        locker.GetValue().SetDefaultView();
+      }
+      else
+      {
+        // With a size observer, let it decide which view to use
+        SizeChangeFunctor functor(locker.GetValue());
+        observers_.Notify(this, functor);
+      }
+    }
+  }
+
+
+  void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor)
+  {
+    if (IsStarted())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    interactor_ = &interactor;
+  }
+
+
+  void WorldSceneWidget::Start()
+  {
+    ViewportGeometry geometry;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      SetSceneExtent(locker);
+      geometry = locker.GetValue();
+    }
+
+    WidgetBase::Start();
+
+    ViewChangeFunctor functor(geometry);
+    observers_.Notify(this, functor);
+  }
+      
+
+  void WorldSceneWidget::SetDefaultView()
+  {
+    ViewportGeometry geometry;
+
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      SetSceneExtent(locker);
+      locker.GetValue().SetDefaultView();
+      geometry = locker.GetValue();
+    }
+
+    NotifyChange();
+
+    ViewChangeFunctor functor(geometry);
+    observers_.Notify(this, functor);
+  }
+
+
+  void WorldSceneWidget::SetView(const ViewportGeometry& view)
+  {
+    {
+      SharedValue<ViewportGeometry>::Locker locker(view_);
+      locker.GetValue() = view;
+    }
+
+    NotifyChange();
+
+    ViewChangeFunctor functor(view);
+    observers_.Notify(this, functor);
+  }
+
+
+  ViewportGeometry WorldSceneWidget::GetView()
+  {
+    SharedValue<ViewportGeometry>::Locker locker(view_);
+    return locker.GetValue();
+  }
+
+
+  IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button,
+                                                      int x,
+                                                      int y,
+                                                      KeyboardModifiers modifiers)
+  {
+    ViewportGeometry view = GetView();
+
+    double sceneX, sceneY;
+    MapMouseToScene(sceneX, sceneY, view, x, y);
+
+    std::auto_ptr<IWorldSceneMouseTracker> tracker(CreateMouseSceneTracker(view, button, sceneX, sceneY, modifiers));
+    if (tracker.get() != NULL)
+    {
+      return new SceneMouseTracker(view, tracker.release());
+    }
+
+    switch (button)
+    {
+      case MouseButton_Middle:
+        return new PanMouseTracker(*this, x, y);
+
+      case MouseButton_Right:
+        return new ZoomMouseTracker(*this, x, y);
+
+      default:
+        return NULL;
+    }
+  }
+
+
+  void WorldSceneWidget::RenderSceneMouseOver(CairoContext& context,
+                                              const ViewportGeometry& view,
+                                              double x,
+                                              double y)
+  {
+    if (interactor_)
+    {
+      interactor_->MouseOver(context, *this, GetSlice(), view, x, y, GetStatusBar());
+    }
+  }
+
+  IWorldSceneMouseTracker* WorldSceneWidget::CreateMouseSceneTracker(const ViewportGeometry& view,
+                                                                     MouseButton button,
+                                                                     double x,
+                                                                     double y,
+                                                                     KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      return interactor_->CreateMouseTracker(*this, GetSlice(), view, button, x, y, GetStatusBar());
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void WorldSceneWidget::MouseWheel(MouseWheelDirection direction,
+                                    int x,
+                                    int y,
+                                    KeyboardModifiers modifiers) 
+  {
+    if (interactor_)
+    {
+      interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar());
+    }
+  }
+
+
+  void WorldSceneWidget::KeyPressed(char key,
+                                    KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      interactor_->KeyPressed(*this, key, modifiers, GetStatusBar());
+    }
+  }
+}