changeset 865:a29c13497557

Added operators to ScenePoint2D + highlight support on MouseOver for measuring tools
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 25 Jun 2019 15:24:13 +0200
parents ae3eccd0f545
children c71ef52602a0
files Framework/Scene2D/ScenePoint2D.h Framework/Scene2DViewport/AngleMeasureTool.cpp Framework/Scene2DViewport/AngleMeasureTool.h Framework/Scene2DViewport/LineMeasureTool.cpp Framework/Scene2DViewport/LineMeasureTool.h Framework/Scene2DViewport/MeasureTool.h Framework/Scene2DViewport/MeasureToolsToolbox.cpp Framework/Scene2DViewport/MeasureToolsToolbox.h Framework/Scene2DViewport/ViewportController.cpp Framework/Scene2DViewport/ViewportController.h
diffstat 10 files changed, 333 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/ScenePoint2D.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2D/ScenePoint2D.h	Tue Jun 25 15:24:13 2019 +0200
@@ -13,7 +13,7 @@
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Affero General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/AffineTransform2D.h"
-
+#include "../Toolbox/LinearAlgebra.h"
 
 namespace OrthancStone
 {
@@ -40,7 +40,7 @@
     }
 
     ScenePoint2D(double x,
-                 double y) :
+      double y) :
       x_(x),
       y_(y)
     {
@@ -63,5 +63,74 @@
       t.Apply(x, y);
       return ScenePoint2D(x, y);
     }
+
+    const ScenePoint2D operator-(const ScenePoint2D& a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ - a.x_;
+      v.y_ = y_ - a.y_;
+
+      return v;
+    }
+
+    const ScenePoint2D operator*(double a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ * a;
+      v.y_ = y_ * a;
+
+      return v;
+    }
+
+    static double Dot(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      return a.x_ * b.x_ + a.y_ * b.y_;
+    }
+
+    static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      ScenePoint2D n = b - a;
+      return Dot(n, n);
+    }
+
+    /**
+    Distance from point p to [a,b] segment
+
+    Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
+    */
+    static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
+    {
+      ScenePoint2D n = b - a;
+      ScenePoint2D pa = a - p;
+
+      double c = Dot(n, pa);
+
+      // Closest point is a
+      if (c > 0.0)
+        return Dot(pa, pa);
+
+      ScenePoint2D bp = p - b;
+
+      // Closest point is b
+      if (Dot(n, bp) > 0.0)
+        return Dot(bp, bp);
+
+      // if segment length is very short, we approximate distance to the
+      // distance with a
+      double nq = Dot(n, n);
+      if (LinearAlgebra::IsCloseToZero(nq))
+      {
+        // segment is very small: approximate distance from point to segment
+        // with distance from p to a
+        return Dot(pa, pa);
+      }
+      else
+      {
+        // Closest point is between a and b
+        ScenePoint2D e = pa - n * (c / nq);
+        return Dot(e, e);
+      }
+    }
   };
 }
+
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Tue Jun 25 15:24:13 2019 +0200
@@ -43,6 +43,7 @@
     MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
     : MeasureTool(broker, controllerW)
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5))
+    , angleHighlightArea_(AngleHighlightArea_None)
   {
 
   }
@@ -75,10 +76,68 @@
     RefreshScene();
   }
 
