0030619: Draw Harness, ViewerTest - add continuous rendering option to vrepaint command
authorkgv <kgv@opencascade.com>
Thu, 28 Mar 2019 15:36:54 +0000 (18:36 +0300)
committerapn <apn@opencascade.com>
Mon, 1 Apr 2019 15:29:16 +0000 (18:29 +0300)
Aspect_Window::InvalidateContent() - added new virtual method for invalidating window content using platform-specific API.

TKDraw, tkLoop() on Window platform has been changed so that to prevent
continuous input window events blocking terminal input
(e.g. in case if processing events is not fast enough or
if another continuously sends new events).

TKViewerTest, on X11 platform has been fixed a message processing
so that to avoid messages being not processed.
Added aggregation of Exposer and ConfigureNotify events.
Fixed aggregation MotionNotify events.

src/Aspect/Aspect_Window.hxx
src/Cocoa/Cocoa_Window.hxx
src/Cocoa/Cocoa_Window.mm
src/Draw/Draw_Window.cxx
src/ViewerTest/ViewerTest_ViewerCommands.cxx
src/WNT/WNT_Window.cxx
src/WNT/WNT_Window.hxx
src/Xw/Xw_Window.cxx
src/Xw/Xw_Window.hxx

index 3791c62..2783f74 100644 (file)
@@ -30,6 +30,8 @@
 #include <Aspect_TypeOfResize.hxx>
 #include <Standard_Integer.hxx>
 #include <Aspect_Drawable.hxx>
+
+class Aspect_DisplayConnection;
 class Aspect_WindowDefinitionError;
 class Aspect_WindowError;
 class Aspect_Background;
@@ -43,70 +45,80 @@ class Aspect_Window : public Standard_Transient
 {
 
 public:
-  
+
   //! Modifies the window background.
   Standard_EXPORT void SetBackground (const Aspect_Background& ABack);
 
   //! Modifies the window background.
   Standard_EXPORT void SetBackground (const Quantity_Color& color);
-  
+
   //! Modifies the window gradient background.
   Standard_EXPORT void SetBackground (const Aspect_GradientBackground& ABackground);
-  
+
   //! Modifies the window gradient background.
   Standard_EXPORT void SetBackground (const Quantity_Color& theFirstColor, const Quantity_Color& theSecondColor, const Aspect_GradientFillMethod theFillMethod);
-  
+
   //! Opens the window <me>.
   Standard_EXPORT virtual void Map() const = 0;
-  
+
   //! Closes the window <me>.
   Standard_EXPORT virtual void Unmap() const = 0;
-  
+
   //! Apply the resizing to the window <me>.
   Standard_EXPORT virtual Aspect_TypeOfResize DoResize() const = 0;
-  
+
   //! Apply the mapping change to the window <me>.
   //! and returns TRUE if the window is mapped at screen.
   Standard_EXPORT virtual Standard_Boolean DoMapping() const = 0;
-  
+
   //! Returns the window background.
   Standard_EXPORT Aspect_Background Background() const;
-  
+
   //! Returns the current image background fill mode.
   Standard_EXPORT Aspect_FillMethod BackgroundFillMethod() const;
-  
+
   //! Returns the window gradient background.
   Standard_EXPORT Aspect_GradientBackground GradientBackground() const;
-  
+
   //! Returns True if the window <me> is opened
   //! and False if the window is closed.
   Standard_EXPORT virtual Standard_Boolean IsMapped() const = 0;
-  
+
   //! Returns True if the window <me> is virtual
   Standard_EXPORT Standard_Boolean IsVirtual() const;
-  
+
   //! Setup the virtual state
   Standard_EXPORT void SetVirtual (const Standard_Boolean theVirtual);
-  
+
   //! Returns The Window RATIO equal to the physical
   //! WIDTH/HEIGHT dimensions
   Standard_EXPORT virtual Standard_Real Ratio() const = 0;
-  
+
   //! Returns The Window POSITION in PIXEL
   Standard_EXPORT virtual void Position (Standard_Integer& X1, Standard_Integer& Y1, Standard_Integer& X2, Standard_Integer& Y2) const = 0;
-  
+
   //! Returns The Window SIZE in PIXEL
   Standard_EXPORT virtual void Size (Standard_Integer& Width, Standard_Integer& Height) const = 0;
-  
+
   //! Returns native Window handle (HWND on Windows, Window with Xlib, and so on)
   Standard_EXPORT virtual Aspect_Drawable NativeHandle() const = 0;
-  
+
   //! Returns parent of native Window handle (HWND on Windows, Window with Xlib, and so on)
   Standard_EXPORT virtual Aspect_Drawable NativeParentHandle() const = 0;
 
   //! Returns native Window FB config (GLXFBConfig on Xlib)
   Standard_EXPORT virtual Aspect_FBConfig NativeFBConfig() const = 0;
 
+  //! Invalidate entire window content.
+  //!
+  //! Implementation is expected to allow calling this method from non-GUI thread,
+  //! e.g. by queuing exposure event into window message queue or in other thread-safe manner.
+  //!
+  //! Optional display argument should be passed when called from non-GUI thread
+  //! on platforms implementing thread-unsafe connections to display.
+  //! NULL can be passed instead otherwise.
+  virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp) { (void )theDisp; }
+
   DEFINE_STANDARD_RTTIEXT(Aspect_Window,Standard_Transient)
 
 protected:
