summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXNotify.c
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/tkMacOSXNotify.c')
-rw-r--r--macosx/tkMacOSXNotify.c166
1 files changed, 143 insertions, 23 deletions
diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c
index 6120ae5..367f3b6 100644
--- a/macosx/tkMacOSXNotify.c
+++ b/macosx/tkMacOSXNotify.c
@@ -261,7 +261,6 @@ Tk_MacOSXSetupTkNotifier(void)
Tcl_CreateEventSource(TkMacOSXEventsSetupProc,
TkMacOSXEventsCheckProc, NULL);
TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL);
- Tcl_SetServiceMode(TCL_SERVICE_ALL);
TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode);
TclMacOSXNotifierAddRunLoopMode(NSModalPanelRunLoopMode);
}
@@ -299,6 +298,86 @@ 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
@@ -312,18 +391,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(
+ ClientData clientData)
+{
+
+ if (ticker) {
+ ticker = Tcl_CreateTimerHandler(TICK, Heartbeat, NULL);
+ }
+}
+
+static const Tcl_Time zeroBlockTime = { 0, 0 };
+
static void
TkMacOSXEventsSetupProc(
ClientData clientData,
int flags)
{
- static Bool havePeriodicEvents = NO;
NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode];
/*
@@ -331,35 +425,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);
}
}
}
@@ -387,6 +485,7 @@ TkMacOSXEventsCheckProc(
int flags)
{
NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode];
+ int eventsFound = 0;
/*
* runloopMode will be nil if we are in a Tcl event loop.
@@ -425,17 +524,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 {
@@ -452,6 +553,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);
+ }
}
}