+  void AngleMeasureTool::SetAngleHighlightArea(AngleHighlightArea area)
+  {
+    if (angleHighlightArea_ != area)
+    {
+      angleHighlightArea_ = area;
+      RefreshScene();
+    }
+  }
+
+  void AngleMeasureTool::ResetHighlightState()
+  {
+    SetAngleHighlightArea(AngleHighlightArea_None);
+  }
+
+  void AngleMeasureTool::Highlight(ScenePoint2D p)
+  {
+    AngleHighlightArea angleHighlightArea = AngleHitTest(p);
+    SetAngleHighlightArea(angleHighlightArea);
+  }
+
+  AngleMeasureTool::AngleHighlightArea AngleMeasureTool::AngleHitTest(ScenePoint2D p) const
+  {
+    const double pixelToScene =
+      GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+    const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
+
+    {
+      const double sqDistanceFromSide1End = ScenePoint2D::SquaredDistancePtPt(p, side1End_);
+      if (sqDistanceFromSide1End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side1End;
+    }
+
+    {
+      const double sqDistanceFromSide2End = ScenePoint2D::SquaredDistancePtPt(p, side2End_);
+      if (sqDistanceFromSide2End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side2End;
+    }
+
+    {
+      const double sqDistanceFromCenter = ScenePoint2D::SquaredDistancePtPt(p, center_);
+      if (sqDistanceFromCenter <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Center;
+    }
+
+    {
+      const double sqDistanceFromSide1 = ScenePoint2D::SquaredDistancePtSegment(center_, side1End_, p);
+      if (sqDistanceFromSide1 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side1;
+    }
+
+    {
+      const double sqDistanceFromSide2 = ScenePoint2D::SquaredDistancePtSegment(center_, side2End_, p);
+      if (sqDistanceFromSide2 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+        return AngleHighlightArea_Side2;
+    }
+
+    return AngleHighlightArea_None;
+  }
 
   bool AngleMeasureTool::HitTest(ScenePoint2D p) const
   {
-    throw std::logic_error("The method or operation is not implemented.");
+    return AngleHitTest(p) != AngleHighlightArea_None;
   }
 
   void AngleMeasureTool::SetCenter(ScenePoint2D pt)
@@ -101,7 +160,8 @@
           PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
           polylineLayer->ClearAllChains();
 
-          const Color color(0, 183, 17);
+          const Color color(TOOL_ANGLE_LINES_COLOR_RED, TOOL_ANGLE_LINES_COLOR_GREEN, TOOL_ANGLE_LINES_COLOR_BLUE);
+          const Color highlightColor(TOOL_ANGLE_LINES_HL_COLOR_RED, TOOL_ANGLE_LINES_HL_COLOR_GREEN, TOOL_ANGLE_LINES_HL_COLOR_BLUE);
 
           // sides
           {
@@ -109,13 +169,20 @@
               PolylineSceneLayer::Chain chain;
               chain.push_back(side1End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false, color);
+
+              if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2))
+                polylineLayer->AddChain(chain, false, highlightColor);
+              else
+                polylineLayer->AddChain(chain, false, color);
             }
             {
               PolylineSceneLayer::Chain chain;
               chain.push_back(side2End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false, color);
+              if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2))
+                polylineLayer->AddChain(chain, false, highlightColor);
+              else
+                polylineLayer->AddChain(chain, false, color);
             }
           }
 
@@ -126,14 +193,23 @@
               //TODO: take DPI into account
               AddSquare(chain, GetScene(), side1End_, 
                 GetController()->GetHandleSideLengthS());
-              polylineLayer->AddChain(chain, true, color);
+              
+              if (angleHighlightArea_ == AngleHighlightArea_Side1End)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
+              
             }
             {
               PolylineSceneLayer::Chain chain;
               //TODO: take DPI into account
               AddSquare(chain, GetScene(), side2End_, 
                 GetController()->GetHandleSideLengthS());
-              polylineLayer->AddChain(chain, true, color);
+
+              if (angleHighlightArea_ == AngleHighlightArea_Side2End)
+                  polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
           }
 
@@ -143,7 +219,10 @@
 
             AddShortestArc(chain, side1End_, center_, side2End_,
                            controller->GetAngleToolArcRadiusS());
-            polylineLayer->AddChain(chain, false, color);
+            if (angleHighlightArea_ == AngleHighlightArea_Center)
+              polylineLayer->AddChain(chain, false, highlightColor);
+            else
+              polylineLayer->AddChain(chain, false, color);
           }
         }
         {
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Tue Jun 25 15:24:13 2019 +0200
@@ -47,18 +47,34 @@
     void SetCenter(ScenePoint2D start);
     void SetSide2End(ScenePoint2D start);
 
+    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE;
+    virtual void ResetHighlightState() ORTHANC_OVERRIDE;
 
-    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    enum AngleHighlightArea
+    {
+      AngleHighlightArea_None,
+      AngleHighlightArea_Side1End,
+      AngleHighlightArea_Side1,
+      AngleHighlightArea_Side2End,
+      AngleHighlightArea_Side2,
+      AngleHighlightArea_Center
+    };
+
+
+    AngleHighlightArea AngleHitTest(ScenePoint2D p) const;
 
   private:
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
+    void                SetAngleHighlightArea(AngleHighlightArea area);
 
   private:
-    ScenePoint2D    side1End_;
-    ScenePoint2D    side2End_;
-    ScenePoint2D    center_;
+    ScenePoint2D                    side1End_;
+    ScenePoint2D                    side2End_;
+    ScenePoint2D                    center_;
     boost::shared_ptr<LayerHolder>  layerHolder_;
+    AngleHighlightArea              angleHighlightArea_;
   };
 }
 
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Tue Jun 25 15:24:13 2019 +0200
@@ -33,6 +33,7 @@
     MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
     : MeasureTool(broker, controllerW)
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5))
+    , lineHighlightArea_(LineHighlightArea_None)
   {
 
   }