index 12ad440..dcfe0d3 100644 (file)
@@ -136,6 +136,10 @@ public:
   //! Returns nothing on OS X
   virtual Aspect_FBConfig NativeFBConfig() const Standard_OVERRIDE { return NULL; }
 
+  //! Invalidate entire window content by setting NSView::setNeedsDisplay property.
+  //! Call will be implicitly redirected to the main thread when called from non-GUI thread.
+  Standard_EXPORT virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp = NULL) Standard_OVERRIDE;
+
 protected:
 
 #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
index 73aa701..2613a57 100644 (file)
@@ -66,6 +66,31 @@ static Standard_Integer getScreenBottom()
 }
 #endif
 
+//! Extension for Cocoa_Window::InvalidateContent().
+#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+  @interface UIView (UIViewOcctAdditions)
+  - (void )invalidateContentOcct: (id )theSender;
+  @end
+  @implementation UIView (UIViewOcctAdditions)
+  - (void )invalidateContentOcct: (id )theSender
+  {
+    (void )theSender;
+    [self setNeedsDisplay];
+  }
+  @end
+#else
+  @interface NSView (NSViewOcctAdditions)
+  - (void )invalidateContentOcct: (id )theSender;
+  @end
+  @implementation NSView (NSViewOcctAdditions)
+  - (void )invalidateContentOcct: (id )theSender
+  {
+    (void )theSender;
+    [self setNeedsDisplay: YES];
+  }
+  @end
+#endif
+
 // =======================================================================
 // function : Cocoa_Window
 // purpose  :
@@ -377,3 +402,30 @@ void Cocoa_Window::Size (Standard_Integer& theWidth,
   theWidth  = (Standard_Integer )aBounds.size.width;
   theHeight = (Standard_Integer )aBounds.size.height;
 }
+
+// =======================================================================
+// function : InvalidateContent
+// purpose  :
+// =======================================================================
+void Cocoa_Window::InvalidateContent (const Handle(Aspect_DisplayConnection)& )
+{
+  if (myHView == NULL)
+  {
+    return;
+  }
+
+  if ([NSThread isMainThread])
+  {
+  #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+    [myHView setNeedsDisplay];
+  #else
+    [myHView setNeedsDisplay: YES];
+  #endif
+  }
+  else
+  {
+    [myHView performSelectorOnMainThread: @selector(invalidateContentOcct:)
+                              withObject: NULL
+                           waitUntilDone: NO];
+  }
+}
index 237cf93..626ed07 100644 (file)
@@ -2345,23 +2345,24 @@ static DWORD WINAPI tkLoop(VOID)
   Standard_Boolean toLoop = Standard_True;
   while (toLoop)
   {
-    while(Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT));
+    // The natural way is first flushing events, already put into queue, and then processing custom code in-between.
+    // Unfortunately, Tcl has no API returning the number of queued events like XPending(), and only empty state can be checked.
+    // Since events can be continuously fed from parallel threads, Tcl_DoOneEvent might never return empty state at all.
+    const bool isTclEventQueueEmpty = Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT) == 0;
     if (console_semaphore == HAS_CONSOLE_COMMAND)
     {
-      TCollection_AsciiString aCmdUtf8 (console_command);
-      if (Draw_Interprete (aCmdUtf8.ToCString()))
+      const TCollection_AsciiString aCmdUtf8 (console_command);
+      const bool wasInterpreted = Draw_Interprete (aCmdUtf8.ToCString());
+      if (Draw_IsConsoleSubsystem)
       {
-        if (Draw_IsConsoleSubsystem) Prompt (interp, 0);
-      }
-      else
-      {
-        if (Draw_IsConsoleSubsystem) Prompt (interp, 1);
+        Prompt (interp, wasInterpreted ? 0 : 1);
       }
       console_semaphore = WAIT_CONSOLE_COMMAND;
     }
-    else
+    else if (isTclEventQueueEmpty)
     {
-      Sleep(100);
+      // release CPU while polling
+      Sleep (1);
     }
   #ifdef _TK
     // We should not exit until the Main Tk window is closed
index 3056b3c..f52df39 100644 (file)
@@ -59,6 +59,7 @@
 #include <Image_AlienPixMap.hxx>
 #include <Image_VideoRecorder.hxx>
 #include <OpenGl_GraphicDriver.hxx>
+#include <OSD.hxx>
 #include <OSD_Timer.hxx>
 #include <TColStd_HSequenceOfAsciiString.hxx>
 #include <TColStd_SequenceOfInteger.hxx>
@@ -574,6 +575,121 @@ TCollection_AsciiString ViewerTest::GetCurrentViewName ()
 {
   return ViewerTest_myViews.Find2( ViewerTest::CurrentView());
 }
