diff --git a/src/Aspect/Aspect_Window.hxx b/src/Aspect/Aspect_Window.hxx index 3791c62469..2783f74518 100644 --- a/src/Aspect/Aspect_Window.hxx +++ b/src/Aspect/Aspect_Window.hxx @@ -30,6 +30,8 @@ #include #include #include + +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 . Standard_EXPORT virtual void Map() const = 0; - + //! Closes the window . Standard_EXPORT virtual void Unmap() const = 0; - + //! Apply the resizing to the window . Standard_EXPORT virtual Aspect_TypeOfResize DoResize() const = 0; - + //! Apply the mapping change to the window . //! 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 is opened //! and False if the window is closed. Standard_EXPORT virtual Standard_Boolean IsMapped() const = 0; - + //! Returns True if the window 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: diff --git a/src/Cocoa/Cocoa_Window.hxx b/src/Cocoa/Cocoa_Window.hxx index 12ad440df2..dcfe0d37cf 100644 --- a/src/Cocoa/Cocoa_Window.hxx +++ b/src/Cocoa/Cocoa_Window.hxx @@ -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 diff --git a/src/Cocoa/Cocoa_Window.mm b/src/Cocoa/Cocoa_Window.mm index 73aa70110d..2613a57419 100644 --- a/src/Cocoa/Cocoa_Window.mm +++ b/src/Cocoa/Cocoa_Window.mm @@ -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]; + } +} diff --git a/src/Draw/Draw_Window.cxx b/src/Draw/Draw_Window.cxx index 237cf93340..626ed07959 100644 --- a/src/Draw/Draw_Window.cxx +++ b/src/Draw/Draw_Window.cxx @@ -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 diff --git a/src/ViewerTest/ViewerTest_ViewerCommands.cxx b/src/ViewerTest/ViewerTest_ViewerCommands.cxx index 3056b3ca6a..f52df39bb8 100644 --- a/src/ViewerTest/ViewerTest_ViewerCommands.cxx +++ b/src/ViewerTest/ViewerTest_ViewerCommands.cxx @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -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: - { - if((Atom)aReport.xclient.data.l[0] == GetDisplayConnection()->GetAtom(Aspect_XA_DELETE_WINDOW)) - { - // Close the window - ViewerTest::RemoveView(FindViewIdByWindowHandle (aReport.xclient.window)); - } - } - return 0; - case FocusIn: + switch (aReport.type) + { + case ClientMessage: + { + if ((Atom)aReport.xclient.data.l[0] == GetDisplayConnection()->GetAtom(Aspect_XA_DELETE_WINDOW)) { - // Activate inactive view - Window aWindow = GetWindowHandle(VT_GetWindow()); - if(aWindow != aReport.xfocus.window) - { - ActivateView (FindViewIdByWindowHandle (aReport.xfocus.window)); - } + // Close the window + ViewerTest::RemoveView(FindViewIdByWindowHandle (aReport.xclient.window)); + return toPick ? 0 : 1; } break; - case Expose: + } + 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)) { - VT_ProcessExpose(); + break; } - break; - case ConfigureNotify: + } + + 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)) { - VT_ProcessConfigure(); + break; } - break; - case KeyPress: + } + + 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) + { + VT_ProcessKeyPress (aKeyBuf); + } + break; + } + case ButtonPress: + { + X_ButtonPress = aReport.xbutton.x; + Y_ButtonPress = aReport.xbutton.y; + if (aReport.xbutton.button == Button1) + { + if (aReport.xbutton.state & ControlMask) { + toPickMore = VT_ProcessButton1Press (argc, argv, toPick, (aReport.xbutton.state & ShiftMask) != 0); + } + else + { + IsDragged = Standard_True; + DragFirst = Standard_True; + } + } + else if (aReport.xbutton.button == Button3) + { + // Start rotation + VT_ProcessButton3Press(); + } + break; + } + case ButtonRelease: + { + if (!IsDragged) + { + VT_ProcessButton3Release(); + break; + } - KeySym ks_ret ; - char buf_ret[11] ; - int ret_len ; - XComposeStatus status_in_out; + Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext(); + if (aContext.IsNull()) + { + std::cout << "Error: No active view.\n"; + return 0; + } - ret_len = XLookupString( ( XKeyEvent *)&aReport , - (char *) buf_ret , 10 , - &ks_ret , &status_in_out ) ; + if (!DragFirst + && aContext->IsDisplayed (GetRubberBand())) + { + aContext->Remove (GetRubberBand(), Standard_False); + aContext->CurrentViewer()->RedrawImmediate(); + } + if (aReport.xbutton.button != Button1) + { + break; + } - buf_ret[ret_len] = '\0' ; + const Standard_Boolean isShiftPressed = (aReport.xbutton.state & ShiftMask) != 0; + if (DragFirst) + { + if (isShiftPressed) + { + aContext->ShiftSelect (Standard_True); + } + else + { + 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; + } - if (ret_len) + // remove all the ButtonMotionMask and process them at once + for (int aNbMaxEvents = XPending (aDisplay); aNbMaxEvents > 0; --aNbMaxEvents) + { + if (!XCheckWindowEvent (aDisplay, anXWindow, ButtonMotionMask | PointerMotionMask, &aReport)) + { + break; + } + } + + if (IsDragged) + { + if (!DragFirst + && 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(); + } + else + { + X_Motion = aReport.xmotion.x; + Y_Motion = aReport.xmotion.y; + if ((aReport.xmotion.state & ControlMask) != 0) + { + if ((aReport.xmotion.state & Button1Mask) != 0) { - VT_ProcessKeyPress (buf_ret); + ProcessControlButton1Motion(); + } + else if ((aReport.xmotion.state & Button2Mask) != 0) + { + VT_ProcessControlButton2Motion(); + } + else if ((aReport.xmotion.state & Button3Mask) != 0) + { + VT_ProcessControlButton3Motion(); } } - 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(); - } + VT_ProcessMotion(); } - break; - case ButtonRelease: - { - if( IsDragged ) - { - if( !DragFirst ) - { - if (ViewerTest::GetAISContext()->IsDisplayed (GetRubberBand())) - { - ViewerTest::GetAISContext()->Remove (GetRubberBand(), Standard_False); - ViewerTest::GetAISContext()->CurrentViewer()->RedrawImmediate(); - } - } - - Handle( AIS_InteractiveContext ) aContext = ViewerTest::GetAISContext(); - if( aContext.IsNull() ) - { - cout << "The context is null. Please use vinit before createmesh" << endl; - return 0; - } - - 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(); - - IsDragged = Standard_False; - } - else - VT_ProcessButton3Release(); - } - break; - case MotionNotify: - { - if (GetWindowHandle (VT_GetWindow()) != aReport.xmotion.window) - { - break; - } - if( IsDragged ) - { - 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(); - } - else - { - 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(); - } - } - } - break; -} -return pick; + } + 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 anEventNumbers; - // Get number of messages from every display - for (NCollection_DoubleMap ::Iterator - anIter (ViewerTest_myDrivers); anIter.More(); anIter.Next()) + Display* aDispX = (Display* )theDispX; + Handle(Aspect_DisplayConnection) aDispConn; + for (NCollection_DoubleMap::Iterator + aDriverIter (ViewerTest_myDrivers); aDriverIter.More(); aDriverIter.Next()) { - anEventNumbers.Append(XPending (anIter.Key2()->GetDisplayConnection()->GetDisplay())); - } - // Handle events for every display - int anEventIter = 0; - for (NCollection_DoubleMap ::Iterator - anIter (ViewerTest_myDrivers); anIter.More(); anIter.Next(), anEventIter++) - { - for (int i = 0; i < anEventNumbers.Value(anEventIter) && - XPending (anIter.Key2()->GetDisplayConnection()->GetDisplay()) > 0; ++i) + const Handle(Aspect_DisplayConnection)& aDispConnTmp = aDriverIter.Key2()->GetDisplayConnection(); + if (aDispConnTmp->GetDisplay() == aDispX) { - 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; + aDispConn = aDispConnTmp; + break; + } + } + if (aDispConn.IsNull()) + { + std::cerr << "Error: ViewerTest is unable processing messages for unknown X Display\n"; + return; + } + + // process new events in queue + SetDisplayConnection (aDispConn); + int aNbRemain = 0; + for (int aNbEventsMax = XPending (aDispX), anEventIter (0);;) + { + const int anEventResult = ViewerMainLoop (0, NULL); + if (anEventResult == 0) + { + 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" diff --git a/src/WNT/WNT_Window.cxx b/src/WNT/WNT_Window.cxx index ed0643a3ee..88ebbea973 100644 --- a/src/WNT/WNT_Window.cxx +++ b/src/WNT/WNT_Window.cxx @@ -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 diff --git a/src/WNT/WNT_Window.hxx b/src/WNT/WNT_Window.hxx index 7de5c41eef..d031ab4703 100644 --- a/src/WNT/WNT_Window.hxx +++ b/src/WNT/WNT_Window.hxx @@ -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: diff --git a/src/Xw/Xw_Window.cxx b/src/Xw/Xw_Window.cxx index 3b221537f0..264c61b42d 100644 --- a/src/Xw/Xw_Window.cxx +++ b/src/Xw/Xw_Window.cxx @@ -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 diff --git a/src/Xw/Xw_Window.hxx b/src/Xw/Xw_Window.hxx index 0de3457f74..6ab427370d 100644 --- a/src/Xw/Xw_Window.hxx +++ b/src/Xw/Xw_Window.hxx @@ -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