@@ -72,26 +73,50 @@
     RefreshScene();
   }
 
-  
+  void LineMeasureTool::SetLineHighlightArea(LineHighlightArea area)
+  {
+    if (lineHighlightArea_ != area)
+    {
+      lineHighlightArea_ = area;
+      RefreshScene();
+    }
+  }
 
-  bool LineMeasureTool::HitTest(ScenePoint2D p) const
+  void LineMeasureTool::ResetHighlightState()
+  {
+    SetLineHighlightArea(LineHighlightArea_None);
+  }
+ 
+  void LineMeasureTool::Highlight(ScenePoint2D p)
+  {
+    LineHighlightArea lineHighlightArea = LineHitTest(p);
+    SetLineHighlightArea(lineHighlightArea);
+  }
+
+  LineMeasureTool::LineHighlightArea LineMeasureTool::LineHitTest(ScenePoint2D p) const
   {
     const double pixelToScene =
       GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+    const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
 
-    // the hit test will return true if the supplied point (in scene coords)
-    // is close to the handle or to the line.
-
-    // since the handle is small, a nice approximation is to defined this
-    // as a threshold on the distance between the point and the handle center.
+    const double sqDistanceFromStart = ScenePoint2D::SquaredDistancePtPt(p, start_);
+    if (sqDistanceFromStart <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_Start;
+    
+    const double sqDistanceFromEnd = ScenePoint2D::SquaredDistancePtPt(p, end_);
+    if (sqDistanceFromEnd <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_End;
 
-    // this threshold is defined as a constant value in CANVAS units.
-
+    const double sqDistanceFromPtSegment = ScenePoint2D::SquaredDistancePtSegment(start_, end_, p);
+    if (sqDistanceFromPtSegment <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD)
+      return LineHighlightArea_Segment;
 
-    // line equation from two points (non-normalized)
-    // (y0-y1)*x + (x1-x0)*xy + (x0*y1 - x1*y0) = 0
-    // 
-    return false;
+    return LineHighlightArea_None;
+  }
+
+  bool LineMeasureTool::HitTest(ScenePoint2D p) const
+  {
+    return LineHitTest(p) != LineHighlightArea_None;
   }
 
   void LineMeasureTool::RefreshScene()
@@ -112,11 +137,18 @@
                             TOOL_LINES_COLOR_GREEN, 
                             TOOL_LINES_COLOR_BLUE);
 
+          const Color highlightColor(TOOL_LINES_HL_COLOR_RED,
+                                     TOOL_LINES_HL_COLOR_GREEN,
+                                     TOOL_LINES_HL_COLOR_BLUE);
+
           {
             PolylineSceneLayer::Chain chain;
             chain.push_back(start_);
             chain.push_back(end_);
-            polylineLayer->AddChain(chain, false, color);
+            if(lineHighlightArea_ == LineHighlightArea_Segment)
+              polylineLayer->AddChain(chain, false, highlightColor);
+            else
+              polylineLayer->AddChain(chain, false, color);
           }
 
           // handles
@@ -128,7 +160,10 @@
               AddSquare(chain, GetScene(), start_, 
                 GetController()->GetHandleSideLengthS());
               
-              polylineLayer->AddChain(chain, true, color);
+              if (lineHighlightArea_ == LineHighlightArea_Start)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
 
             {
@@ -138,7 +173,10 @@
               AddSquare(chain, GetScene(), end_, 
                 GetController()->GetHandleSideLengthS());
               
-              polylineLayer->AddChain(chain, true, color);
+              if (lineHighlightArea_ == LineHighlightArea_End)
+                polylineLayer->AddChain(chain, true, highlightColor);
+              else
+                polylineLayer->AddChain(chain, true, color);
             }
           }
 
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Tue Jun 25 15:24:13 2019 +0200
@@ -47,16 +47,33 @@
 
 
     virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+    virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE;