+
+//! Auxiliary tool performing continuous redraws of specified window.
+class ViewerTest_ContinuousRedrawer
+{
+public:
+  //! Return global instance.
+  static ViewerTest_ContinuousRedrawer& Instance()
+  {
+    static ViewerTest_ContinuousRedrawer aRedrawer;
+    return aRedrawer;
+  }
+public:
+
+  //! Destructor.
+  ~ViewerTest_ContinuousRedrawer()
+  {
+    Stop();
+  }
+
+  //! Start thread.
+  void Start (const Handle(Aspect_Window)& theWindow,
+              Standard_Real theTargetFps)
+  {
+    if (myWindow != theWindow
+     || myTargetFps != theTargetFps)
+    {
+      Stop();
+      myWindow = theWindow;
+      myTargetFps = theTargetFps;
+    }
+    if (myThread.GetId() == 0)
+    {
+      myToStop = false;
+      myThread.Run (this);
+    }
+  }
+
+  //! Stop thread.
+  void Stop (const Handle(Aspect_Window)& theWindow = NULL)
+  {
+    if (!theWindow.IsNull()
+      && myWindow != theWindow)
+    {
+      return;
+    }
+
+    {
+      Standard_Mutex::Sentry aLock (myMutex);
+      myToStop = true;
+    }
+    myThread.Wait();
+    myToStop = false;
+    myWindow.Nullify();
+  }
+
+private:
+
+  //! Thread loop.
+  void doThreadLoop()
+  {
+    Handle(Aspect_DisplayConnection) aDisp = new Aspect_DisplayConnection();
+    OSD_Timer aTimer;
+    aTimer.Start();
+    Standard_Real aTimeOld = 0.0;
+    const Standard_Real aTargetDur = myTargetFps > 0.0 ? 1.0 / myTargetFps : -1.0;
+    for (;;)
+    {
+      {
+        Standard_Mutex::Sentry aLock (myMutex);
+        if (myToStop)
+        {
+          return;
+        }
+      }
+      if (myTargetFps > 0.0)
+      {
+        const Standard_Real aTimeNew  = aTimer.ElapsedTime();
+        const Standard_Real aDuration = aTimeNew - aTimeOld;
+        if (aDuration >= aTargetDur)
+        {
+          myWindow->InvalidateContent (aDisp);
+          aTimeOld = aTimeNew;
+        }
+      }
+      else
+      {
+        myWindow->InvalidateContent (aDisp);
+      }
+
+      OSD::MilliSecSleep (1);
+    }
+  }
+
+  //! Thread creation callback.
+  static Standard_Address doThreadWrapper (Standard_Address theData)
+  {
+    ViewerTest_ContinuousRedrawer* aThis = (ViewerTest_ContinuousRedrawer* )theData;
+    aThis->doThreadLoop();
+    return 0;
+  }
+
+  //! Empty constructor.
+  ViewerTest_ContinuousRedrawer()
+  : myThread (doThreadWrapper),
+    myTargetFps (0.0),
+    myToStop (false) {}
+
+private:
+  Handle(Aspect_Window) myWindow;
+  OSD_Thread      myThread;
+  Standard_Mutex  myMutex;
+  Standard_Real   myTargetFps;
+  volatile bool   myToStop;
+};
+
 //==============================================================================
 //function : ViewerInit
 //purpose  : Create the window viewer and initialize all the global variable
@@ -617,15 +733,26 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
     aPxHeight = thePxHeight;
 
   // Get graphic driver (create it or get from another view)
-  if (!ViewerTest_myDrivers.IsBound1 (aViewNames.GetDriverName()))
+  const bool isNewDriver = !ViewerTest_myDrivers.IsBound1 (aViewNames.GetDriverName());
+  if (isNewDriver)
   {
     // Get connection string
   #if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX))
-    TCollection_AsciiString aDisplayName(theDisplayName);
-    if (!aDisplayName.IsEmpty())
-      SetDisplayConnection (new Aspect_DisplayConnection ());
+    if (!theDisplayName.IsEmpty())
+    {
+      SetDisplayConnection (new Aspect_DisplayConnection (theDisplayName));
+    }
     else
-      SetDisplayConnection (new Aspect_DisplayConnection (aDisplayName));
+    {
+      ::Display* aDispX = NULL;
+      // create dedicated display connection instead of reusing Tk connection
+      // so that to procede events independently through VProcessEvents()/ViewerMainLoop() callbacks
+      /*Draw_Interpretor& aCommands = Draw::GetInterpretor();
+      Tcl_Interp* aTclInterp = aCommands.Interp();
+      Tk_Window aMainWindow = Tk_MainWindow (aTclInterp);
+      aDispX = aMainWindow != NULL ? Tk_Display (aMainWindow) : NULL;*/
+      SetDisplayConnection (new Aspect_DisplayConnection (aDispX));
+    }
   #else
     (void)theDisplayName; // avoid warning on unused argument
     SetDisplayConnection (new Aspect_DisplayConnection ());
@@ -800,15 +927,13 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
     a3DViewer->SetLightOn();
   }
 
