diff options
Diffstat (limited to 'macosx/tkMacOSXNotify.c')
-rw-r--r-- | macosx/tkMacOSXNotify.c | 187 |
1 files changed, 157 insertions, 30 deletions
diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c index 82ded8a..e169a7d 100644 --- a/macosx/tkMacOSXNotify.c +++ b/macosx/tkMacOSXNotify.c @@ -13,10 +13,10 @@ * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ +#include <tclInt.h> #include "tkMacOSXPrivate.h" #include "tkMacOSXInt.h" #include "tkMacOSXConstants.h" -#include <tclInt.h> #import <objc/objc-auto.h> /* This is not used for anything at the moment. */ @@ -156,6 +156,7 @@ void DebugPrintQueue(void) * this block should be removed. */ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if ([theEvent type] == NSAppKitDefined) { static Bool aWindowIsMoving = NO; switch([theEvent subtype]) { @@ -174,6 +175,8 @@ void DebugPrintQueue(void) break; } } +#endif + [super sendEvent:theEvent]; [NSApp tkCheckPasteboard]; @@ -183,6 +186,13 @@ void DebugPrintQueue(void) #endif } + +- (void) _runBackgroundLoop +{ + while(Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_TIMER_EVENTS|TCL_DONT_WAIT)){ + TkMacOSXDrawAllViews(NULL); + } +} @end #pragma mark - @@ -201,15 +211,13 @@ void DebugPrintQueue(void) *---------------------------------------------------------------------- */ -NSString * +static NSString * GetRunLoopMode(NSModalSession modalSession) { NSString *runLoopMode = nil; if (modalSession) { runLoopMode = NSModalPanelRunLoopMode; - } else if (TkpGetCapture()) { - runLoopMode = NSEventTrackingRunLoopMode; } if (!runLoopMode) { runLoopMode = [[NSRunLoop currentRunLoop] currentMode]; @@ -261,7 +269,6 @@ Tk_MacOSXSetupTkNotifier(void) Tcl_CreateEventSource(TkMacOSXEventsSetupProc, TkMacOSXEventsCheckProc, NULL); TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL); - Tcl_SetServiceMode(TCL_SERVICE_ALL); TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode); TclMacOSXNotifierAddRunLoopMode(NSModalPanelRunLoopMode); } @@ -300,10 +307,90 @@ TkMacOSXNotifyExitHandler( /* *---------------------------------------------------------------------- * + * TkMacOSXDrawAllViews -- + * + * This static function is meant to be run as an idle task. It attempts + * to redraw all views which have the tkNeedsDisplay property set to YES. + * This relies on a feature of [NSApp nextEventMatchingMask: ...] which + * is undocumented, namely that it sometimes blocks and calls drawRect + * for all views that need display before it returns. We call it with + * deQueue=NO so that it will not change anything on the AppKit event + * queue, because we only want the side effect that it runs drawRect. The + * only time when any NSViews have the needsDisplay property set to YES + * is during execution of this function. + * + * The reason for running this function as an idle task is to try to + * arrange that all widgets will be fully configured before they are + * drawn. Any idle tasks that might reconfigure them should be higher on + * the idle queue, so they should be run before the display procs are run + * by drawRect. + * + * If this function is called directly with non-NULL clientData parameter + * then the int which it references will be set to the number of windows + * that need display, but the needsDisplay property of those windows will + * not be changed. + * + * Results: + * None. + * + * Side effects: + * Parts of windows my get redrawn. + * + *---------------------------------------------------------------------- + */ + +void +TkMacOSXDrawAllViews( + ClientData clientData) +{ + int count = 0, *dirtyCount = (int *)clientData; + + for (NSWindow *window in [NSApp windows]) { + if ([[window contentView] isMemberOfClass:[TKContentView class]]) { + TKContentView *view = [window contentView]; + if ([view tkNeedsDisplay]) { + count++; + if (dirtyCount) { + continue; + } + [view setNeedsDisplayInRect:[view tkDirtyRect]]; + } + } else { + [window displayIfNeeded]; + } + } + if (dirtyCount) { + *dirtyCount = count; + } + [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:GetRunLoopMode(TkMacOSXGetModalSession()) + dequeue:NO]; + for (NSWindow *window in [NSApp windows]) { + if ([[window contentView] isMemberOfClass:[TKContentView class]]) { + TKContentView *view = [window contentView]; + + /* + * If we did not run drawRect, we set needsDisplay back to NO. + * Note that if drawRect did run it may have added to Tk's dirty + * rect, due to attempts to draw outside of drawRect's dirty rect. + */ + + if ([view needsDisplay]) { + [view setNeedsDisplay: NO]; + } + } + } + [NSApp setNeedsToDraw:NO]; +} + +/* + *---------------------------------------------------------------------- + * * TkMacOSXEventsSetupProc -- * * This procedure implements the setup part of the MacOSX event source. It - * is invoked by Tcl_DoOneEvent before calling TkMacOSXEventsProc to + * is invoked by Tcl_DoOneEvent before calling TkMacOSXEventsCheckProc to * process all queued NSEvents. In our case, all we need to do is to set * the Tcl MaxBlockTime to 0 before starting the loop to process all * queued NSEvents. @@ -313,18 +400,33 @@ TkMacOSXNotifyExitHandler( * * Side effects: * - * If NSEvents are queued, then the maximum block time will be set to 0 to - * ensure that control returns immediately to Tcl. + * If NSEvents are queued, or if there is any drawing that needs to be + * done, then the maximum block time will be set to 0 to ensure that + * Tcl_WaitForEvent returns immediately. * *---------------------------------------------------------------------- */ +#define TICK 200 +static Tcl_TimerToken ticker = NULL; + +static void +Heartbeat( + TCL_UNUSED(ClientData)) +{ + + if (ticker) { + ticker = Tcl_CreateTimerHandler(TICK, Heartbeat, NULL); + } +} + +static const Tcl_Time zeroBlockTime = { 0, 0 }; + static void TkMacOSXEventsSetupProc( ClientData dummy, int flags) { - static Bool havePeriodicEvents = NO; NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; (void)dummy; @@ -333,35 +435,39 @@ TkMacOSXEventsSetupProc( */ if (flags & TCL_WINDOW_EVENTS && !runloopMode) { - static const Tcl_Time zeroBlockTime = { 0, 0 }; + [NSApp _resetAutoreleasePool]; - /* - * Call this with dequeue=NO -- just checking if the queue is empty. - */ + /* + * After calling this setup proc, Tcl_DoOneEvent will call + * Tcl_WaitForEvent. Then it will call check proc to collect the + * events and translate them into XEvents. + * + * If we have any events waiting or if there is any drawing to be done + * we want Tcl_WaitForEvent to return immediately. So we set the block + * time to 0 and stop the heatbeat. + */ NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:GetRunLoopMode(TkMacOSXGetModalSession()) dequeue:NO]; - if (currentEvent) { - if (currentEvent.type > 0) { - Tcl_SetMaxBlockTime(&zeroBlockTime); - [NSEvent stopPeriodicEvents]; - havePeriodicEvents = NO; - } - } else if (!havePeriodicEvents){ + if ((currentEvent) || [NSApp needsToDraw] ) { + Tcl_SetMaxBlockTime(&zeroBlockTime); + Tcl_DeleteTimerHandler(ticker); + ticker = NULL; + } else if (ticker == NULL) { /* - * When the user is not generating events we schedule a "hearbeat" - * event to fire every 0.1 seconds. This helps to make the vwait - * command more responsive when there is no user input, e.g. when - * running the test suite. + * When the user is not generating events we schedule a "heartbeat" + * TimerHandler to fire every 200 milliseconds. The handler does + * nothing, but when its timer fires it causes Tcl_WaitForEvent to + * return. This helps avoid hangs when calling vwait during the + * non-regression tests. */ - havePeriodicEvents = YES; - [NSEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.1]; + ticker = Tcl_CreateTimerHandler(TICK, Heartbeat, NULL); } } } @@ -385,11 +491,11 @@ TkMacOSXEventsSetupProc( */ static void TkMacOSXEventsCheckProc( - ClientData dummy, + TCL_UNUSED(ClientData), int flags) { NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; - (void)dummy; + int eventsFound = 0; /* * runloopMode will be nil if we are in a Tcl event loop. @@ -428,17 +534,19 @@ TkMacOSXEventsCheckProc( inMode:GetRunLoopMode(modalSession) dequeue:YES]; if (currentEvent) { + /* * Generate Xevents. */ - int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent]; - Tcl_SetServiceMode(oldServiceMode); if (processedEvent) { + eventsFound++; + #ifdef TK_MAC_DEBUG_EVENTS TKLog(@" event: %@", currentEvent); #endif + if (modalSession) { [NSApp _modalSession:modalSession sendEvent:currentEvent]; } else { @@ -455,6 +563,25 @@ TkMacOSXEventsCheckProc( */ [NSApp _unlockAutoreleasePool]; + + /* + * Add an idle task to the end of the idle queue which will redisplay + * all of our dirty windows. We want this to happen after all other + * idle tasks have run so that all widgets will be configured before + * they are displayed. The drawRect method "borrows" the idle queue + * while drawing views. That is, it sends expose events which cause + * display procs to be posted as idle tasks and then runs an inner + * event loop to processes those idle tasks. We are trying to arrange + * for the idle queue to be empty when it starts that process and empty + * when it finishes. + */ + + int dirtyCount = 0; + TkMacOSXDrawAllViews(&dirtyCount); + if (dirtyCount > 0) { + Tcl_CancelIdleCall(TkMacOSXDrawAllViews, NULL); + Tcl_DoWhenIdle(TkMacOSXDrawAllViews, NULL); + } } } |