+    virtual void ResetHighlightState() ORTHANC_OVERRIDE;
+
+    enum LineHighlightArea
+    {
+      LineHighlightArea_None,
+      LineHighlightArea_Start,
+      LineHighlightArea_End,
+      LineHighlightArea_Segment
+    };
+
+
+    LineHighlightArea LineHitTest(ScenePoint2D p) const;
 
   private:
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
+    void                SetLineHighlightArea(LineHighlightArea area);
+
+  private:
 
   private:
-    ScenePoint2D   start_;
-    ScenePoint2D   end_;
-    boost::shared_ptr<LayerHolder> layerHolder_;
-    int            baseLayerIndex_;
+    ScenePoint2D                    start_;
+    ScenePoint2D                    end_;
+    boost::shared_ptr<LayerHolder>  layerHolder_;
+    int                             baseLayerIndex_;
+    LineHighlightArea               lineHighlightArea_;
   };
 
 }
--- a/Framework/Scene2DViewport/MeasureTool.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.h	Tue Jun 25 15:24:13 2019 +0200
@@ -71,6 +71,18 @@
     measuring tool
     */
     virtual bool HitTest(ScenePoint2D p) const = 0;
+
+    /**
+    Will change the measuring tool to provide visual feedback on the GUI 
+    element that is in the pointer hit zone
+    */
+    virtual void Highlight(ScenePoint2D p) = 0;
+
+    /**
+    This function must reset the visual highlighted hot zone feedback
+    */
+    virtual void ResetHighlightState() = 0;
+
   protected:
     MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
 
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Tue Jun 25 15:24:13 2019 +0200
@@ -322,4 +322,11 @@
         p.GetY() + yoffsets[i] * pixelToScene);
     }
   }
+
+  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p)
+  {
+    os << "x = " << p.GetX() << " , y = " << p.GetY();
+    return os;
+  }
+
 }
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Tue Jun 25 15:24:13 2019 +0200
@@ -33,7 +33,7 @@
   void AddSquare(PolylineSceneLayer::Chain& chain,
     boost::shared_ptr<const Scene2D>     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLengthS);
+    const double& sideLengthS);
 
   /**
     Creates an arc centered on c that goes
@@ -48,23 +48,23 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const ScenePoint2D&         p1
-    , const ScenePoint2D&         c
-    , const ScenePoint2D&         p2
-    , const double&               radiusS
+    PolylineSceneLayer::Chain& chain
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double& radiusS
     , const int                   subdivisionsCount = 63);
 
   /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from
     start angle to end angle, by following the shortest arc.
 
     Warning: the existing chain content will be wiped out.
   */
   void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const ScenePoint2D&         centerS
-    , const double&               radiusS
+    PolylineSceneLayer::Chain& chain
+    , const ScenePoint2D& centerS
+    , const double& radiusS
     , const double                startAngleRad
     , const double                endAngleRad
     , const int                   subdivisionsCount = 63);
@@ -79,24 +79,24 @@
       - so that r2 belongs to the p2,c line
       - so that the distance from c to r2 equals radius
 
-    if clockwise is true, the arc is drawn from r1 to r2 with increasing 
+    if clockwise is true, the arc is drawn from r1 to r2 with increasing
     angle values. Otherwise, the angle values decrease.
 
     Warning: the existing chain content will be wiped out.
   */
 
   void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
-    , const ScenePoint2D&        p1
-    , const ScenePoint2D&        c
-    , const ScenePoint2D&        p2
-    , const double&              radiusS
+    PolylineSceneLayer::Chain & chain
+    , const Scene2D & scene
+    , const ScenePoint2D & p1
+    , const ScenePoint2D & c
+    , const ScenePoint2D & p2
+    , const double& radiusS
     , const bool                 clockwise
     , const int                  subdivisionsCount = 63);
- 
+
   /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from
     start angle to end angle with the supplied radius.
 
     if clockwise is true, the arc is drawn from start to end by increasing the
@@ -107,10 +107,10 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&      scene
+    PolylineSceneLayer::Chain& chain
+    , const Scene2D& scene
     , const ScenePoint2D& centerS
-    , const double&       radiusS
+    , const double& radiusS
     , const double        startAngleRad
     , const double        endAngleRad
     , const bool          clockwise
