diff options
author | marc_culler <marc.culler@gmail.com> | 2020-07-18 16:53:31 (GMT) |
---|---|---|
committer | marc_culler <marc.culler@gmail.com> | 2020-07-18 16:53:31 (GMT) |
commit | b63b41199525b0e3e0e95f65e6991f75f8e31dd5 (patch) | |
tree | 40c8cd9613fe3160eb087628d192d6ee40a35487 | |
parent | 451fa4203bff3f5e6a0ab5028f900823174b4c83 (diff) | |
parent | 132c939d5ab36c6a55d7bc9e1692fd981c9a88ca (diff) | |
download | tk-b63b41199525b0e3e0e95f65e6991f75f8e31dd5.zip tk-b63b41199525b0e3e0e95f65e6991f75f8e31dd5.tar.gz tk-b63b41199525b0e3e0e95f65e6991f75f8e31dd5.tar.bz2 |
Provide better control over how and when [NSView drawRect:] is called.
-rw-r--r-- | generic/tkFont.c | 2 | ||||
-rw-r--r-- | generic/tkTest.c | 6 | ||||
-rw-r--r-- | generic/ttk/ttkTheme.c | 8 | ||||
-rw-r--r-- | macosx/tkMacOSXDraw.c | 82 | ||||
-rw-r--r-- | macosx/tkMacOSXEmbed.c | 4 | ||||
-rw-r--r-- | macosx/tkMacOSXEvent.c | 35 | ||||
-rw-r--r-- | macosx/tkMacOSXEvent.h | 4 | ||||
-rw-r--r-- | macosx/tkMacOSXInt.h | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyEvent.c | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXNotify.c | 166 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 15 | ||||
-rw-r--r-- | macosx/tkMacOSXSubwindows.c | 69 | ||||
-rw-r--r-- | macosx/tkMacOSXTest.c | 30 | ||||
-rw-r--r-- | macosx/tkMacOSXWindowEvent.c | 271 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 20 | ||||
-rw-r--r-- | macosx/tkMacOSXXStubs.c | 15 | ||||
-rw-r--r-- | tests/canvImg.test | 8 | ||||
-rw-r--r-- | tests/image.test | 17 | ||||
-rw-r--r-- | tests/listbox.test | 23 | ||||
-rw-r--r-- | tests/textDisp.test | 8 | ||||
-rw-r--r-- | tests/textWind.test | 6 | ||||
-rw-r--r-- | tests/wm.test | 12 |
22 files changed, 450 insertions, 365 deletions
diff --git a/generic/tkFont.c b/generic/tkFont.c index cbe885a..0b858fc 100644 --- a/generic/tkFont.c +++ b/generic/tkFont.c @@ -883,7 +883,7 @@ TheWorldHasChanged( * This is ignored on other platforms. */ - if (!TkpWillDrawWidget(NULL)) { + if (TkpWillDrawWidget(NULL)) { return; } diff --git a/generic/tkTest.c b/generic/tkTest.c index a8929b9..e80f488 100644 --- a/generic/tkTest.c +++ b/generic/tkTest.c @@ -31,9 +31,9 @@ #if defined(MAC_OSX_TK) #include "tkMacOSXInt.h" #include "tkScrollbar.h" -#define LOG_DISPLAY TkTestLogDisplay() +#define LOG_DISPLAY(drawable) TkTestLogDisplay(drawable) #else -#define LOG_DISPLAY 1 +#define LOG_DISPLAY(drawable) 1 #endif #ifdef __UNIX__ @@ -1581,7 +1581,7 @@ ImageDisplay( * not just the changed portion. Tests must account for this. */ - if (LOG_DISPLAY) { + if (LOG_DISPLAY(drawable)) { sprintf(buffer, "%s display %d %d %d %d", instPtr->masterPtr->imageName, imageX, imageY, width, height); Tcl_SetVar2(instPtr->masterPtr->interp, instPtr->masterPtr->varName, diff --git a/generic/ttk/ttkTheme.c b/generic/ttk/ttkTheme.c index 6bf48a3..7bde80e 100644 --- a/generic/ttk/ttkTheme.c +++ b/generic/ttk/ttkTheme.c @@ -15,13 +15,6 @@ #define PKG_ASSOC_KEY "Ttk" -#ifdef MAC_OSX_TK - extern void TkMacOSXFlushWindows(void); - #define UPDATE_WINDOWS() TkMacOSXFlushWindows() -#else - #define UPDATE_WINDOWS() -#endif - /*------------------------------------------------------------------------ * +++ Styles. * @@ -517,7 +510,6 @@ static void ThemeChangedProc(ClientData clientData) Tcl_BackgroundException(pkgPtr->interp, code); } pkgPtr->themeChangePending = 0; - UPDATE_WINDOWS(); } /* diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c index e56e666..7d12a4b 100644 --- a/macosx/tkMacOSXDraw.c +++ b/macosx/tkMacOSXDraw.c @@ -174,7 +174,9 @@ TkMacOSXBitmapRepFromDrawableRect( if (cg_image) { CGImageRelease(cg_image); } - } else if ((view = TkMacOSXDrawableView(mac_drawable)) != NULL) { + } else if (TkMacOSXDrawableView(mac_drawable) != NULL) { + TKContentView *tkview = (TKContentView *)view; + /* * Convert Tk top-left to NSView bottom-left coordinates. */ @@ -197,7 +199,7 @@ TkMacOSXBitmapRepFromDrawableRect( [view cacheDisplayInRect:view_rect toBitmapImageRep:bitmap_rep]; } else { TkMacOSXDbgMsg("No CGContext - cannot copy from screen to bitmap."); - [view setNeedsDisplay:YES]; + [tkview addTkDirtyRect:[tkview bounds]]; return NULL; } } else { @@ -1622,47 +1624,53 @@ TkMacOSXSetupDrawingContext( if (dc.context) { dc.portBounds = clipBounds = CGContextGetClipBoundingBox(dc.context); } else if (win) { - NSView *view = TkMacOSXDrawableView(macDraw); + TKContentView *view = (TKContentView *)TkMacOSXDrawableView(macDraw); if (!view) { Tcl_Panic("TkMacOSXSetupDrawingContext(): " "no NSView to draw into !"); } + if (dc.clipRgn) { + CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, + .ty = [view bounds].size.height}; + HIShapeGetBounds(dc.clipRgn, &clipBounds); + clipBounds = CGRectApplyAffineTransform(clipBounds, t); + } + if (view != [NSView focusView]) { - /* - * We can only draw into the view when the current CGContext is valid - * and belongs to the view. Validity can only be guaranteed inside of - * a view's drawRect or setFrame methods. The isDrawing attribute - * tells us whether we are being called from one of those methods. - * - * If the CGContext is not valid then we mark our view as needing - * display in the bounding rectangle of the clipping region and - * return failure. That rectangle should get drawn in a later call - * to drawRect. - * - * As an exception to the above, if mouse buttons are pressed at the - * moment when we fail to obtain a valid context we schedule the entire - * view for a redraw rather than just the clipping region. The purpose - * of this is to make sure that scrollbars get updated correctly. - */ + /* + * We can only draw into the view when the current CGContext is + * valid and belongs to the view. Validity can only be guaranteed + * inside of a view's drawRect or setFrame methods. The isDrawing + * attribute tells us whether we are being called from drawRect. + * If the CGContext is not valid then we mark our view as needing + * display. + */ - if (![NSApp isDrawing] || view != [NSView focusView]) { - NSRect bounds = [view bounds]; - NSRect dirtyNS = bounds; - if ([NSEvent pressedMouseButtons]) { - [view setNeedsDisplay:YES]; + if (dc.clipRgn) { + [view addTkDirtyRect:NSRectFromCGRect(clipBounds)]; } else { - CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, - .ty = dirtyNS.size.height}; - if (dc.clipRgn) { - CGRect dirtyCG = NSRectToCGRect(dirtyNS); - HIShapeGetBounds(dc.clipRgn, &dirtyCG); - dirtyNS = NSRectToCGRect(CGRectApplyAffineTransform(dirtyCG, t)); - } - [view setNeedsDisplayInRect:dirtyNS]; + [view addTkDirtyRect:[view bounds]]; } canDraw = false; goto end; + } else if (dc.clipRgn) { + + /* + * Drawing will also fail if we are being called from drawRect but + * the clipping rectangle set by drawRect does not contain the + * clipping region of our drawing context. See bug [2a61eca3a8]. + * If we can't draw all of the clipping region of the drawing + * context then we draw whatever we can, but we also add a dirty + * rectangle so the entire widget will get redrawn in the next + * cycle. + */ + + CGRect currentClip = CGContextGetClipBoundingBox( + [NSGraphicsContext currentContext].CGContext); + if (!NSContainsRect(currentClip, clipBounds)) { + [view addTkDirtyRect:clipBounds]; + } } dc.view = view; @@ -1694,6 +1702,7 @@ TkMacOSXSetupDrawingContext( CGContextSetTextDrawingMode(dc.context, kCGTextFill); CGContextConcatCTM(dc.context, t); if (dc.clipRgn) { + #ifdef TK_MAC_DEBUG_DRAWING CGContextSaveGState(dc.context); ChkErr(HIShapeReplacePathInCGContext, dc.clipRgn, dc.context); @@ -1701,13 +1710,14 @@ TkMacOSXSetupDrawingContext( CGContextEOFillPath(dc.context); CGContextRestoreGState(dc.context); #endif /* TK_MAC_DEBUG_DRAWING */ + CGRect r; if (!HIShapeIsRectangular(dc.clipRgn) || !CGRectContainsRect( - *HIShapeGetBounds(dc.clipRgn, &r), - CGRectApplyAffineTransform(clipBounds, t))) { - ChkErr(HIShapeReplacePathInCGContext, dc.clipRgn, dc.context); - CGContextEOClip(dc.context); + *HIShapeGetBounds(dc.clipRgn, &r), + CGRectApplyAffineTransform(clipBounds, t))) { + ChkErr(HIShapeReplacePathInCGContext, dc.clipRgn, dc.context); + CGContextEOClip(dc.context); } } if (gc) { diff --git a/macosx/tkMacOSXEmbed.c b/macosx/tkMacOSXEmbed.c index 6c4ac1f..e58cdd0 100644 --- a/macosx/tkMacOSXEmbed.c +++ b/macosx/tkMacOSXEmbed.c @@ -1049,9 +1049,7 @@ EmbedGeometryRequest( */ Tk_GeometryRequest((Tk_Window) winPtr, width, height); - while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) { - /* Empty loop body. */ - } + while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_TIMER_EVENTS|TCL_DONT_WAIT)) {} if ((winPtr->changes.width != width) || (winPtr->changes.height != height)) { EmbedSendConfigure(containerPtr); diff --git a/macosx/tkMacOSXEvent.c b/macosx/tkMacOSXEvent.c index b9c9b6a..31a7663 100644 --- a/macosx/tkMacOSXEvent.c +++ b/macosx/tkMacOSXEvent.c @@ -104,43 +104,8 @@ enum { return processedEvent; } @end - #pragma mark - - -/* - *---------------------------------------------------------------------- - * - * TkMacOSXFlushWindows -- - * - * This routine is a stub called by XSync, which is called during the Tk - * update command. The language specification does not require that the - * update command be synchronous but many of the tests implicitly assume - * that it is. It is definitely asynchronous on macOS since many idle - * tasks are run inside of the drawRect method of a window's contentView, - * which will not be called until after this function returns. - * - * Results: - * None. - * - * Side effects: Processes all pending idle events then calls the display - * method of each visible window. - * - *---------------------------------------------------------------------- - */ - -MODULE_SCOPE void -TkMacOSXFlushWindows(void) -{ - if (Tk_GetNumMainWindows() == 0) { - return; - } - while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)){} - for (NSWindow *w in [NSApp orderedWindows]) { - [w display]; - } -} - /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXEvent.h b/macosx/tkMacOSXEvent.h index 46d1585..850e9f6 100644 --- a/macosx/tkMacOSXEvent.h +++ b/macosx/tkMacOSXEvent.h @@ -18,6 +18,8 @@ #include "tkMacOSXInt.h" #endif -MODULE_SCOPE void TkMacOSXFlushWindows(void); +/* + * Currently nothing needs to be declared here. + */ #endif diff --git a/macosx/tkMacOSXInt.h b/macosx/tkMacOSXInt.h index ee2ab75..89d76ce 100644 --- a/macosx/tkMacOSXInt.h +++ b/macosx/tkMacOSXInt.h @@ -200,7 +200,7 @@ MODULE_SCOPE void TkpClipDrawableToRect(Display *display, Drawable d, int x, MODULE_SCOPE void TkpRetainRegion(TkRegion r); MODULE_SCOPE void TkpReleaseRegion(TkRegion r); MODULE_SCOPE void TkpShiftButton(NSButton *button, NSPoint delta); -MODULE_SCOPE Bool TkTestLogDisplay(void); +MODULE_SCOPE Bool TkTestLogDisplay(Drawable drawable); MODULE_SCOPE Bool TkMacOSXInDarkMode(Tk_Window tkwin); /* diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index b0d91ad..572e318 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -255,12 +255,11 @@ static NSUInteger textInputModifiers; */ if (type == NSKeyDown && [theEvent isARepeat]) { + xEvent.xany.type = KeyRelease; Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); xEvent.xany.type = KeyPress; } - if (xEvent.xany.type == KeyPress) { - } Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); return theEvent; } @@ -269,14 +268,6 @@ static NSUInteger textInputModifiers; @implementation TKContentView --(id)init { - self = [super init]; - if (self) { - _needsRedisplay = NO; - } - return self; -} - /* * Implementation of the NSTextInputClient protocol. */ @@ -552,7 +543,6 @@ static NSUInteger textInputModifiers; } /* End of NSTextInputClient implementation. */ -@synthesize needsRedisplay = _needsRedisplay; @end 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); + } } } diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 1266c8b..a77282c 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -307,6 +307,9 @@ MODULE_SCOPE int TkMacOSXRegisterServiceWidgetObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); MODULE_SCOPE unsigned TkMacOSXAddVirtual(unsigned int keycode); +MODULE_SCOPE void TkMacOSXWinNSBounds(TkWindow *winPtr, NSView *view, + NSRect *bounds); +MODULE_SCOPE void TkMacOSXDrawAllViews(ClientData clientData); #pragma mark Private Objective-C Classes @@ -342,11 +345,13 @@ VISIBILITY_HIDDEN int _poolLock; int _macOSVersion; /* 10000 * major + 100*minor */ Bool _isDrawing; + Bool _needsToDraw; #endif } @property int poolLock; @property int macOSVersion; @property Bool isDrawing; +@property Bool needsToDraw; @end @interface TKApplication(TKInit) @@ -417,9 +422,11 @@ VISIBILITY_HIDDEN { @private NSString *privateWorkingText; - Bool _needsRedisplay; + Bool _tkNeedsDisplay; + NSRect _tkDirtyRect; } -@property Bool needsRedisplay; +@property Bool tkNeedsDisplay; +@property NSRect tkDirtyRect; @end @interface TKContentView(TKKeyEvent) @@ -428,7 +435,9 @@ VISIBILITY_HIDDEN @end @interface TKContentView(TKWindowEvent) -- (void) generateExposeEvents: (HIShapeRef) shape; +- (void) addTkDirtyRect: (NSRect) rect; +- (void) clearTkDirtyRect; +- (void) generateExposeEvents: (NSRect) rect; - (void) tkToolbarButton: (id) sender; @end diff --git a/macosx/tkMacOSXSubwindows.c b/macosx/tkMacOSXSubwindows.c index b085ba4..8bae8fd 100644 --- a/macosx/tkMacOSXSubwindows.c +++ b/macosx/tkMacOSXSubwindows.c @@ -136,6 +136,9 @@ XMapWindow( Display *display, /* Display. */ Window window) /* Window. */ { + if (!window) { + return BadWindow; + } MacDrawable *macWin = (MacDrawable *) window; TkWindow *winPtr = macWin->winPtr; NSWindow *win = TkMacOSXDrawableWindow(window); @@ -157,6 +160,7 @@ XMapWindow( winPtr->flags |= TK_MAPPED; if (Tk_IsTopLevel(winPtr)) { if (!Tk_IsEmbedded(winPtr)) { + TKContentView *view = [win contentView]; /* * We want to activate Tk when a toplevel is mapped but we must not @@ -169,7 +173,7 @@ XMapWindow( TkMacOSXApplyWindowAttributes(winPtr, win); [win setExcludedFromWindowsMenu:NO]; [NSApp activateIgnoringOtherApps:NO]; - [[win contentView] setNeedsDisplay:YES]; + [view addTkDirtyRect: [view bounds]]; if ([win canBecomeKeyWindow]) { [win makeKeyAndOrderFront:NSApp]; } else { @@ -212,10 +216,9 @@ XMapWindow( TkMacOSXInvalClipRgns((Tk_Window) winPtr->parentPtr); } - if ([NSApp isDrawing]) { - [[win contentView] setNeedsRedisplay:YES]; - } else { - [[win contentView] setNeedsDisplay:YES]; + TKContentView *view = [win contentView]; + if (view != [NSView focusView]) { + [view addTkDirtyRect:[view bounds]]; } /* @@ -329,10 +332,9 @@ XUnmapWindow( TkMacOSXUpdateClipRgn(parentPtr); } winPtr->flags &= ~TK_MAPPED; - if ([NSApp isDrawing]) { - [[win contentView] setNeedsRedisplay:YES]; - } else { - [[win contentView] setNeedsDisplay:YES]; + TKContentView *view = [win contentView]; + if (view != [NSView focusView]) { + [view addTkDirtyRect:[view bounds]]; } return Success; } @@ -986,7 +988,8 @@ InvalViewRect( void *ref) { static CGAffineTransform t; - NSView *view = ref; + TKContentView *view = ref; + NSRect dirtyRect; if (!view) { return paramErr; @@ -997,8 +1000,8 @@ InvalViewRect( NSHeight([view bounds])); break; case kHIShapeEnumerateRect: - [view setNeedsDisplayInRect:NSRectFromCGRect( - CGRectApplyAffineTransform(*rect, t))]; + dirtyRect = NSRectFromCGRect(CGRectApplyAffineTransform(*rect, t)); + [view addTkDirtyRect:dirtyRect]; break; } return noErr; @@ -1284,7 +1287,7 @@ TkMacOSXInvalClipRgns( * None. * * Side effects: - * None. + * Fills in a Rect. * *---------------------------------------------------------------------- */ @@ -1308,15 +1311,14 @@ TkMacOSXWinBounds( * TkMacOSXWinCGBounds -- * * Given a Tk window this function determines the window's bounds in - * relation to the Macintosh window's coordinate system. This is also the - * same coordinate system as the Tk toplevel window in which this window - * is contained. + * the coordinate system of the Tk toplevel window in which this window + * is contained. This fills in a CGRect struct. * * Results: * None. * * Side effects: - * None. + * Fill in a CGRect. * *---------------------------------------------------------------------- */ @@ -1331,6 +1333,39 @@ TkMacOSXWinCGBounds( bounds->size.width = winPtr->changes.width; bounds->size.height = winPtr->changes.height; } +/* + *---------------------------------------------------------------------- + * + * TkMacOSXWinNSBounds -- + * + * Given a Tk window this function determines the window's bounds in + * the coordinate system of the TKContentView in which this Tk window + * is contained, which has the origin at the lower left corner. This + * fills in an NSRect struct and requires the TKContentView as a + * parameter + * + * Results: + * None. + * + * Side effects: + * Fills in an NSRect. + * + *---------------------------------------------------------------------- + */ + +void +TkMacOSXWinNSBounds( + TkWindow *winPtr, + NSView *view, + NSRect *bounds) +{ + bounds->size.width = winPtr->changes.width; + bounds->size.height = winPtr->changes.height; + bounds->origin.x = winPtr->privatePtr->xOff; + bounds->origin.y = ([view bounds].size.height - + bounds->size.height - + winPtr->privatePtr->yOff); +} /* *---------------------------------------------------------------------- diff --git a/macosx/tkMacOSXTest.c b/macosx/tkMacOSXTest.c index a5d1d83..39f50e6 100644 --- a/macosx/tkMacOSXTest.c +++ b/macosx/tkMacOSXTest.c @@ -14,6 +14,8 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXConstants.h" +#include "tkMacOSXWm.h" + /* * Forward declarations of procedures defined later in this file: @@ -108,7 +110,8 @@ DebuggerObjCmd( * first call will usually not occur inside of drawRect. * * Results: - * On OSX 10.14 and later, returns true if and only if called from + * On OSX 10.14 and later, returns true if and only if the NSView of the + * drawable is the current focusView, which can only be the case when * within [NSView drawRect]. On earlier systems returns false if * and only if called from with [NSView drawRect]. * @@ -118,9 +121,28 @@ DebuggerObjCmd( *---------------------------------------------------------------------- */ MODULE_SCOPE Bool -TkTestLogDisplay(void) { - if ([NSApp macOSVersion] >= 101400) { - return [NSApp isDrawing]; +TkTestLogDisplay( + Drawable drawable) +{ + MacDrawable *macWin = (MacDrawable *) drawable; + NSWindow *win = nil; + if (macWin->toplevel && macWin->toplevel->winPtr && + macWin->toplevel->winPtr->wmInfoPtr && + macWin->toplevel->winPtr->wmInfoPtr->window) { + win = macWin->toplevel->winPtr->wmInfoPtr->window; + } else if (macWin->winPtr && macWin->winPtr->wmInfoPtr && + macWin->winPtr->wmInfoPtr->window) { + win = macWin->winPtr->wmInfoPtr->window; + }/* + else if (macWin->toplevel && (macWin->toplevel->flags & TK_EMBEDDED)) { + TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr); + if (contWinPtr) { + win = TkMacOSXDrawableWindow((Drawable) contWinPtr->privatePtr); + } + }*/ + if (win && [NSApp macOSVersion] >= 101400) { + TKContentView *view = [win contentView]; + return (view == [NSView focusView]); } else { return ![NSApp isDrawing]; } diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index 17f8585..5f52ea7 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -30,17 +30,17 @@ * Declaration of functions used only in this file */ -static int GenerateUpdates(HIShapeRef updateRgn, - CGRect *updateBounds, TkWindow *winPtr); +static int GenerateUpdates( + CGRect *updateBounds, TkWindow *winPtr); static int GenerateActivateEvents(TkWindow *winPtr, int activeFlag); -static void DoWindowActivate(ClientData clientData); #pragma mark TKApplication(TKWindowEvent) -#ifdef TK_MAC_DEBUG_NOTIFICATIONS -extern NSString *NSWindowWillOrderOnScreenNotification; extern NSString *NSWindowDidOrderOnScreenNotification; +extern NSString *NSWindowWillOrderOnScreenNotification; + +#ifdef TK_MAC_DEBUG_NOTIFICATIONS extern NSString *NSWindowDidOrderOffScreenNotification; #endif @@ -90,14 +90,11 @@ extern NSString *NSWindowDidOrderOffScreenNotification; height = bounds.size.height - wmPtr->yInParent; flags |= TK_SIZE_CHANGED; } - if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) { - /* - * Propagate geometry changes immediately. - */ - - flags |= TK_MACOSX_HANDLE_EVENT_IMMEDIATELY; - } + /* + * Propagate geometry changes immediately. + */ + flags |= TK_MACOSX_HANDLE_EVENT_IMMEDIATELY; TkGenWMConfigureEvent((Tk_Window) winPtr, x, y, width, height, flags); } @@ -115,24 +112,20 @@ extern NSString *NSWindowDidOrderOffScreenNotification; winPtr->wmInfoPtr->hints.initial_state = TkMacOSXIsWindowZoomed(winPtr) ? ZoomState : NormalState; Tk_MapWindow((Tk_Window) winPtr); - if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) { - /* - * Process all Tk events generated by Tk_MapWindow(). - */ + /* + * Process all Tk events generated by Tk_MapWindow(). + */ - while (Tcl_ServiceEvent(0)) {} - while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {} + while (Tcl_ServiceEvent(0)) {} + while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {} - /* - * NSWindowDidDeminiaturizeNotification is received after - * NSWindowDidBecomeKeyNotification, so activate manually - */ + /* + * NSWindowDidDeminiaturizeNotification is received after + * NSWindowDidBecomeKeyNotification, so activate manually + */ - GenerateActivateEvents(winPtr, 1); - } else { - Tcl_DoWhenIdle(DoWindowActivate, winPtr); - } + GenerateActivateEvents(winPtr, 1); } } @@ -208,35 +201,41 @@ extern NSString *NSWindowDidOrderOffScreenNotification; return (winPtr ? NO : YES); } -#ifdef TK_MAC_DEBUG_NOTIFICATIONS - -- (void) windowDragStart: (NSNotification *) notification -{ - TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); -} - -- (void) windowLiveResize: (NSNotification *) notification +- (void) windowBecameVisible: (NSNotification *) notification { - TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); - //BOOL start = [[notification name] isEqualToString:NSWindowWillStartLiveResizeNotification]; + NSWindow *window = [notification object]; + TkWindow *winPtr = TkMacOSXGetTkWindow(window); + if (winPtr) { + TKContentView *view = [window contentView]; + [view addTkDirtyRect:[view bounds]]; + Tcl_CancelIdleCall(TkMacOSXDrawAllViews, NULL); + Tcl_DoWhenIdle(TkMacOSXDrawAllViews, NULL); + } } - (void) windowMapped: (NSNotification *) notification { - TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); NSWindow *w = [notification object]; TkWindow *winPtr = TkMacOSXGetTkWindow(w); if (winPtr) { - //Tk_MapWindow((Tk_Window) winPtr); + while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {} } } -- (void) windowBecameVisible: (NSNotification *) notification +#ifdef TK_MAC_DEBUG_NOTIFICATIONS + +- (void) windowDragStart: (NSNotification *) notification { TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); } +- (void) windowLiveResize: (NSNotification *) notification +{ + TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); + //BOOL start = [[notification name] isEqualToString:NSWindowWillStartLiveResizeNotification]; +} + - (void) windowUnmapped: (NSNotification *) notification { TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); @@ -263,6 +262,8 @@ extern NSString *NSWindowDidOrderOffScreenNotification; observe(NSWindowDidResizeNotification, windowBoundsChanged:); observe(NSWindowDidDeminiaturizeNotification, windowExpanded:); observe(NSWindowDidMiniaturizeNotification, windowCollapsed:); + observe(NSWindowWillOrderOnScreenNotification, windowMapped:); + observe(NSWindowDidOrderOnScreenNotification, windowBecameVisible:); #if !(MAC_OS_X_VERSION_MAX_ALLOWED < 1070) observe(NSWindowDidEnterFullScreenNotification, windowEnteredFullScreen:); @@ -273,8 +274,6 @@ extern NSString *NSWindowDidOrderOffScreenNotification; observe(NSWindowWillMoveNotification, windowDragStart:); observe(NSWindowWillStartLiveResizeNotification, windowLiveResize:); observe(NSWindowDidEndLiveResizeNotification, windowLiveResize:); - observe(NSWindowWillOrderOnScreenNotification, windowMapped:); - observe(NSWindowDidOrderOnScreenNotification, windowBecameVisible:); observe(NSWindowDidOrderOffScreenNotification, windowUnmapped:); #endif #undef observe @@ -399,24 +398,36 @@ extern NSString *NSWindowDidOrderOffScreenNotification; * tkwin NULL or pointing to a widget in the current focusView. * * Side effects: - * The tkwin parameter may be recorded to handle redrawing the widget - * later. + * Currently none. One day the tkwin parameter may be recorded to + * handle redrawing the widget later. * *---------------------------------------------------------------------- */ int TkpWillDrawWidget(Tk_Window tkwin) { - if (![NSApp isDrawing]) { - return 0; - } + int result; if (tkwin) { TkWindow *winPtr = (TkWindow *)tkwin; - NSView *view = TkMacOSXDrawableView(winPtr->privatePtr); - return (view == [NSView focusView]); + TKContentView *view = (TKContentView *) TkMacOSXDrawableView( + winPtr->privatePtr); + result = ([NSApp isDrawing] && view == [NSView focusView]); +#if 0 + printf("TkpWillDrawWidget: %s %d %d \n", Tk_PathName(tkwin), + [NSApp isDrawing], (view == [NSView focusView])); + if (!result) { + NSRect dirtyRect; + TkMacOSXWinNSBounds(winPtr, view, &dirtyRect); + printf("TkpAppCanDraw: dirtyRect for %s is %s\n", + Tk_PathName(tkwin), + NSStringFromRect(dirtyRect).UTF8String); + [view addTkDirtyRect:dirtyRect]; + } +#endif } else { - return 1; + result = [NSApp isDrawing]; } + return result; } /* @@ -424,7 +435,7 @@ TkpWillDrawWidget(Tk_Window tkwin) { * * GenerateUpdates -- * - * Given a Macintosh update region and a Tk window this function generates + * Given an update rectangle and a Tk window, this function generates * an X Expose event for the window if it meets the update region. The * function will then recursively have each damaged window generate Expose * events for its child windows. @@ -440,39 +451,23 @@ TkpWillDrawWidget(Tk_Window tkwin) { static int GenerateUpdates( - HIShapeRef updateRgn, CGRect *updateBounds, TkWindow *winPtr) { TkWindow *childPtr; XEvent event; CGRect bounds, damageBounds; - HIShapeRef boundsRgn, damageRgn; TkMacOSXWinCGBounds(winPtr, &bounds); if (!CGRectIntersectsRect(bounds, *updateBounds)) { return 0; } - if (!HIShapeIntersectsRect(updateRgn, &bounds)) { - return 0; - } /* * Compute the bounding box of the area that the damage occurred in. */ - boundsRgn = HIShapeCreateWithRect(&bounds); - damageRgn = HIShapeCreateIntersection(updateRgn, boundsRgn); - if (HIShapeIsEmpty(damageRgn)) { - CFRelease(damageRgn); - CFRelease(boundsRgn); - return 0; - } - HIShapeGetBounds(damageRgn, &damageBounds); - - CFRelease(damageRgn); - CFRelease(boundsRgn); - + damageBounds = CGRectIntersection(bounds, *updateBounds); event.xany.serial = LastKnownRequestProcessed(Tk_Display(winPtr)); event.xany.send_event = false; event.xany.window = Tk_WindowId(winPtr); @@ -486,7 +481,7 @@ GenerateUpdates( Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); #ifdef TK_MAC_DEBUG_DRAWING - TKLog(@"Expose %p {{%d, %d}, {%d, %d}}", event.xany.window, event.xexpose.x, + TKLog(@"Exposed %p {{%d, %d}, {%d, %d}}", event.xany.window, event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height); #endif @@ -499,7 +494,7 @@ GenerateUpdates( if (!Tk_IsMapped(childPtr) || Tk_IsTopLevel(childPtr)) { continue; } - GenerateUpdates(updateRgn, updateBounds, childPtr); + GenerateUpdates(updateBounds, childPtr); } /* @@ -509,7 +504,7 @@ GenerateUpdates( if (Tk_IsContainer(winPtr)) { childPtr = TkpGetOtherWindow(winPtr); if (childPtr != NULL && Tk_IsMapped(childPtr)) { - GenerateUpdates(updateRgn, updateBounds, childPtr); + GenerateUpdates(updateBounds, childPtr); } /* @@ -552,29 +547,6 @@ GenerateActivateEvents( /* *---------------------------------------------------------------------- * - * DoWindowActivate -- - * - * Idle handler that calls GenerateActivateEvents(). - * - * Results: - * None. - * - * Side effects: - * Additional events may be placed on the Tk event queue. - * - *---------------------------------------------------------------------- - */ - -void -DoWindowActivate( - ClientData clientData) -{ - GenerateActivateEvents(clientData, 1); -} - -/* - *---------------------------------------------------------------------- - * * TkMacOSXGenerateFocusEvent -- * * Given a Macintosh window activate event this function generates all @@ -922,42 +894,31 @@ ConfigureRestrictProc( return (eventPtr->type==ConfigureNotify ? TK_PROCESS_EVENT : TK_DEFER_EVENT); } -/* - * If a window gets mapped inside the drawRect method, this will be run as an - * idle task, after drawRect returns, to clean up the mess. - */ +@implementation TKContentView(TKWindowEvent) -static void -RedisplayView( - ClientData clientdata) +- (void) addTkDirtyRect: (NSRect) rect { - NSView *view = (NSView *) clientdata; - - /* - * Make sure that we are not trying to display a view that no longer - * exists. Must call [NSApp windows] because [NSApp orderedWindows] excludes - * floating/utility windows and other window panels. - */ - - for (NSWindow *w in [NSApp windows]) { - if ([w contentView] == view) { - [view setNeedsDisplay:YES]; - break; - } - } + _tkNeedsDisplay = YES; + _tkDirtyRect = NSUnionRect(_tkDirtyRect, rect); + [NSApp setNeedsToDraw:YES]; } -@implementation TKContentView(TKWindowEvent) +- (void) clearTkDirtyRect +{ + _tkNeedsDisplay = NO; + _tkDirtyRect = NSZeroRect; + [NSApp setNeedsToDraw:NO]; +} - (void) drawRect: (NSRect) rect { - const NSRect *rectsBeingDrawn; - NSInteger rectsBeingDrawnCount; #ifdef TK_MAC_DEBUG_DRAWING TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); - if (winPtr) fprintf(stderr, "drawRect: drawing %s\n", - Tk_PathName(winPtr)); + if (winPtr) { + fprintf(stderr, "drawRect: drawing %s in %s\n", + Tk_PathName(winPtr), NSStringFromRect(rect).UTF8String); + } #endif /* @@ -973,30 +934,9 @@ RedisplayView( } [NSApp setIsDrawing: YES]; - - [self getRectsBeingDrawn:&rectsBeingDrawn count:&rectsBeingDrawnCount]; - CGFloat height = [self bounds].size.height; - HIMutableShapeRef drawShape = HIShapeCreateMutable(); - - while (rectsBeingDrawnCount--) { - CGRect r = NSRectToCGRect(*rectsBeingDrawn++); - -#ifdef TK_MAC_DEBUG_DRAWING - fprintf(stderr, "drawRect: %dx%d@(%d,%d)\n", (int)r.size.width, - (int)r.size.height, (int)r.origin.x, (int)r.origin.y); -#endif - - r.origin.y = height - (r.origin.y + r.size.height); - HIShapeUnionWithRect(drawShape, &r); - } - [self generateExposeEvents:(HIShapeRef)drawShape]; - CFRelease(drawShape); - [NSApp setIsDrawing: NO]; - - if ([self needsRedisplay]) { - [self setNeedsRedisplay:NO]; - Tcl_DoWhenIdle(RedisplayView, self); - } + [self clearTkDirtyRect]; + [self generateExposeEvents:rect]; + [NSApp setIsDrawing:NO]; #ifdef TK_MAC_DEBUG_DRAWING fprintf(stderr, "drawRect: done.\n"); @@ -1056,9 +996,7 @@ RedisplayView( * Generate and process expose events to redraw the window. */ - HIRect bounds = NSRectToCGRect([self bounds]); - HIShapeRef shape = HIShapeCreateWithRect(&bounds); - [self generateExposeEvents: shape]; + [self generateExposeEvents: [self bounds]]; [w displayIfNeeded]; /* @@ -1076,11 +1014,11 @@ RedisplayView( * pending idle events are processed so the drawing will actually take place. */ -- (void) generateExposeEvents: (HIShapeRef) shape +- (void) generateExposeEvents: (NSRect) rect { unsigned long serial; - CGRect updateBounds; int updatesNeeded; + CGRect updateBounds; TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); ClientData oldArg; Tk_RestrictProc *oldProc; @@ -1089,26 +1027,25 @@ RedisplayView( } /* - * Generate Tk Expose events. - */ - - HIShapeGetBounds(shape, &updateBounds); - - /* - * All of these events will share the same serial number. + * Generate Tk Expose events. All of these events will share the same + * serial number. */ - serial = LastKnownRequestProcessed(Tk_Display(winPtr)); - updatesNeeded = GenerateUpdates(shape, &updateBounds, winPtr); - + updateBounds = NSRectToCGRect(rect); + updateBounds.origin.y = ([self bounds].size.height - updateBounds.origin.y + - updateBounds.size.height); + updatesNeeded = GenerateUpdates(&updateBounds, winPtr); if (updatesNeeded) { + serial = LastKnownRequestProcessed(Tk_Display(winPtr)); + /* - * First process all of the Expose events. + * Use the ExposeRestrictProc to process only the expose events. This + * will create idle drawing tasks, which we handle before we return. */ oldProc = Tk_RestrictEvents(ExposeRestrictProc, UINT2PTR(serial), &oldArg); - while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {}; + while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS|TCL_DONT_WAIT)) {}; Tk_RestrictEvents(oldProc, oldArg, &oldArg); /* @@ -1120,9 +1057,10 @@ RedisplayView( * effect.) * * Fortunately, Tk schedules all drawing to be done while Tcl is idle. - * So we can do the drawing by processing all of the idle events that - * were created when the expose events were processed. + * So to run any display procs which were scheduled by the expose + * events we process all idle events before returning. */ + while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {} } } @@ -1202,10 +1140,15 @@ RedisplayView( - (BOOL) isOpaque { NSWindow *w = [self window]; - return (w && (([w styleMask] & NSTexturedBackgroundWindowMask) || - ![w isOpaque]) ? NO : YES); + return (w && (([w styleMask] & NSTexturedBackgroundWindowMask) || + ![w isOpaque]) ? NO : YES); } +/* + * On Catalina this is never called and drawRect clips to the rect that + * is passed to it by AppKit. + */ + - (BOOL) wantsDefaultClipping { return NO; diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 8e52278..dca8686 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -3805,10 +3805,6 @@ WmWithdrawCmd( TkpWmSetState(winPtr, WithdrawnState); - NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); - [win orderOut:NSApp]; - [win setExcludedFromWindowsMenu:YES]; - /* * If this window has a transient, the transient must also be withdrawn. */ @@ -6201,13 +6197,14 @@ TkpRedrawWidget(Tk_Window tkwin) { } w = TkMacOSXDrawableWindow(winPtr->window); if (w) { - NSView *view = [w contentView]; + TKContentView *view = [w contentView]; TkMacOSXWinBounds(winPtr, &tkBounds); bounds = NSMakeRect(tkBounds.left, [view bounds].size.height - tkBounds.bottom, tkBounds.right - tkBounds.left, tkBounds.bottom - tkBounds.top); - [view setNeedsDisplayInRect:bounds]; + [view setTkNeedsDisplay:YES]; + [view setTkDirtyRect:bounds]; } } @@ -6412,6 +6409,7 @@ TkpWmSetState( if (state == WithdrawnState) { Tk_UnmapWindow((Tk_Window) winPtr); } else if (state == IconicState) { + /* * The window always gets unmapped. If we can show the icon version of * the window we also collapse it. @@ -6424,9 +6422,13 @@ TkpWmSetState( Tk_UnmapWindow((Tk_Window) winPtr); } else if (state == NormalState || state == ZoomState) { Tk_MapWindow((Tk_Window) winPtr); - if (macWin && ([macWin styleMask] & NSMiniaturizableWindowMask) && - [macWin isMiniaturized]) { - [macWin deminiaturize:NSApp]; + if (macWin && ([macWin styleMask] & NSMiniaturizableWindowMask)) { + if ([macWin isMiniaturized]) { + [macWin deminiaturize:NSApp]; + } + else { + [macWin orderFront:nil]; + } } TkMacOSXZoomToplevel(macWin, state == NormalState ? inZoomIn : inZoomOut); diff --git a/macosx/tkMacOSXXStubs.c b/macosx/tkMacOSXXStubs.c index 6aeec36..107f712 100644 --- a/macosx/tkMacOSXXStubs.c +++ b/macosx/tkMacOSXXStubs.c @@ -752,7 +752,18 @@ XSync( Display *display, Bool discard) { - TkMacOSXFlushWindows(); + /* + * The main use of XSync is by the update command, which alternates + * between running an event loop to process all events without waiting and + * calling XSync on all displays until no events are left. There is + * nothing for the mac to do with respect to syncing its one display but + * it can (and, during regression testing, frequently does) happen that + * timer events fire during the event loop. Processing those here seems + * to make the update command work in a way that is more consistent with + * its behavior on other platforms. + */ + + while (Tcl_DoOneEvent(TCL_TIMER_EVENTS|TCL_DONT_WAIT)){} display->request++; return 0; } @@ -945,7 +956,7 @@ XSynchronize( Display *display, Bool onoff) { - display->request++; + display->request++; return NULL; } diff --git a/tests/canvImg.test b/tests/canvImg.test index b60e384..1abea78 100644 --- a/tests/canvImg.test +++ b/tests/canvImg.test @@ -727,12 +727,6 @@ test canvImg-9.1 {DisplayImage procedure} -constraints testImageType -setup { image delete foo } -result {75 150 105 165} -if {[tk windowingsystem] == "aqua" && $tcl_platform(osVersion) > 18} { - # Aqua >= 10.14 will redraw the entire image. - set result_10_1 {{foo display 0 0 30 15}} -} else { - set result_10_1 {{foo display 2 4 6 8}} -} test canvImg-10.1 {TranslateImage procedure} -constraints testImageType -setup { .c delete all update @@ -750,7 +744,7 @@ test canvImg-10.1 {TranslateImage procedure} -constraints testImageType -setup { } -cleanup { .c delete all image delete foo -} -result $result_10_1 +} -result {{foo display 2 4 6 8}} test canvImg-11.1 {TranslateImage procedure} -constraints testImageType -setup { .c delete all diff --git a/tests/image.test b/tests/image.test index da65a66..cac304f 100644 --- a/tests/image.test +++ b/tests/image.test @@ -357,12 +357,6 @@ test image-8.1 {Tk_ImageCmd procedure, "inuse" option} -constraints { catch {destroy .b} } -result [list 0 1] -if {[tk windowingsystem] == "aqua" && $tcl_platform(osVersion) > 18} { - # Aqua >= 10.14 will redraw the entire image in drawRect. - set result_9_1 {{foo display 0 0 30 15}} -} else { - set result_9_1 {{foo display 5 6 7 8}} -} test image-9.1 {Tk_ImageChanged procedure} -constraints testImageType -setup { .c delete all imageCleanup @@ -385,13 +379,8 @@ test image-9.1 {Tk_ImageChanged procedure} -constraints testImageType -setup { } -cleanup { .c delete all imageCleanup -} -result $result_9_1 -if {[tk windowingsystem] == "aqua" && $tcl_platform(osVersion) > 18} { - # Aqua >= 10.14 will redraw the entire image. - set result_9_2 {{foo display 0 0 30 15} {foo display 0 0 30 15}} -} else { - set result_9_2 {{foo display 5 6 25 9} {foo display 0 0 12 14}} -} +} -result {{foo display 5 6 7 8}} + test image-9.2 {Tk_ImageChanged procedure} -constraints testImageType -setup { .c delete all imageCleanup @@ -411,7 +400,7 @@ test image-9.2 {Tk_ImageChanged procedure} -constraints testImageType -setup { } -cleanup { .c delete all imageCleanup -} -result $result_9_2 +} -result {{foo display 5 6 25 9} {foo display 0 0 12 14}} test image-10.1 {Tk_GetImage procedure} -setup { imageCleanup diff --git a/tests/listbox.test b/tests/listbox.test index 14c5c97..01cc397 100644 --- a/tests/listbox.test +++ b/tests/listbox.test @@ -2666,7 +2666,7 @@ test listbox-21.9 {ListboxListVarProc, test hscrollbar after listvar mod} -setup listbox .l -font $fixed -width 10 -xscrollcommand "record x" -listvar x set log {} pack .l - set timeout [after 500 {set log timeout}] + set timeout [after 500 {lappend log timeout1}] vwait log lappend x "0000000000" update @@ -2684,7 +2684,7 @@ test listbox-21.10 {ListboxListVarProc, test hscrollbar after listvar mod} -setu listbox .l -font $fixed -width 10 -xscrollcommand "record x" -listvar x set log {} pack .l - set timeout [after 500 {set log timeout}] + set timeout [after 500 {lappend log timeout2}] vwait log lappend x "0000000000" update @@ -2695,7 +2695,7 @@ test listbox-21.10 {ListboxListVarProc, test hscrollbar after listvar mod} -setu set log } -cleanup { destroy .l - after cancel timeout + after cancel $timeout } -result [list {x 0 1} {x 0 1} {x 0 0.5} {x 0 1}] test listbox-21.11 {ListboxListVarProc, bad list} -setup { destroy .l @@ -2764,7 +2764,7 @@ test listbox-21.15 {ListboxListVarProc, update vertical scrollbar} -setup { update set log {} pack .l - set timeout [after 500 {set log timeout}] + set timeout [after 500 {lappend log timeout3}] vwait log update lappend x a b c d e f @@ -2801,19 +2801,19 @@ test listbox-22.1 {UpdateHScrollbar} -setup { destroy .l } -body { listbox .l -font $fixed -width 10 -xscrollcommand "record x" - set log {} pack .l - set timeout [after 500 {set log timeout}] - vwait log + update idletasks + set log {} + set timeout [after 500 {lappend log timeout4}] .l insert end "0000000000" - update + vwait log .l insert end "00000000000000000000" vwait log set log } -cleanup { destroy .l after cancel $timeout -} -result [list {x 0 1} {x 0 1} {x 0 0.5}] +} -result [list {x 0 1} {x 0 0.5}] # ConfigureListboxItem @@ -3221,8 +3221,3 @@ option clear # cleanup cleanupTests return - - - - - diff --git a/tests/textDisp.test b/tests/textDisp.test index b4891e4..796fd92 100644 --- a/tests/textDisp.test +++ b/tests/textDisp.test @@ -4196,12 +4196,14 @@ test textDisp-33.2 {one line longer than fits in the widget} { destroy .tt pack [text .tt -wrap char] .tt debug 1 + update idletasks set tk_textHeightCalc "" + set timer [after 700 lappend tk_textHeightCalc "Timed out"] .tt insert 1.0 [string repeat "more wrap + " 1] - after 100 ; update idletasks - # Nothing should have been recalculated. + vwait tk_textHeightCalc + after cancel timer set tk_textHeightCalc -} {} +} {1.0} test textDisp-33.3 {one line longer than fits in the widget} { destroy .tt pack [text .tt -wrap char] diff --git a/tests/textWind.test b/tests/textWind.test index 938357b..f2daaca 100644 --- a/tests/textWind.test +++ b/tests/textWind.test @@ -751,15 +751,17 @@ test textWind-10.5 {EmbWinLayoutProc procedure, error in creating window} -setup destroy .t.f proc bgerror args { global msg - lappend msg $args + if {$msg == ""} { + lappend msg $args + } } } -body { .t insert 1.0 "Some sample text" + set msg {} .t window create 1.5 -create { frame .t.f frame .t.f.f -width 10 -height 20 -bg $color } - set msg {} update idletasks lappend msg [winfo exists .t.f.f] } -cleanup { diff --git a/tests/wm.test b/tests/wm.test index 4d0d73b..9e6d8ce 100644 --- a/tests/wm.test +++ b/tests/wm.test @@ -2310,6 +2310,11 @@ test wm-forget-1.4 "pack into unmapped toplevel causes crash" -body { test wm-forget-2 {bug [e9112ef96e] - [wm forget] doesn't completely} -setup { catch {destroy .l .f.b .f} set res {} + if {[tk windowingsystem] == "aqua"} { + proc doUpdate {} {update idletasks} + } else { + proc doUpdate {} {update} + } } -body { label .l -text "Top Dot" frame .f @@ -2317,16 +2322,15 @@ test wm-forget-2 {bug [e9112ef96e] - [wm forget] doesn't completely} -setup { pack .l -side top pack .f.b pack .f -side bottom - update set res [winfo manager .f] pack forget .f - update + doUpdate lappend res [winfo manager .f] wm manage .f - update + doUpdate lappend res [winfo manager .f] wm forget .f - update + doUpdate lappend res [winfo manager .f] } -cleanup { destroy .l .f.b .f |