-  #if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX))
-  #if TCL_MAJOR_VERSION  < 8
-  Tk_CreateFileHandler((void*)XConnectionNumber(GetDisplayConnection()->GetDisplay()),
-      TK_READABLE, VProcessEvents, (ClientData) VT_GetWindow()->XWindow() );
-  #else
-  Tk_CreateFileHandler(XConnectionNumber(GetDisplayConnection()->GetDisplay()),
-      TK_READABLE, VProcessEvents, (ClientData) VT_GetWindow()->XWindow() );
-  #endif
-  #endif
+#if !defined(_WIN32) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX))
+  if (isNewDriver)
+  {
+    ::Display* aDispX = GetDisplayConnection()->GetDisplay();
+    Tcl_CreateFileHandler (XConnectionNumber (aDispX), TCL_READABLE, VProcessEvents, (ClientData )aDispX);
+  }
+#endif
 
   VT_GetWindow()->Map();
 
@@ -1363,6 +1488,8 @@ void ViewerTest::RemoveView (const TCollection_AsciiString& theViewName, const S
   // Delete view
   Handle(V3d_View) aView = ViewerTest_myViews.Find1(theViewName);
   Handle(AIS_InteractiveContext) aCurrentContext = FindContextByView(aView);
+  ViewerTest_ContinuousRedrawer& aRedrawer = ViewerTest_ContinuousRedrawer::Instance();
+  aRedrawer.Stop (aView->Window());
 
   // Remove view resources
   ViewerTest_myViews.UnBind1(theViewName);
@@ -1399,11 +1526,7 @@ void ViewerTest::RemoveView (const TCollection_AsciiString& theViewName, const S
       {
         ViewerTest_myDrivers.UnBind2 (aCurrentContext->CurrentViewer()->Driver());
       #if !defined(_WIN32) && !defined(__WIN32__) && (!defined(__APPLE__) || defined(MACOSX_USE_GLX))
-        #if TCL_MAJOR_VERSION  < 8
-        Tk_DeleteFileHandler((void*)XConnectionNumber(aCurrentContext->CurrentViewer()->Driver()->GetDisplayConnection()->GetDisplay()));
-        #else
-        Tk_DeleteFileHandler(XConnectionNumber(aCurrentContext->CurrentViewer()->Driver()->GetDisplayConnection()->GetDisplay()));
-        #endif
+        Tcl_DeleteFileHandler (XConnectionNumber (aCurrentContext->CurrentViewer()->Driver()->GetDisplayConnection()->GetDisplay()));
       #endif
       }
 
@@ -2553,235 +2676,297 @@ int max( int a, int b )
     return b;
 }
 
-int ViewerMainLoop(Standard_Integer argc, const char** argv)
-
+int ViewerMainLoop (Standard_Integer argc, const char** argv)
 {
   static XEvent aReport;
-  Standard_Boolean pick = argc > 0;
-  Display *aDisplay = GetDisplayConnection()->GetDisplay();
+  const Standard_Boolean toPick = argc > 0;
+  Standard_Boolean toPickMore = toPick;
+  Display* aDisplay = GetDisplayConnection()->GetDisplay();
   XNextEvent (aDisplay, &aReport);
 
   // Handle event for the chosen display connection
-  switch (aReport.type) {
-      case ClientMessage:
+  switch (aReport.type)
+  {
+    case ClientMessage:
+    {
+      if ((Atom)aReport.xclient.data.l[0] == GetDisplayConnection()->GetAtom(Aspect_XA_DELETE_WINDOW))
+      {
+        // Close the window
+        ViewerTest::RemoveView(FindViewIdByWindowHandle (aReport.xclient.window));
+        return toPick ? 0 : 1;
+      }
+      break;
+    }
+    case FocusIn:
+    {
+      // Activate inactive view
+      Window aWindow = GetWindowHandle(VT_GetWindow());
+      if (aWindow != aReport.xfocus.window)
+      {
+        ActivateView (FindViewIdByWindowHandle (aReport.xfocus.window));
+      }
+      break;
+    }
+    case Expose:
+    {
+      Window anXWindow = GetWindowHandle (VT_GetWindow());
+      if (anXWindow == aReport.xexpose.window)
+      {
+        VT_ProcessExpose();
+      }
+
+      // remove all the ExposureMask and process them at once
+      for (int aNbMaxEvents = XPending (aDisplay); aNbMaxEvents > 0; --aNbMaxEvents)
+      {
+        if (!XCheckWindowEvent (aDisplay, anXWindow, ExposureMask, &aReport))
         {
-          if((Atom)aReport.xclient.data.l[0] == GetDisplayConnection()->GetAtom(Aspect_XA_DELETE_WINDOW))
-          {
-            // Close the window
-            ViewerTest::RemoveView(FindViewIdByWindowHandle (aReport.xclient.window));
-          }
+          break;
         }
-        return 0;
-     case FocusIn:
+      }
+
+      break;
+    }
+    case ConfigureNotify:
+    {
+      // remove all the StructureNotifyMask and process them at once
+      Window anXWindow = GetWindowHandle (VT_GetWindow());
+      for (int aNbMaxEvents = XPending (aDisplay); aNbMaxEvents > 0; --aNbMaxEvents)
+      {
+        if (!XCheckWindowEvent (aDisplay, anXWindow, StructureNotifyMask, &aReport))
+        {
+          break;
+        }
+      }
+
+      if (anXWindow == aReport.xconfigure.window)
+      {
+        VT_ProcessConfigure();
+      }
+      break;
+    }
+    case KeyPress:
+    {
+      KeySym aKeySym;
+      char aKeyBuf[11];
+      XComposeStatus status_in_out;
+      int aKeyLen = XLookupString ((XKeyEvent* )&aReport, (char* )aKeyBuf, 10, &aKeySym, &status_in_out);
+      aKeyBuf[aKeyLen] = '\0';
+      if (aKeyLen != 0)
       {
-         // Activate inactive view
-         Window aWindow = GetWindowHandle(VT_GetWindow());
-         if(aWindow != aReport.xfocus.window)
-         {
-           ActivateView (FindViewIdByWindowHandle (aReport.xfocus.window));
-         }
+        VT_ProcessKeyPress (aKeyBuf);
       }
       break;
-      case Expose:
+    }
+    case ButtonPress:
+    {
+      X_ButtonPress = aReport.xbutton.x;
+      Y_ButtonPress = aReport.xbutton.y;
+      if (aReport.xbutton.button == Button1)
+      {
+        if (aReport.xbutton.state & ControlMask)
         {
-          VT_ProcessExpose();
+          toPickMore = VT_ProcessButton1Press (argc, argv, toPick, (aReport.xbutton.state & ShiftMask) != 0);
         }
-        break;
-      case ConfigureNotify:
+        else
         {
-          VT_ProcessConfigure();
+          IsDragged = Standard_True;
+          DragFirst = Standard_True;
         }
+      }
+      else if (aReport.xbutton.button == Button3)
+      {
+        // Start rotation
+        VT_ProcessButton3Press();
+      }
+      break;
+    }
+    case ButtonRelease:
+    {
+      if (!IsDragged)
+      {
+        VT_ProcessButton3Release();
         break;
-      case KeyPress:
-        {
-
-          KeySym ks_ret ;
-          char buf_ret[11] ;
-          int ret_len ;
-          XComposeStatus status_in_out;
+      }
 
-          ret_len = XLookupString( ( XKeyEvent *)&aReport ,
-            (char *) buf_ret , 10 ,
-            &ks_ret , &status_in_out ) ;
+      Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext();
+      if (aContext.IsNull())
+      {
+        std::cout << "Error: No active view.\n";
+        return 0;
+      }
 
+      if (!DragFirst
+        && aContext->IsDisplayed (GetRubberBand()))
+      {
+        aContext->Remove (GetRubberBand(), Standard_False);
+        aContext->CurrentViewer()->RedrawImmediate();
+      }
 
-          buf_ret[ret_len] = '\0' ;
+      if (aReport.xbutton.button != Button1)
+      {
+        break;
+      }
 
-          if (ret_len)
-          {
-            VT_ProcessKeyPress (buf_ret);
-          }
+      const Standard_Boolean isShiftPressed = (aReport.xbutton.state & ShiftMask) != 0;
+      if (DragFirst)
+      {
+        if (isShiftPressed)
+        {
+          aContext->ShiftSelect (Standard_True);
         }
-        break;
-      case ButtonPress:
+        else
         {
-          X_ButtonPress = aReport.xbutton.x;
-          Y_ButtonPress = aReport.xbutton.y;
-
-          if (aReport.xbutton.button == Button1)
-          {
-            if (aReport.xbutton.state & ControlMask)
-            {
-              pick = VT_ProcessButton1Press (argc, argv, pick, (aReport.xbutton.state & ShiftMask));
-            }
-            else
-            {
-              IsDragged = Standard_True;
-              DragFirst = Standard_True;
-            }
-          }
-          else if (aReport.xbutton.button == Button3)
-          {
-            // Start rotation
-            VT_ProcessButton3Press();
-          }
+          aContext->Select (Standard_True);
         }
+      }
+      else
+      {
+        if (isShiftPressed)
+        {
+          aContext->ShiftSelect (Min (X_ButtonPress, X_Motion), Min (Y_ButtonPress, Y_Motion),
+                                 Max (X_ButtonPress, X_Motion), Max (Y_ButtonPress, Y_Motion),
+                                 ViewerTest::CurrentView(), Standard_True);
+        }
+        else
+        {
+          aContext->Select (Min (X_ButtonPress, X_Motion), Min(Y_ButtonPress, Y_Motion),
+                            Max (X_ButtonPress, X_Motion), Max(Y_ButtonPress, Y_Motion),
+                            ViewerTest::CurrentView(), Standard_True);
+        }
+      }
+      IsDragged = Standard_False;
+      break;
+    }
+    case MotionNotify:
+    {
+      Window anXWindow = GetWindowHandle (VT_GetWindow());
+      if (anXWindow != aReport.xmotion.window)
+      {
         break;
-      case ButtonRelease:
+      }
+
+      // remove all the ButtonMotionMask and process them at once
+      for (int aNbMaxEvents = XPending (aDisplay); aNbMaxEvents > 0; --aNbMaxEvents)
+      {
+        if (!XCheckWindowEvent (aDisplay, anXWindow, ButtonMotionMask | PointerMotionMask, &aReport))
         {
-          if( IsDragged )
-          {
-            if( !DragFirst )
-            {
-              if (ViewerTest::GetAISContext()->IsDisplayed (GetRubberBand()))
-              {
-                ViewerTest::GetAISContext()->Remove (GetRubberBand(), Standard_False);
-                ViewerTest::GetAISContext()->CurrentViewer()->RedrawImmediate();
-              }
-            }
+          break;
+        }
+      }
 
-            Handle( AIS_InteractiveContext ) aContext = ViewerTest::GetAISContext();
-            if( aContext.IsNull() )
-            {
-              cout << "The context is null. Please use vinit before createmesh" << endl;
-              return 0;
-            }
+      if (IsDragged)
+      {
+        if (!DragFirst
+          && ViewerTest::GetAISContext()->IsDisplayed (GetRubberBand()))
+        {
+          ViewerTest::GetAISContext()->Remove (GetRubberBand(), Standard_False);
+        }
 
-            Standard_Boolean ShiftPressed = ( aReport.xbutton.state & ShiftMask );
-            if( aReport.xbutton.button==1 )
-              if( DragFirst )
-                if( ShiftPressed )
-                {
-                  aContext->ShiftSelect (Standard_True);
-                }
-                else
-                {
-                  aContext->Select (Standard_True);
-                }
-              else
-                if( ShiftPressed )
-                {
-                  aContext->ShiftSelect(Min(X_ButtonPress, X_Motion), Min(Y_ButtonPress, Y_Motion),
-                                        Max(X_ButtonPress, X_Motion), Max(Y_ButtonPress, Y_Motion),
-                                        ViewerTest::CurrentView(), Standard_True);
-                }
-                else
-                {
-                  aContext->Select(Min(X_ButtonPress, X_Motion), Min(Y_ButtonPress, Y_Motion),
-                                   Max(X_ButtonPress, X_Motion), Max(Y_ButtonPress, Y_Motion),
-                                   ViewerTest::CurrentView(), Standard_True);
-                }
-            else
-              VT_ProcessButton3Release();
+        X_Motion = aReport.xmotion.x;
+        Y_Motion = aReport.xmotion.y;
+        DragFirst = Standard_False;
 
-            IsDragged = Standard_False;
-          }
-          else
-            VT_ProcessButton3Release();
-        }
-        break;
-      case MotionNotify:
+        Window aWindow = GetWindowHandle(VT_GetWindow());
+        Window aRoot;
+        int anX, anY;
+        unsigned int aWidth, aHeight, aBorderWidth, aDepth;
+        XGetGeometry (aDisplay, aWindow, &aRoot, &anX, &anY, &aWidth, &aHeight, &aBorderWidth, &aDepth);
+        GetRubberBand()->SetRectangle (X_ButtonPress, aHeight - Y_ButtonPress, X_Motion, aHeight - Y_Motion);
+        ViewerTest::GetAISContext()->Display (GetRubberBand(), 0, -1, Standard_False, AIS_DS_Displayed);
+        ViewerTest::GetAISContext()->CurrentViewer()->RedrawImmediate();
+      }
+      else
+      {
+        X_Motion = aReport.xmotion.x;
+        Y_Motion = aReport.xmotion.y;
+        if ((aReport.xmotion.state & ControlMask) != 0)
         {
-          if (GetWindowHandle (VT_GetWindow()) != aReport.xmotion.window)
+          if ((aReport.xmotion.state & Button1Mask) != 0)
           {
-            break;
+            ProcessControlButton1Motion();
           }
-          if( IsDragged )
+          else if ((aReport.xmotion.state & Button2Mask) != 0)
           {
-            if( !DragFirst )
-            {
-              if (ViewerTest::GetAISContext()->IsDisplayed (GetRubberBand()))
-              {
-                ViewerTest::GetAISContext()->Remove (GetRubberBand(), Standard_False);
-              }
-            }
-
-            X_Motion = aReport.xmotion.x;
-            Y_Motion = aReport.xmotion.y;
-            DragFirst = Standard_False;
-
-            Window aWindow = GetWindowHandle(VT_GetWindow());
-            Window aRoot;
-            int anX, anY;
-            unsigned int aWidth, aHeight, aBorderWidth, aDepth;
-            XGetGeometry (aDisplay, aWindow, &aRoot, &anX, &anY, &aWidth, &aHeight, &aBorderWidth, &aDepth);
-            GetRubberBand()->SetRectangle (X_ButtonPress, aHeight - Y_ButtonPress, X_Motion, aHeight - Y_Motion);
-            ViewerTest::GetAISContext()->Display (GetRubberBand(), 0, -1, Standard_False, AIS_DS_Displayed);
-            ViewerTest::GetAISContext()->CurrentViewer()->RedrawImmediate();
+            VT_ProcessControlButton2Motion();
           }
-          else
+          else if ((aReport.xmotion.state & Button3Mask) != 0)
           {
-            X_Motion = aReport.xmotion.x;
-            Y_Motion = aReport.xmotion.y;
-
-            // remove all the ButtonMotionMaskr
-            while( XCheckMaskEvent( aDisplay, ButtonMotionMask, &aReport) ) ;
-
-            if ( aReport.xmotion.state & ControlMask ) {
-              if ( aReport.xmotion.state & Button1Mask ) {
-                ProcessControlButton1Motion();
-              }
-              else if ( aReport.xmotion.state & Button2Mask ) {
-                VT_ProcessControlButton2Motion();
-              }
-              else if ( aReport.xmotion.state & Button3Mask ) {
-                VT_ProcessControlButton3Motion();
-              }
-            }
-            else
-            {
-              VT_ProcessMotion();
-            }
+            VT_ProcessControlButton3Motion();
           }
         }
-        break;
-}
-return pick;
+        else
+        {
+          VT_ProcessMotion();
+        }
+      }
+      break;
+    }
+  }
+  return (!toPick || toPickMore) ? 1 : 0;
 }
 
 //==============================================================================
 //function : VProcessEvents
-//purpose  : call by Tk_CreateFileHandler() to be able to manage the
-//       event in the Viewer window
+//purpose  : manage the event in the Viewer window (see Tcl_CreateFileHandler())
 //==============================================================================
-
-static void VProcessEvents(ClientData,int)
+static void VProcessEvents (ClientData theDispX, int)
 {
-  NCollection_Vector<int> anEventNumbers;
-  // Get number of messages from every display
-  for (NCollection_DoubleMap <TCollection_AsciiString, Handle(Graphic3d_GraphicDriver)>::Iterator
-       anIter (ViewerTest_myDrivers); anIter.More(); anIter.Next())
+  Display* aDispX = (Display* )theDispX;
+  Handle(Aspect_DisplayConnection) aDispConn;
+  for (NCollection_DoubleMap<TCollection_AsciiString, Handle(Graphic3d_GraphicDriver)>::Iterator
+       aDriverIter (ViewerTest_myDrivers); aDriverIter.More(); aDriverIter.Next())
   {
-    anEventNumbers.Append(XPending (anIter.Key2()->GetDisplayConnection()->GetDisplay()));
+    const Handle(Aspect_DisplayConnection)& aDispConnTmp = aDriverIter.Key2()->GetDisplayConnection();
+    if (aDispConnTmp->GetDisplay() == aDispX)
+    {
+      aDispConn = aDispConnTmp;
+      break;
+    }
+  }
+  if (aDispConn.IsNull())
+  {
+    std::cerr << "Error: ViewerTest is unable processing messages for unknown X Display\n";
+    return;
   }
-    // Handle events for every display
-  int anEventIter = 0;
-  for (NCollection_DoubleMap <TCollection_AsciiString, Handle(Graphic3d_GraphicDriver)>::Iterator
-       anIter (ViewerTest_myDrivers); anIter.More(); anIter.Next(), anEventIter++)
+
+  // process new events in queue
+  SetDisplayConnection (aDispConn);
+  int aNbRemain = 0;
+  for (int aNbEventsMax = XPending (aDispX), anEventIter (0);;)
   {
-    for (int i = 0; i < anEventNumbers.Value(anEventIter) &&
-         XPending (anIter.Key2()->GetDisplayConnection()->GetDisplay()) > 0; ++i)
+    const int anEventResult = ViewerMainLoop (0, NULL);
+    if (anEventResult == 0)
     {
-      SetDisplayConnection (anIter.Key2()->GetDisplayConnection());
-      int anEventResult = ViewerMainLoop( 0, NULL);
-      // If window is closed or context was not found finish current event processing loop
-      if (!anEventResult)
-       return;
+      return;
+    }
+
+    aNbRemain = XPending (aDispX);
+    if (++anEventIter >= aNbEventsMax
+     || aNbRemain <= 0)
+    {
+      break;
     }
   }
 
-  SetDisplayConnection (ViewerTest::GetAISContext()->CurrentViewer()->Driver()->GetDisplayConnection());
+  // Listening X events through Tcl_CreateFileHandler() callback is fragile,
+  // it is possible that new events will arrive to queue before the end of this callback
+  // so that either this callback should go into an infinite loop (blocking processing of other events)
+  // or to keep unprocessed events till the next queue update (which can arrive not soon).
+  // Sending a dummy event in this case is a simple workaround (still, it is possible that new event will be queued in-between).
+  if (aNbRemain != 0)
+  {
+    XEvent aDummyEvent;
+    memset (&aDummyEvent, 0, sizeof(aDummyEvent));
+    aDummyEvent.type = ClientMessage;
+    aDummyEvent.xclient.format = 32;
+    XSendEvent (aDispX, InputFocus, False, 0, &aDummyEvent);
+    XFlush (aDispX);
+  }
 
+  if (const Handle(AIS_InteractiveContext)& anActiveCtx = ViewerTest::GetAISContext())
+  {
+    SetDisplayConnection (anActiveCtx->CurrentViewer()->Driver()->GetDisplayConnection());
+  }
 }
 #endif
 
@@ -2983,7 +3168,8 @@ static int VRepaint (Draw_Interpretor& , Standard_Integer theArgNb, const char**
   {
     TCollection_AsciiString anArg (theArgVec[anArgIter]);
     anArg.LowerCase();
-    if (anArg == "-immediate")
+    if (anArg == "-immediate"
+     || anArg == "-imm")
     {
       isImmediateUpdate = Standard_True;
       if (anArgIter + 1 < theArgNb
@@ -2992,9 +3178,32 @@ static int VRepaint (Draw_Interpretor& , Standard_Integer theArgNb, const char**
         ++anArgIter;
       }
     }
+    else if (anArg == "-continuous"
+          || anArg == "-cont"
+          || anArg == "-fps"
+          || anArg == "-framerate")
+    {
+      Standard_Real aFps = -1.0;
+      if (anArgIter + 1 < theArgNb
+       && TCollection_AsciiString (theArgVec[anArgIter + 1]).IsRealValue())
+      {
+        aFps = Draw::Atof (theArgVec[++anArgIter]);
+      }
+
+      ViewerTest_ContinuousRedrawer& aRedrawer = ViewerTest_ContinuousRedrawer::Instance();
+      if (Abs (aFps) >= 1.0)
+      {
+        aRedrawer.Start (aView->Window(), aFps);
+      }
+      else
+      {
+        aRedrawer.Stop();
+      }
+    }
     else
     {
       std::cout << "Syntax error at '" << anArg << "'\n";
+      return 1;
     }
   }
 
@@ -11692,7 +11901,7 @@ static Standard_Integer VXRotate (Draw_Interpretor& di,
     di << argv[0] << "ERROR : use 'vinit' command before \n";
     return 1;
   }
-  
+
   if (argc != 3)
   {
     di << "ERROR : Usage : " << argv[0] << " name angle\n";
@@ -12566,8 +12775,13 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
     "   \"scale\" - specifies factor to scale computed z range.\n",
     __FILE__, VZFit, group);
   theCommands.Add("vrepaint",
-            "vrepaint [-immediate]"
-    "\n\t\t: force redraw",
+            "vrepaint [-immediate] [-continuous FPS]"
+    "\n\t\t: force redraw of active View"
+    "\n\t\t:   -immediate  flag performs redraw of immediate layers only;"
+    "\n\t\t:   -continuous activates/deactivates continuous redraw of active View,"
+    "\n\t\t:                0 means no continuous rendering,"
+    "\n\t\t:               -1 means non-stop redraws,"
+    "\n\t\t:               >0 specifies target framerate,",
     __FILE__,VRepaint,group);
   theCommands.Add("vclear",
     "vclear          : vclear"
index ed0643a..88ebbea 100644 (file)
@@ -355,4 +355,16 @@ void WNT_Window::SetPos (const Standard_Integer theX,  const Standard_Integer th
   aYBottom = theY1;
 }
 
+// =======================================================================
+// function : InvalidateContent
+// purpose  :
+// =======================================================================
+void WNT_Window::InvalidateContent (const Handle(Aspect_DisplayConnection)& )
+{
+  if (myHWindow != NULL)
+  {
+    ::InvalidateRect ((HWND )myHWindow, NULL, TRUE);
+  }
+}
+
 #endif // _WIN32
index 7de5c41..d031ab4 100644 (file)
@@ -115,6 +115,10 @@ public:
   //! Returns nothing on Windows
   virtual Aspect_FBConfig NativeFBConfig() const Standard_OVERRIDE { return NULL; }
 
+  //! Invalidate entire window content by calling InvalidateRect() WinAPI function, resulting in WM_PAINT event put into window message loop.
+  //! Method can be called from non-window thread, and system will also automatically aggregate multiple events into single one.
+  Standard_EXPORT virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp = NULL) Standard_OVERRIDE;
+
   DEFINE_STANDARD_RTTIEXT(WNT_Window,Aspect_Window)
 
 protected:
index 3b22153..264c61b 100644 (file)
@@ -482,4 +482,26 @@ void Xw_Window::Size (Standard_Integer& theWidth,
   theHeight = aWinAttr.height;
 }
 
+// =======================================================================
+// function : InvalidateContent
+// purpose  :
+// =======================================================================
+void Xw_Window::InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp)
+{
+  if (myXWindow == 0)
+  {
+    return;
+  }
+
+  const Handle(Aspect_DisplayConnection)& aDisp = !theDisp.IsNull() ? theDisp : myDisplay;
+  Display* aDispX = aDisp->GetDisplay();
+
+  XEvent anEvent;
+  memset (&anEvent, 0, sizeof(anEvent));
+  anEvent.type = Expose;
+  anEvent.xexpose.window = myXWindow;
+  XSendEvent (aDispX, myXWindow, False, ExposureMask, &anEvent);
+  XFlush (aDispX);
+}
+
 #endif //  Win32 or Mac OS X
index 0de3457..6ab4273 100644 (file)
@@ -111,6 +111,13 @@ public:
     return myFBConfig;
   }
 
+  //! Invalidate entire window content through generation of Expose event.
+  //! This method does not aggregate multiple calls into single event - dedicated event will be sent on each call.
+  //! When NULL display connection is specified, the connection specified on window creation will be used.
+  //! Sending exposure messages from non-window thread would require dedicated display connection opened specifically
+  //! for this working thread to avoid race conditions, since Xlib display connection is not thread-safe by default.
+  Standard_EXPORT virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp) Standard_OVERRIDE;
+
 protected:
 
   Handle(Aspect_DisplayConnection) myDisplay; //!< X Display connection