@@ -123,9 +123,9 @@
     Warning: the existing chain content will be wiped out.
   */
   void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    const Scene2D& scene,
     const ScenePoint2D& centerS,
-    const double&       radiusS,
+    const double& radiusS,
     const int           numSubdivisions = 63);
 
   /**
@@ -135,10 +135,10 @@
   double NormalizeAngle(double angle);
 
   /**
-    Returns the angle magnitude between the p1,c and p2,c lines. 
+    Returns the angle magnitude between the p1,c and p2,c lines.
     The returned angle is between 0 and 2*pi
 
-    If the angle is between 0 and pi, this means that the shortest arc 
+    If the angle is between 0 and pi, this means that the shortest arc
     from p1 to p2 is clockwise.
 
     If the angle is between pi and 2*pi, this means that the shortest arc
@@ -146,7 +146,7 @@
 
   */
   double MeasureAngle(
-      const ScenePoint2D& p1
+    const ScenePoint2D& p1
     , const ScenePoint2D& c
     , const ScenePoint2D& p2);
 
@@ -163,7 +163,7 @@
   to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
   */
   void GetPositionOnBisectingLine(
-      ScenePoint2D&       result
+    ScenePoint2D& result
     , const ScenePoint2D& p1
     , const ScenePoint2D& c
     , const ScenePoint2D& p2
@@ -172,14 +172,18 @@
 
   /**
   This helper is used when drawing text with an outline.
-  It set the properties for several text layers at once : first the 
-  four outline layers, with a position shift and then the actual main text 
+  It set the properties for several text layers at once : first the
+  four outline layers, with a position shift and then the actual main text
   layer.
 
   The five text layers are supposed to already exist in the scene, starting
-  from layerIndex, up to (and not including) layerIndex+5. 
+  from layerIndex, up to (and not including) layerIndex+5.
   */
   void SetTextLayerOutlineProperties(
     boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder,
     const char* text, ScenePoint2D p);
+
+
+  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p);
 }
+
--- a/Framework/Scene2DViewport/ViewportController.cpp	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Tue Jun 25 15:24:13 2019 +0200
@@ -100,6 +100,15 @@
     return ret;
   }
 
+
+  void ViewportController::ResetMeasuringToolsHighlight()
+  {
+    for (size_t i = 0; i < measureTools_.size(); ++i)
+    {
+      measureTools_[i]->ResetHighlightState();
+    }
+  }
+
   const OrthancStone::AffineTransform2D& ViewportController::GetCanvasToSceneTransform() const
   {
     return scene_->GetCanvasToSceneTransform();
--- a/Framework/Scene2DViewport/ViewportController.h	Mon Jun 24 19:07:34 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Tue Jun 25 15:24:13 2019 +0200
@@ -42,10 +42,21 @@
   const uint8_t TEXT_COLOR_GREEN = 223;
   const uint8_t TEXT_COLOR_BLUE = 81;
 
+  const uint8_t TOOL_ANGLE_LINES_COLOR_RED = 0;
+  const uint8_t TOOL_ANGLE_LINES_COLOR_GREEN = 183;
+  const uint8_t TOOL_ANGLE_LINES_COLOR_BLUE = 17;
+                     
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_RED = 0;
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_GREEN = 17;
+  const uint8_t TOOL_ANGLE_LINES_HL_COLOR_BLUE = 183;
+
   const uint8_t TOOL_LINES_COLOR_RED = 0;
   const uint8_t TOOL_LINES_COLOR_GREEN = 223;
   const uint8_t TOOL_LINES_COLOR_BLUE = 21;
 
+  const uint8_t TOOL_LINES_HL_COLOR_RED = 0;
+  const uint8_t TOOL_LINES_HL_COLOR_GREEN = 21;
+  const uint8_t TOOL_LINES_HL_COLOR_BLUE = 223;
 
   const uint8_t TEXT_OUTLINE_COLOR_RED = 0;
   const uint8_t TEXT_OUTLINE_COLOR_GREEN = 56;
@@ -88,6 +99,12 @@
     std::vector<boost::shared_ptr<MeasureTool> > HitTestMeasureTools(ScenePoint2D p);
 
     /**
+    This function will traverse the measuring tools and will clear their 
+    highlighted state
+    */
+    void ResetMeasuringToolsHighlight();
+
+    /**
     With this method, the object takes ownership of the supplied tracker and
     updates it according to user interaction
     */