Salome HOME
Copyright update 2022
[modules/geom.git] / src / MeasureGUI / MeasureGUI_DimensionInteractor.cxx
1 // Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 // GEOM GEOMGUI : GUI for Geometry component
24 // File   : MeasureGUI_DimensionInteractor.cxx
25 // Author : Anton POLETAEV, Open CASCADE S.A.S.
26
27 #include "MeasureGUI_DimensionInteractor.h"
28
29 #include <GEOM_Displayer.h>
30 #include <SalomeApp_Application.h>
31 #include <OCCViewer_ViewManager.h>
32 #include <OCCViewer_ViewWindow.h>
33 #include <OCCViewer_ViewPort3d.h>
34 #include <SUIT_ViewManager.h>
35 #include <SUIT_ViewWindow.h>
36 #include <SUIT_Desktop.h>
37
38 #include <Basics_OCCTVersion.hxx>
39
40 #include <QMouseEvent>
41
42 #include <AIS_InteractiveContext.hxx>
43 #include <AIS_LengthDimension.hxx>
44 #include <AIS_DiameterDimension.hxx>
45 #include <AIS_AngleDimension.hxx>
46 #include <Prs3d_DimensionAspect.hxx>
47 #include <Prs3d_TextAspect.hxx>
48 #include <gp_Dir.hxx>
49 #include <gp_Vec.hxx>
50 #include <gce_MakeDir.hxx>
51 #include <ElCLib.hxx>
52 #include <ElSLib.hxx>
53 #include <Extrema_ExtCC.hxx>
54 #include <Extrema_POnCurv.hxx>
55 #include <GeomAdaptor_Curve.hxx>
56 #include <GeomAPI_IntCS.hxx>
57 #include <Geom_Line.hxx>
58 #include <Geom_Plane.hxx>
59 #include <NCollection_Sequence.hxx>
60 #include <Quantity_Length.hxx>
61
62 //=================================================================================
63 // function : Constructor
64 // purpose  : 
65 //=================================================================================
66 MeasureGUI_DimensionInteractor::MeasureGUI_DimensionInteractor( GeometryGUI* theGUI,
67                                                                 QObject* theParent )
68 : QObject( theParent ),
69   myGeomGUI( theGUI ),
70   myIsEnabled( false ),
71   myVM( NULL ),
72   myViewer( NULL ),
73   myOperation( Operation_None )
74 {
75 }
76
77 //=================================================================================
78 // function : Deactivate
79 // purpose  : 
80 //=================================================================================
81 MeasureGUI_DimensionInteractor::~MeasureGUI_DimensionInteractor()
82 {
83   Disable();
84 }
85
86 //=================================================================================
87 // function : Enable
88 // purpose  : 
89 //=================================================================================
90 void MeasureGUI_DimensionInteractor::Enable()
91 {
92   if ( myIsEnabled )
93   {
94     return;
95   }
96
97   myIsEnabled = true;
98
99   // install event filtering on viewer windows
100   SalomeApp_Application* anApp = myGeomGUI->getApp();
101   if ( !anApp )
102   {
103     return;
104   }
105
106   myVM     = (OCCViewer_ViewManager*) anApp->getViewManager( OCCViewer_Viewer::Type(), false );
107   myViewer = (OCCViewer_Viewer*) myVM->getViewModel();
108   if ( !myVM || !myViewer )
109   {
110     return;
111   }
112
113   connect( myVM, SIGNAL( viewCreated( SUIT_ViewWindow* ) ), SLOT( OnViewCreated( SUIT_ViewWindow* ) ) );
114   connect( myVM, SIGNAL( deleteView ( SUIT_ViewWindow* ) ), SLOT( OnViewRemoved( SUIT_ViewWindow* ) ) );
115
116   QVector<SUIT_ViewWindow*>           aViews  = myVM->getViews();
117   QVector<SUIT_ViewWindow*>::iterator aViewIt = aViews.begin();
118   for ( ; aViewIt != aViews.end(); ++aViewIt )
119   {
120     ConnectView( *aViewIt );
121   }
122 }
123
124 //=================================================================================
125 // function : Disable
126 // purpose  : 
127 //=================================================================================
128 void MeasureGUI_DimensionInteractor::Disable()
129 {
130   if ( !myIsEnabled )
131   {
132     return;
133   }
134
135   myIsEnabled = false;
136
137   if (myVM) {
138     // remove event filtering from viewer windows
139     QVector<SUIT_ViewWindow*>           aViews  = myVM->getViews();
140     QVector<SUIT_ViewWindow*>::iterator aViewIt = aViews.begin();
141     for ( ; aViewIt != aViews.end(); ++aViewIt )
142     {
143       DisconnectView( *aViewIt );
144     }
145   }
146 }
147
148 //=================================================================================
149 // function : GetOperation
150 // purpose  : 
151 //=================================================================================
152 MeasureGUI_DimensionInteractor::Operation
153   MeasureGUI_DimensionInteractor::GetOperation( const Handle(SelectMgr_EntityOwner)& theEntity,
154                                                 const Qt::MouseButtons theButtons,
155                                                 const Qt::KeyboardModifiers theKeys )
156 {
157   if ( ( theButtons & Qt::LeftButton ) == 0 )
158   {
159     return Operation_None;
160   }
161
162 #if OCC_VERSION_LARGE >= 0x070400ff
163   Handle(PrsDim_DimensionOwner) anOwner = Handle(PrsDim_DimensionOwner)::DownCast( theEntity );
164 #else
165   Handle(AIS_DimensionOwner) anOwner = Handle(AIS_DimensionOwner)::DownCast( theEntity );
166 #endif
167   if ( anOwner.IsNull() )
168   {
169     return Operation_None;
170   }
171
172   //Standard_Real anAngTolerance = M_PI / 30.0; // 6 degree tolerance
173
174   switch ( anOwner->SelectionMode() )
175   {
176 #if OCC_VERSION_LARGE >= 0x070400ff
177     case PrsDim_DimensionSelectionMode_Line :
178 #else
179     case AIS_DSM_Line :
180 #endif
181     {
182       if ( ( theKeys & Qt::ControlModifier ) == 0 )
183       {
184         return Operation_MoveFlyoutInPlane;
185       }
186
187       // "free moving" is only available for length and diameter
188       if ( !myInteractedIO->IsKind( STANDARD_TYPE( AIS_LengthDimension ) )
189         && !myInteractedIO->IsKind( STANDARD_TYPE( AIS_DiameterDimension ) ) )
190       {
191         return Operation_None;
192       }
193
194       return Operation_MoveFlyoutFree;
195     }
196
197 #if OCC_VERSION_LARGE >= 0x070400ff
198     case PrsDim_DimensionSelectionMode_Text : return Operation_MoveText;
199 #else
200     case AIS_DSM_Text : return Operation_MoveText;
201 #endif
202     default :           return Operation_None;
203   }
204 }
205
206 //=================================================================================
207 // function : StartOperation
208 // purpose  : 
209 //=================================================================================
210 bool MeasureGUI_DimensionInteractor::StartOperation( const Operation theOp,
211                                                      const Handle(V3d_View)& theView,
212                                                      const int theX,
213                                                      const int theY )
214 {
215   Standard_Real anAngTolerance = M_PI / 30.0; // 6 degree tolerance
216
217   switch ( theOp )
218   {
219     case Operation_MoveFlyoutInPlane :
220     case Operation_MoveText :
221     {
222       // operation can be started if the plane in which the flyout 
223       // or text moved is not completely orthogonal to the screen
224       gp_Pln aPlane = myInteractedIO->GetPlane();
225       gp_Lin aProj  = Projection( theView, theX, theY );
226       Standard_Real aCrossAng = aProj.Direction().Angle( aPlane.Axis().Direction() );
227       return Abs( M_PI * 0.5 - aCrossAng ) > anAngTolerance;
228     }
229
230     case Operation_MoveFlyoutFree :
231     {
232       // operation can be started only for linear dimensions
233       if ( !myInteractedIO->IsKind( STANDARD_TYPE( AIS_LengthDimension ) )
234         && !myInteractedIO->IsKind( STANDARD_TYPE( AIS_DiameterDimension ) ) )
235       {
236         return false;
237       }
238
239       // determine whether the rotation operation is frontal or side
240       gp_Pnt aFirstPoint;
241       gp_Pnt aSecondPoint;
242
243       // get first and second point from which the flyout line is normally extended
244       Handle(AIS_LengthDimension) aLength = Handle(AIS_LengthDimension)::DownCast( myInteractedIO );
245       if ( !aLength.IsNull() )
246       {
247         aFirstPoint  = aLength->FirstPoint();
248         aSecondPoint = aLength->SecondPoint();
249       }
250
251       Handle(AIS_DiameterDimension) aDiameter = Handle(AIS_DiameterDimension)::DownCast( myInteractedIO );
252       if ( !aDiameter.IsNull() )
253       {
254         const gp_Pnt& aCenter = aDiameter->Circle().Location();
255         aFirstPoint    = aDiameter->AnchorPoint();
256         aSecondPoint   = gp_Pnt( aFirstPoint.XYZ() + gp_Vec( aFirstPoint, aCenter ).XYZ() * 2.0 );
257       }
258
259       gp_Dir aBaseDir = gce_MakeDir( aFirstPoint, aSecondPoint );
260       gp_Pln aPlane   = myInteractedIO->GetPlane();
261       gp_Dir aPlaneN  = aPlane.Axis().Direction();
262       gp_Lin aProj    = Projection( theView, theX, theY );
263
264       const gp_Dir& aProjDir = aProj.Direction();
265
266       // can turn only if looking from side
267       if ( aProjDir.IsParallel( aPlaneN, anAngTolerance ) ||
268            aProjDir.IsNormal( aBaseDir, anAngTolerance ) )
269       {
270         return false;
271       }
272
273       bool isProjected     = false;
274       gp_Pnt aPointOnPlane = ProjectPlane( theView, theX, theY, aPlane, isProjected );
275       if ( !isProjected )
276       {
277         return false;
278       }
279
280       gp_Vec aPointVector( aFirstPoint, aPointOnPlane );
281       gp_Pnt aPointOnBase = gp_Pnt( aFirstPoint.XYZ() + aBaseDir.XYZ() * aPointVector.Dot( aBaseDir ) );
282       myFreeMovePlane = gp_Pln( aPointOnBase, aBaseDir );
283
284       return true;
285     }
286
287     default:
288       return false;
289   }
290 }
291
292 //=================================================================================
293 // function : MoveFlyoutFree
294 // purpose  : 
295 //=================================================================================
296 void MeasureGUI_DimensionInteractor::MoveFlyoutFree( const Handle(V3d_View)& theView,
297                                                      const int theX,
298                                                      const int theY )
299 {
300   // project point onto dimension plane
301   bool isProjected     = false;
302   gp_Pln aPlane        = myFreeMovePlane;
303   gp_Pnt aPointOnPlane = ProjectPlane( theView, theX, theY, aPlane, isProjected );
304   if ( !isProjected )
305   {
306     return;
307   }
308
309   gp_Pnt aFirstPoint;
310   gp_Pnt aSecondPoint;
311
312   // get first and second point from which the flyout line is normally extended
313   Handle(AIS_LengthDimension) aLength = Handle(AIS_LengthDimension)::DownCast( myInteractedIO );
314   if ( !aLength.IsNull() )
315   {
316     aFirstPoint  = aLength->FirstPoint();
317     aSecondPoint = aLength->SecondPoint();
318   }
319
320   Handle(AIS_DiameterDimension) aDiameter = Handle(AIS_DiameterDimension)::DownCast( myInteractedIO );
321   if ( !aDiameter.IsNull() )
322   {
323     const gp_Pnt& aCenter = aDiameter->Circle().Location();
324     aFirstPoint    = aDiameter->AnchorPoint();
325     aSecondPoint   = gp_Pnt( aFirstPoint.XYZ() + gp_Vec( aFirstPoint, aCenter ).XYZ() * 2.0 );
326   }
327
328   gp_Vec aPointVector( aFirstPoint, aPointOnPlane );
329   gp_Dir aBaseDir     = gce_MakeDir( aFirstPoint, aSecondPoint );
330   gp_Pnt aPointOnBase = gp_Pnt( aFirstPoint.XYZ() + aBaseDir.XYZ() * aPointVector.Dot( aBaseDir ) );
331
332   // snapping tolerance
333   Standard_Real aSize[2];
334   theView->Size( aSize[0], aSize[1] );
335   Standard_Real aSnapTolerance = 1e-2 * Max( aSize[0], aSize[1] );
336
337   gp_Dir aFlyoutDir = gce_MakeDir( aPointOnBase, aPointOnPlane );
338
339   // snapping to planes
340   NCollection_Sequence<gp_Pln> aSnapPlanes;
341
342   if ( aPointOnPlane.Distance( aPointOnBase ) > aSnapTolerance )
343   {
344     if ( !aBaseDir.IsParallel( gp::DZ(), Precision::Angular() ) )
345     {
346       aSnapPlanes.Append( gp_Pln( aFirstPoint, gp::DZ().Crossed( aBaseDir ) ) );
347     }
348     if ( !aBaseDir.IsParallel( gp::DX(), Precision::Angular() ) )
349     {
350       aSnapPlanes.Append( gp_Pln( aFirstPoint, gp::DX().Crossed( aBaseDir ) ) );
351     }
352     if ( !aBaseDir.IsParallel( gp::DY(), Precision::Angular() ) )
353     {
354       aSnapPlanes.Append( gp_Pln( aFirstPoint, gp::DY().Crossed( aBaseDir ) ) );
355     }
356   }
357
358   // snap "drag proj" to Flyout plane, relative XOY, YOZ, ZOX planes
359   NCollection_Sequence<gp_Pln>::Iterator aSnapIt( aSnapPlanes );
360
361   for ( ; aSnapIt.More(); aSnapIt.Next() )
362   {
363     const gp_Pln& aSnapPln = aSnapIt.Value();
364
365     // project direction into plane
366     gp_Vec aPlaneDir = gp_Vec( aSnapPln.Axis().Direction() );
367     gp_Vec aFlyoutProj = gp_Vec( aFlyoutDir ) - aPlaneDir * gp_Vec( aFlyoutDir ).Dot( aPlaneDir );
368
369     // snapping is not applicable
370     if ( aSnapPln.Contains( gp_Lin( aFirstPoint, aFlyoutDir ), Precision::Confusion(), Precision::Angular() ) )
371     {
372       continue;
373     }
374     if ( Abs( gp_Vec( aPointOnBase, aPointOnPlane ).Dot( aPlaneDir ) ) > aSnapTolerance )
375     {
376       continue;
377     }
378     if ( aFlyoutProj.Magnitude() <= Precision::Confusion() )
379     {
380       continue;
381     }
382
383     aFlyoutDir = gp_Dir( aFlyoutProj );
384
385     break;
386   }
387   
388   Standard_Real aNewFlyout = aPointOnPlane.Distance( aPointOnBase );
389
390   if ( aNewFlyout <= Precision::Confusion() )
391   {
392     myInteractedIO->SetFlyout( 0.0 );
393     myViewer->getAISContext()->Redisplay( myInteractedIO, Standard_True );
394     return;
395   }
396
397   gp_Pln aNewPlane = gp_Pln( aFirstPoint, aBaseDir.Crossed( aFlyoutDir ) );
398
399   myInteractedIO->SetFlyout( aNewFlyout );
400   myInteractedIO->SetCustomPlane( aNewPlane );
401
402   myViewer->getAISContext()->Redisplay( myInteractedIO, Standard_True );
403 }
404
405 //=================================================================================
406 // function : MoveFlyoutInPlane
407 // purpose  : 
408 //=================================================================================
409 void MeasureGUI_DimensionInteractor::MoveFlyoutInPlane( const Handle(V3d_View)& theView,
410                                                         const int theX,
411                                                         const int theY )
412 {
413   // project point onto dimension plane
414   bool isProjected     = false;
415   gp_Pln aPlane        = myInteractedIO->GetPlane();
416   gp_Pnt aPointOnPlane = ProjectPlane( theView, theX, theY, aPlane, isProjected );
417   if ( !isProjected )
418   {
419     return;
420   }
421
422   const gp_Dir& aPlaneN = aPlane.Axis().Direction();
423
424   // new flyout value
425   Standard_Real aFlyout = 0.0;
426
427   // extend flyout of linear dimension
428   if ( myInteractedIO->IsKind( STANDARD_TYPE( AIS_LengthDimension ) )
429     || myInteractedIO->IsKind( STANDARD_TYPE( AIS_DiameterDimension ) ) )
430   {
431     gp_Pnt aFirstPoint;
432     gp_Pnt aSecondPoint;
433
434     // get first and second point from which the flyout line is normally extended
435     Handle(AIS_LengthDimension) aLength = Handle(AIS_LengthDimension)::DownCast( myInteractedIO );
436     if ( !aLength.IsNull() )
437     {
438       aFirstPoint  = aLength->FirstPoint();
439       aSecondPoint = aLength->SecondPoint();
440     }
441
442     Handle(AIS_DiameterDimension) aDiameter = Handle(AIS_DiameterDimension)::DownCast( myInteractedIO );
443     if ( !aDiameter.IsNull() )
444     {
445       const gp_Pnt& aCenter = aDiameter->Circle().Location();
446       aFirstPoint    = aDiameter->AnchorPoint();
447       aSecondPoint   = gp_Pnt( aFirstPoint.XYZ() + gp_Vec( aFirstPoint, aCenter ).XYZ() * 2.0 );
448     }
449
450     gp_Lin aBaseLine( aFirstPoint, gce_MakeDir( aFirstPoint, aSecondPoint ) );
451
452     // check if the negative value is expected
453     gp_Dir aPositiveDir = aPlaneN.Crossed( aBaseLine.Direction() );
454     gp_Dir aPointDir    = gce_MakeDir( aBaseLine.Location(), aPointOnPlane );
455     Standard_Boolean isPositive = aPointDir.Dot( aPositiveDir ) > 0;
456
457     aFlyout = isPositive 
458       ?  aBaseLine.Distance( aPointOnPlane )
459       : -aBaseLine.Distance( aPointOnPlane );
460   }
461
462   // exten flyout of angular dimension
463   if ( myInteractedIO->IsKind( STANDARD_TYPE( AIS_AngleDimension ) ) )
464   {
465     Handle(AIS_AngleDimension) anAngle = Handle(AIS_AngleDimension)::DownCast( myInteractedIO );
466
467     const gp_Pnt& aCenterPoint = anAngle->CenterPoint();
468     const gp_Pnt& aFirstPoint  = anAngle->FirstPoint();
469     const gp_Pnt& aSecondPoint = anAngle->SecondPoint();
470
471     gp_Dir aFirstDir  = gce_MakeDir( aCenterPoint, aFirstPoint );
472     gp_Dir aSecondDir = gce_MakeDir( aCenterPoint, aSecondPoint );
473
474     Standard_Real aDirAngle = aFirstDir.Angle( aSecondDir );
475
476     //                 p1 center p2
477     //                   \ dir /
478     // the direction are: \ | /
479     //            first    \|/   second
480     //             dir      c      dir
481     // 
482
483     gp_Dir aCenterDir = aFirstDir.Rotated( gp_Ax1( aCenterPoint, aPlaneN ), -aDirAngle * 0.5 );
484
485     gp_Dir aPointDir  = gce_MakeDir( aCenterPoint, aPointOnPlane );
486
487     Standard_Boolean isPositive = aPointDir.Dot( aCenterDir ) > 0;
488
489     Standard_Real aPointAngle = aPointDir.AngleWithRef(  aCenterDir,  aPlaneN );
490     if ( !isPositive )
491     {
492       aPointAngle = ( M_PI - Abs( aPointAngle ) ) * ( -Sign( 1.0, aPointAngle ) );
493     }
494
495     gp_Vec aPointVec = gp_Vec( aCenterPoint, aPointOnPlane );
496
497     // calculate flyout for each separate case of point location
498     if ( aPointAngle < -aDirAngle * 0.5 ) // outside of second dir
499     {
500       aFlyout = aPointVec.Dot( aFirstDir );
501     }
502     else if ( aPointAngle > aDirAngle * 0.5 ) // outside of first dir
503     {
504       aFlyout = aPointVec.Dot( aSecondDir );
505     }
506     else // between first and second direction
507     {
508       aFlyout = isPositive
509         ?  aPointVec.Magnitude()
510         : -aPointVec.Magnitude();
511     }
512   }
513
514   myInteractedIO->SetFlyout( aFlyout );
515
516   myViewer->getAISContext()->Redisplay( myInteractedIO, Standard_True );
517 }
518
519 //=================================================================================
520 // function : MoveText
521 // purpose  : 
522 //=================================================================================
523 void MeasureGUI_DimensionInteractor::MoveText( const Handle(V3d_View)& theView,
524                                                const int theX,
525                                                const int theY )
526 {
527   // project point onto dimension plane
528   bool isProjected     = false;
529   gp_Pln aPlane        = myInteractedIO->GetPlane();
530   gp_Pnt aPointOnPlane = ProjectPlane( theView, theX, theY, aPlane, isProjected );
531   if ( !isProjected )
532   {
533     return;
534   }
535
536   const gp_Dir& aPlaneN = aPlane.Axis().Direction();
537
538   Prs3d_DimensionTextHorizontalPosition aHPos   = myInteractedIO->DimensionAspect()->TextHorizontalPosition();
539   Prs3d_DimensionTextVerticalPosition   aVPos   = myInteractedIO->DimensionAspect()->TextVerticalPosition();
540   Prs3d_DimensionArrowOrientation       aArrPos = myInteractedIO->DimensionAspect()->ArrowOrientation();
541
542   Standard_Real aHeight = myInteractedIO->DimensionAspect()->TextAspect()->Height() * 0.5;
543
544   // move text of linear dimension
545   if ( myInteractedIO->IsKind( STANDARD_TYPE( AIS_LengthDimension ) )
546     || myInteractedIO->IsKind( STANDARD_TYPE( AIS_DiameterDimension ) ) )
547   {
548     gp_Pnt aFirstPoint;
549     gp_Pnt aSecondPoint;
550
551     // get first and second point from which the flyout line is normally extended
552     Handle(AIS_LengthDimension) aLength = Handle(AIS_LengthDimension)::DownCast( myInteractedIO );
553     if ( !aLength.IsNull() )
554     {
555       aFirstPoint  = aLength->FirstPoint();
556       aSecondPoint = aLength->SecondPoint();
557     }
558
559     Handle(AIS_DiameterDimension) aDiameter = Handle(AIS_DiameterDimension)::DownCast( myInteractedIO );
560     if ( !aDiameter.IsNull() )
561     {
562       const gp_Pnt& aCenter = aDiameter->Circle().Location();
563       aFirstPoint    = aDiameter->AnchorPoint();
564       aSecondPoint   = gp_Pnt( aFirstPoint.XYZ() + gp_Vec( aFirstPoint, aCenter ).XYZ() * 2.0 );
565     }
566
567     // get flyout line
568     Standard_Real aFlyout   = myInteractedIO->GetFlyout();
569     gp_Dir aBaseDir         = gce_MakeDir( aFirstPoint, aSecondPoint );
570     gp_Dir aFlyoutDir       = aFlyout >= 0.0 
571       ?  aPlaneN.Crossed( gce_MakeDir( aFirstPoint, aSecondPoint ) )
572       : -aPlaneN.Crossed( gce_MakeDir( aFirstPoint, aSecondPoint ) );
573     gp_Vec aFlyoutTranslate = gp_Vec( aFlyoutDir ) * Abs( myInteractedIO->GetFlyout() );
574     gp_Lin aFlyoutLine      = gp_Lin( aFirstPoint, aBaseDir ).Translated( aFlyoutTranslate );
575
576     // check if positive or negative offset along flyout line
577     gp_Vec aFlyout2Point = gp_Vec( aFlyoutLine.Location(), aPointOnPlane );
578
579     Standard_Real aPosY = aFlyout2Point.Dot( aFlyoutDir );
580     Standard_Real aPosX = aFlyout2Point.Dot( aFlyoutLine.Direction() );
581
582     if ( aPosX < 0.0 )
583     {
584       aHPos   = Prs3d_DTHP_Left;
585       aArrPos = Prs3d_DAO_External;
586     }
587     else if ( aPosX > aFirstPoint.Distance( aSecondPoint ) )
588     {
589       aHPos   = Prs3d_DTHP_Right;
590       aArrPos = Prs3d_DAO_External;
591     }
592     else
593     {
594       aHPos   = Prs3d_DTHP_Center;
595       aArrPos = Prs3d_DAO_Fit;
596     }
597
598     if ( aPosY > aHeight )
599     {
600       aVPos = Prs3d_DTVP_Above;
601     }
602     else if ( aPosY < -aHeight )
603     {
604       aVPos = Prs3d_DTVP_Below;
605     }
606     else
607     {
608       aVPos = Prs3d_DTVP_Center;
609     }
610   }
611
612   // move text of angular dimension
613   if ( myInteractedIO->IsKind( STANDARD_TYPE( AIS_AngleDimension ) ) )
614   {
615     Handle(AIS_AngleDimension) anAngle = Handle(AIS_AngleDimension)::DownCast( myInteractedIO );
616
617     const gp_Pnt& aCenterPoint = anAngle->CenterPoint();
618     const gp_Pnt& aFirstPoint  = anAngle->FirstPoint();
619     const gp_Pnt& aSecondPoint = anAngle->SecondPoint();
620
621     //Standard_Boolean isPositive = anAngle->GetFlyout() > 0.0;
622
623     gp_Dir aFirstDir  = gce_MakeDir( aCenterPoint, aFirstPoint );
624     gp_Dir aSecondDir = gce_MakeDir( aCenterPoint, aSecondPoint );
625     if ( anAngle->GetFlyout() < 0.0 )
626     {
627       aFirstDir.Reverse();
628       aSecondDir.Reverse();
629     }
630
631     Standard_Real aDirAngle = aFirstDir.Angle( aSecondDir );
632
633     //                 p1 center p2
634     //                   \ dir /
635     // the direction are: \ | /
636     //            first    \|/   second
637     //             dir      c      dir
638
639     gp_Dir aCenterDir = aFirstDir.Rotated( gp_Ax1( aCenterPoint, aPlaneN ), -aDirAngle * 0.5 );
640
641     gp_Dir aPointDir = gce_MakeDir( aCenterPoint, aPointOnPlane );
642
643     Standard_Real aPointAngle = aPointDir.AngleWithRef( aCenterDir, aPlaneN );
644
645     gp_Vec aPointVec = gp_Vec( aCenterPoint, aPointOnPlane );
646
647     Standard_Real aPosY = 0.0;
648
649     // calculate flyout for each separate case of point location
650     if ( aPointAngle < -aDirAngle * 0.5 ) // outside of second dir
651     {
652       aHPos   = Prs3d_DTHP_Left;
653       aArrPos = Prs3d_DAO_External;
654       aPosY   = aPointVec.Dot( aFirstDir ) - Abs( anAngle->GetFlyout() );
655     }
656     else if ( aPointAngle > aDirAngle * 0.5 ) // outside of first dir
657     {
658       aHPos   = Prs3d_DTHP_Right;
659       aArrPos = Prs3d_DAO_External;
660       aPosY   = aPointVec.Dot( aSecondDir ) - Abs( anAngle->GetFlyout() );
661     }
662     else // between first and second direction
663     {
664       aHPos   = Prs3d_DTHP_Center;
665       aArrPos = Prs3d_DAO_Fit;
666       aPosY   = aPointVec.Magnitude() - Abs( anAngle->GetFlyout() );
667     }
668
669     if ( aPosY > aHeight )
670     {
671       aVPos = Prs3d_DTVP_Above;
672     }
673     else if ( aPosY < -aHeight )
674     {
675       aVPos = Prs3d_DTVP_Below;
676     }
677     else
678     {
679       aVPos = Prs3d_DTVP_Center;
680     }
681   }
682
683   myInteractedIO->DimensionAspect()->SetTextVerticalPosition( aVPos );
684   myInteractedIO->DimensionAspect()->SetTextHorizontalPosition( aHPos );
685   myInteractedIO->DimensionAspect()->SetArrowOrientation( aArrPos );
686   myInteractedIO->SetToUpdate();
687
688   myViewer->getAISContext()->Redisplay( myInteractedIO, Standard_True );
689 }
690
691 //=================================================================================
692 // function : eventFilter
693 // purpose  : 
694 //=================================================================================
695 bool MeasureGUI_DimensionInteractor::eventFilter( QObject* theObject, QEvent* theEvent )
696 {
697   OCCViewer_ViewPort3d* aViewPort = (OCCViewer_ViewPort3d*) theObject;
698
699   Handle(V3d_View) aView3d = aViewPort->getView();
700
701   switch ( theEvent->type() )
702   {
703     // check whether it is the "dimension modify" operation or not.
704     case QEvent::MouseButtonPress :
705     {
706       Handle(AIS_InteractiveContext) anAISContext = myViewer->getAISContext();
707
708       QMouseEvent* aMouseEv = dynamic_cast<QMouseEvent*>( theEvent );
709
710       // init detection
711       anAISContext->MoveTo( aMouseEv->x(), aMouseEv->y(), aView3d, Standard_True );
712       if ( !anAISContext->HasDetected() )
713       {
714         return false;
715       }
716
717       // commented by mpa 18.03.2015: since OCCT version 6.8.0 it's impossible
718       // to change position of the dimensions presentations (flyout, text),
719       // because anAISContext has 2 detected objects.
720
721       // check that there is only one detected entity
722       //anAISContext->InitDetected();
723       //if ( anAISContext->MoreDetected() )
724       //{
725       //  return false;
726       //}
727
728       Handle(SelectMgr_EntityOwner) aDetectedOwner = anAISContext->DetectedOwner();
729       if( aDetectedOwner.IsNull() )
730       {
731         return false;
732       }
733
734       myInteractedIO = Handle(AIS_Dimension)::DownCast( aDetectedOwner->Selectable() );
735
736       // try to start operation for the detected entity
737       Operation aStartOp = GetOperation( aDetectedOwner, aMouseEv->buttons(), aMouseEv->modifiers() );
738       if ( aStartOp == Operation_None )
739       {
740         return false;
741       }
742
743       if ( !StartOperation( aStartOp, aView3d, aMouseEv->x(), aMouseEv->y() ) )
744       {
745         return false;
746       }
747
748       myOperation = aStartOp;
749
750       mySelection.Clear();
751
752       for ( anAISContext->InitSelected(); anAISContext->MoreSelected(); anAISContext->NextSelected() )
753       {
754         mySelection.Append( anAISContext->SelectedOwner() );
755       }
756
757       anAISContext->ClearSelected( Standard_False );
758       anAISContext->AddOrRemoveSelected( aDetectedOwner, Standard_True );
759
760       emit InteractionStarted( myInteractedIO );
761
762       return true;
763     }
764
765     // stop event processing on mouse release
766     case QEvent::MouseButtonRelease :
767     {
768       if ( myOperation != Operation_None )
769       {
770         Handle(AIS_InteractiveContext) anAISContext = myViewer->getAISContext();
771
772         anAISContext->ClearSelected( Standard_False );
773         SeqOfOwners::Iterator anIt( mySelection );
774         for( ; anIt.More(); anIt.Next() )
775         {
776           anAISContext->AddOrRemoveSelected( anIt.Value(), Standard_False );
777         }
778
779         anAISContext->UpdateCurrentViewer();
780
781         mySelection.Clear();
782
783         myOperation = Operation_None;
784
785         emit InteractionFinished( myInteractedIO );
786
787         return true;
788       }
789
790       return false;
791     }
792
793     // perform operation
794     case QEvent::MouseMove :
795     {
796       QMouseEvent* aMouseEv = (QMouseEvent*) theEvent;
797       switch( myOperation )
798       {
799         case Operation_MoveFlyoutFree    : MoveFlyoutFree   ( aView3d, aMouseEv->x(), aMouseEv->y() ); return true;
800         case Operation_MoveFlyoutInPlane : MoveFlyoutInPlane( aView3d, aMouseEv->x(), aMouseEv->y() ); return true;
801         case Operation_MoveText          : MoveText         ( aView3d, aMouseEv->x(), aMouseEv->y() ); return true;
802         default : return false;
803       }
804     }
805
806     default: return false;
807   }
808 }
809
810 //=================================================================================
811 // function : ConnectView
812 // purpose  : 
813 //=================================================================================
814 void MeasureGUI_DimensionInteractor::ConnectView( SUIT_ViewWindow* theView )
815 {
816   ( (OCCViewer_ViewWindow*) theView )->getViewPort()->installEventFilter( this );
817 }
818
819 //=================================================================================
820 // function : DisconnectView
821 // purpose  : 
822 //=================================================================================
823 void MeasureGUI_DimensionInteractor::DisconnectView( SUIT_ViewWindow* theView )
824 {
825   ( (OCCViewer_ViewWindow*) theView )->getViewPort()->removeEventFilter( this );
826 }
827
828 //=================================================================================
829 // function : Projection
830 // purpose  : 
831 //=================================================================================
832 gp_Lin MeasureGUI_DimensionInteractor::Projection( const Handle(V3d_View)& theView,
833                                                    const int theMouseX,
834                                                    const int theMouseY )
835 {
836   Standard_Real P[3], D[3];
837   theView->ConvertWithProj( theMouseX, theMouseY, P[0], P[1], P[2], D[0], D[1], D[2] );
838   return gp_Lin( gp_Pnt( P[0], P[1], P[2] ), gp_Dir( D[0], D[1], D[2] ) );
839 }
840
841 //=================================================================================
842 // function : ProjectPlane
843 // purpose  : 
844 //=================================================================================
845 gp_Pnt MeasureGUI_DimensionInteractor::ProjectPlane( const Handle(V3d_View)& theView,
846                                                      const int theMouseX,
847                                                      const int theMouseY,
848                                                      const gp_Pln& thePlane,
849                                                      bool& theIsDone )
850 {
851   gp_Lin aProjection = Projection( theView, theMouseX, theMouseY );
852
853   Handle(Geom_Line)  aCrossLine  = new Geom_Line( aProjection );
854   Handle(Geom_Plane) aCrossPlane = new Geom_Plane( thePlane );
855
856   GeomAPI_IntCS aFindCross( aCrossLine, aCrossPlane );
857   if ( !aFindCross.IsDone() || aFindCross.NbPoints() == 0 )
858   {
859     theIsDone = false;
860     return gp::Origin();
861   }
862
863   theIsDone = true;
864   return aFindCross.Point( 1 );
865 }
866
867 //=================================================================================
868 // function : SensitivityTolerance
869 // purpose  : 
870 //=================================================================================
871 Standard_Real MeasureGUI_DimensionInteractor::SensitivityTolerance( const Handle(V3d_View)& theView )
872 {
873   // snapping tolerance
874   Standard_Real aSize[2];
875   theView->Size( aSize[0], aSize[1] );
876   return 1e-2 * Max( aSize[0], aSize[1] );
877 }
878
879 //=================================================================================
880 // function : OnViewCreated
881 // purpose  : 
882 //=================================================================================
883 void MeasureGUI_DimensionInteractor::OnViewCreated( SUIT_ViewWindow* theView )
884 {
885   ConnectView( theView );
886 }
887
888 //=================================================================================
889 // function : OnViewRemoved
890 // purpose  : 
891 //=================================================================================
892 void MeasureGUI_DimensionInteractor::OnViewRemoved( SUIT_ViewWindow* theView )
893 {
894   DisconnectView( theView );
895 }