Index: generic/tkTextDisp.c ================================================================== --- generic/tkTextDisp.c +++ generic/tkTextDisp.c @@ -4272,11 +4272,10 @@ if (dlPtr->nextPtr == dlPtr2) { break; } dlPtr = dlPtr->nextPtr; } - /* * Scan through the lines following the copied ones to see if we are * going to overwrite them with the copy operation. If so, mark them * for redisplay. */ @@ -4296,13 +4295,11 @@ damageRgn = TkCreateRegion(); if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x, oldY, dInfoPtr->maxX-dInfoPtr->x, height, 0, y-oldY, damageRgn)) { -#ifndef MAC_OSX_TK TextInvalidateRegion(textPtr, damageRgn); -#endif } numCopies++; TkDestroyRegion(damageRgn); } @@ -4440,15 +4437,42 @@ #endif /* TK_NO_DOUBLE_BUFFERING */ return; } dlPtr->oldY = dlPtr->y; dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID); +#ifdef MAC_OSX_TK + } else if (dlPtr->chunkPtr != NULL) { + /* + * On macOS we need to redisplay all embedded windows which + * were moved by the call to TkScrollWindows above. This is + * not necessary on Unix or Windows because XScrollWindow will + * have included the bounding rectangles of all of these + * windows in the damage region. The macosx implementation of + * TkScrollWindow does not do this. It simply generates a + * damage region which is the scroll source rectangle minus + * the scroll destination rectangle. This is because there is + * no efficient process available for iterating through the + * subwindows which meet the scrolled area. (On Unix this is + * handled by GraphicsExpose events generated by XCopyArea and + * on Windows by ScrollWindowEx. On macOS the low level + * scrolling is accomplished by calling [view scrollRect:by:]. + * This method does not provide any damage information and, in + * any case, could not be aware of Tk windows which were not + * based on NSView objects. + * + * On the other hand, this loop is already iterating through + * all embedded windows which could possibly have been moved + * by the scrolling. So it is as efficient to redisplay them + * here as it would have been if they had been redisplayed by + * the call to TextInvalidateRegion above. + */ +#else } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0) || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) { - register TkTextDispChunk *chunkPtr; - - /* + /* + * On platforms other than the Mac: + * * It's the first or last DLine which are also overlapping the * top or bottom of the window, but we decided above it wasn't * necessary to display them (we were able to update them by * scrolling). This is fine, except that if the lines contain * any embedded windows, we must still call the display proc @@ -4458,10 +4482,12 @@ * doesn't! * * So, we loop through all the chunks, calling the display * proc of embedded windows only. */ +#endif + register TkTextDispChunk *chunkPtr; for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); chunkPtr = chunkPtr->nextPtr) { int x; if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) { @@ -4480,17 +4506,22 @@ * right). */ x = -chunkPtr->width; } + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(textPtr, &dlPtr->index, string); + LOG("tk_textEmbWinDisplay", string); + } TkTextEmbWinDisplayProc(textPtr, chunkPtr, x, dlPtr->spaceAbove, dlPtr->height-dlPtr->spaceAbove-dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, NULL, (Drawable) None, dlPtr->y + dlPtr->spaceAbove); } - } } #ifndef TK_NO_DOUBLE_BUFFERING Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); #endif /* TK_NO_DOUBLE_BUFFERING */ Index: library/demos/twind.tcl ================================================================== --- library/demos/twind.tcl +++ library/demos/twind.tcl @@ -81,16 +81,16 @@ $t window create end \ -create {button %W.peer -text "Make A Peer" -command "textMakePeer %W" \ -cursor top_left_arrow} -padx 3 $t insert end " widget. Notice how peer widgets can have different " $t insert end "font settings, and by default contain all the images " -$t insert end "of the 'parent', but many of the embedded windows, " -$t insert end "such as buttons will not be there. The easiest way " -$t insert end "to ensure they are in all peers is to use '-create' " -$t insert end "embedded window creation scripts " -$t insert end "(the plot above and the 'Make A Peer' button are " -$t insert end "designed to show up in all peers). A good use of " +$t insert end "of the 'parent', but that the embedded windows, " +$t insert end "such as buttons may not appear in the peer. To ensure " +$t insert end "that embedded windows appear in all peers you can set the " +$t insert end "'-create' option to a script or a string containing %W. " +$t insert end "(The plot above and the 'Make A Peer' button are " +$t insert end "designed to show up in all peers.) A good use of " $t insert end "peers is for " $t window create end \ -create {button %W.split -text "Split Windows" -command "textSplitWindow %W" \ -cursor top_left_arrow} -padx 3 $t insert end " \n\n" @@ -110,10 +110,11 @@ $t insert end "\"Short\", it changes to a longer string so that " $t insert end "you can see how the text widget automatically " $t insert end "changes the layout. Click on the button again " $t insert end "to restore the short string.\n" +$t insert end "\nNOTE: these buttons will not appear in peers!\n" "peer_warning" button $t.default -text Default -command "embDefBg $t" \ -cursor top_left_arrow $t window create end -window $t.default -padx 3 global embToggle set embToggle Short @@ -161,11 +162,10 @@ $t insert end "\n\nFinally, images fit comfortably in text widgets too:" $t image create end -image \ [image create photo -file [file join $tk_demoDirectory images ouster.png]] - proc textWindBigB w { $w configure -borderwidth 15 } @@ -300,10 +300,11 @@ set w [toplevel .peer$n] wm title $w "Text Peer #$n" frame $w.f -highlightthickness 1 -borderwidth 1 -relief sunken set t [$parent peer create $w.f.text -yscrollcommand "$w.scroll set" \ -borderwidth 0 -highlightthickness 0] + $t tag configure peer_warning -font boldFont pack $t -expand yes -fill both ttk::scrollbar $w.scroll -command "$t yview" pack $w.scroll -side right -fill y pack $w.f -expand yes -fill both } @@ -315,11 +316,12 @@ } else { set parent [winfo parent $textW] set w [winfo parent $parent] set t [$textW peer create $w.peer \ -yscrollcommand "$w.scroll set"] + $t tag configure peer_warning -font boldFont $w.pane add $t } } else { return } } Index: macosx/README ================================================================== --- macosx/README +++ macosx/README @@ -1,42 +1,42 @@ -Tcl/Tk Mac OS X README +Tcl/Tk macOS README ---------------------- -This is the README file for the Mac OS X/Darwin version of Tcl/Tk. +This is the README file for the macOS/Darwin version of Tcl/Tk. 1. Where to go for support -------------------------- - The tcl-mac mailing list on sourceforge is the best place to ask questions -specific to Tcl & Tk on Mac OS X: +specific to Tcl & Tk on macOS: http://lists.sourceforge.net/lists/listinfo/tcl-mac (this page also has a link to searchable archives of the list, please check them before asking on the list, many questions have already been answered). - For general Tcl/Tk questions, the newsgroup comp.lang.tcl is your best bet: http://groups.google.com/group/comp.lang.tcl/ -- The Tcl'ers Wiki also has many pages dealing with Tcl & Tk on Mac OS X, see +- The Tcl'ers Wiki also has many pages dealing with Tcl & Tk on macOS, see http://wiki.tcl.tk/_/ref?N=3753 http://wiki.tcl.tk/_/ref?N=8361 -- Please report bugs with Tk on Mac OS X to the tracker: +- Please report bugs with Tk on macOS to the tracker: http://core.tcl.tk/tk/reportlist -2. Using Tcl/Tk on Mac OS X +2. Using Tcl/Tk on macOS --------------------------- -- There are two versions of Tk available on Mac OS X: TkAqua using the native +- There are two versions of Tk available on macOS: TkAqua using the native aqua widgets and look&feel, and TkX11 using the traditional unix X11 wigets. TkX11 requires an X11 server to be installed, such as Apple's X11 (which is -available as an optional or default install on recent Mac OS X). +available as an optional or default install on recent macOS). TkAqua and TkX11 can be distinguished at runtime via [tk windowingsystem]. -- At a minimum, Mac OS X 10.3 is required to run Tcl and TkX11. -TkAqua requires Mac OS X 10.5 or later (starting with the Cocoa-based Tk 8.5.7). +- At a minimum, macOS 10.3 is required to run Tcl and TkX11. +TkAqua requires macOS 10.5 or later (starting with the Cocoa-based Tk 8.5.7). -- Unless weak-linking is used, Tcl/Tk built on Mac OS X 10.x will not run on +- Unless weak-linking is used, Tcl/Tk built on macOS 10.x will not run on 10.y with y < x; on the other hand Tcl/Tk built on 10.y will always run on 10.x with y <= x (but without any of the fixes and optimizations that would be available in a binary built on 10.x). Weak-linking is available on OS X 10.2 or later, it additionally allows Tcl/Tk built on 10.x to run on any 10.y with x > y >= z (for a chosen z >= 2). @@ -60,11 +60,11 @@ the Resources/Scripts directory of the framework. - [load]able binary extensions can linked as either ordinary shared libraries (.dylib) or as MachO bundles (since 8.4.10/8.5a3); bundles have the advantage that they are [load]ed more efficiently from a tcl VFS (no temporary copy to the -native filesystem required), and prior to Mac OS X 10.5, only bundles can be +native filesystem required), and prior to macOS 10.5, only bundles can be [unload]ed. - The 'deploy' target of macosx/GNUmakefile installs the html manpages into the standard documentation location in the Tcl/Tk frameworks: Tcl.framework/Resources/Documentation/Reference/Tcl @@ -161,11 +161,11 @@ present, this procedure is invoked instead by the standard Help menu item. Support for the Window menu and [tk::mac::ShowHelp] was added with the Cocoa-based Tk 8.5.7. - The TkAqua-specific command [tk::unsupported::MacWindowStyle style] is used to -get and set Mac OS X-specific toplevel window class and attributes. Note that +get and set macOS-specific toplevel window class and attributes. Note that the window class and many attributes have to be set before the window is first mapped for the change to have any effect. The command has the following syntax: tk::unsupported::MacWindowStyle style window ?class? ?attributes? The 2 argument form returns a list of the current class and attributes for the @@ -214,37 +214,37 @@ purple #800080 brown #996633 clear systemTransparent - The Cocoa-based TkAqua can be distinguished from the older Carbon-based -version via the [winfo server .] command, example output on Mac OS X 10.5.7: - Cocoa-based: CG409.3 Apple AppKit GC 949.46 Mac OS X 1057 +version via the [winfo server .] command, example output on macOS 10.5.7: + Cocoa-based: CG409.3 Apple AppKit GC 949.46 macOS 1057 Carbon-based: QD10R30 Apple 1057 - If you want to use Remote Debugging with Xcode, you need to set the environment variable XCNOSTDIN to 1 in the Executable editor for Wish. That will cause us to force closing stdin & stdout. Otherwise, given how Xcode launches Wish remotely, they will be left open and then Wish & gdb will fight for stdin. -3. Building Tcl/Tk on Mac OS X +3. Building Tcl/Tk on macOS ------------------------------ -- At least Mac OS X 10.3 is required to build Tcl and TkX11, and Mac OS X 10.5 +- At least macOS 10.3 is required to build Tcl and TkX11, and macOS 10.5 is required to build TkAqua. Apple's Xcode Developer Tools need to be installed (only the most recent version matching your OS release is supported), the Xcode installer is available on Mac OS X install media or may be present in /Applications/Installers on Macs that came with OS X preinstalled. The most recent version can always be downloaded from the ADC website http://connect.apple.com (free ADC membership required). -- Tcl/Tk are most easily built as Mac OS X frameworks via GNUmakefile in +- Tcl/Tk are most easily built as macOS frameworks via GNUmakefile in tcl/macosx and tk/macosx (see below for details), but can also be built with the standard unix configure and make buildsystem in tcl/unix resp. tk/unix as on any other unix platform (indeed, the GNUmakefiles are just wrappers around the unix buildsystem). -The Mac OS X specific configure flags are --enable-aqua, --enable-framework and +The macOS specific configure flags are --enable-aqua, --enable-framework and --disable-corefoundation (which disables CF and notably reverts to the standard select based notifier). Note that --enable-aqua is incompatible with --disable-corefoundation (for both Tcl and Tk configure). - It is also possible to build with the Xcode IDE via the projects in @@ -290,11 +290,11 @@ ${USER}.pbxuser file (located inside the Tk.xcodeproj bundle directory) with a text editor. - To build universal binaries outside of the Xcode IDE, set CFLAGS as follows: export CFLAGS="-arch i386 -arch x86_64 -arch ppc" -This requires Mac OS X 10.4 and Xcode 2.4 (or Xcode 2.2 if -arch x86_64 is +This requires macOS 10.4 and Xcode 2.4 (or Xcode 2.2 if -arch x86_64 is omitted, but _not_ Xcode 2.1) and will work on any architecture (on PowerPC Tiger you need to add "-isysroot /Developer/SDKs/MacOSX10.4u.sdk"). Note that configure requires CFLAGS to contain a least one architecture that can be run on the build machine (i.e. ppc on G3/G4, ppc or ppc64 on G5, ppc or i386 on Core and ppc, i386 or x86_64 on Core2/Xeon). @@ -387,65 +387,211 @@ TCL_FRAMEWORK_DIR=$HOME/Library/Frameworks TCLSH_DIR=$HOME/usr/bin sudo make -C tk${ver}/macosx install \ TCL_FRAMEWORK_DIR=$HOME/Library/Frameworks TCLSH_DIR=$HOME/usr/bin The Makefile variables TCL_FRAMEWORK_DIR and TCLSH_DIR were added with Tk 8.4.3. -4. About the event loop in Tk for Mac OSX ------------------------------------------ +4. Details regarding the macOS port of Tk. +------------------------------------------- + +4.1 About the event loop +~~~~~~~~~~~~~~~~~~~~~~~~ -The main program in a typical OSX application looks like this (see *) +The main program in a typical OSX application looks like this (see +https://developer.apple.com/library/mac/documentation/Cocoa/\ +Reference/ApplicationKit/Classes/NSApplication_Class) void NSApplicationMain(int argc, char *argv[]) { [NSApplication sharedApplication]; [NSBundle loadNibNamed:@"myMain" owner:NSApp]; [NSApp run]; } +Here NSApp is a standard global variable, initialized by the OS, which +points to an object in a subclass of NSApplication (called +TKApplication in the case of the macOS port of Tk). -The run method implements the event loop for the application. There -are three key steps in the run method. First it calls -[NSApp finishLaunching], which creates the bouncing application icon -and does other mysterious things. Second it creates an +The [NSApp run] method implements the event loop for a typical Mac +application. There are three key steps in the run method. First it +calls [NSApp finishLaunching], which creates the bouncing application +icon and does other mysterious things. Second it creates an NSAutoreleasePool. Third, it starts an event loop which drains the NSAutoreleasePool every time the queue is empty, and replaces the drained pool with a new one. This third step is essential to preventing memory leaks, since the internal methods of Appkit objects all assume that an autorelease pool is in scope and will be drained when the event processing cycle ends. -Mac OSX Tk does not call the [NSApp run] method at all. Instead it -uses the event loop built in to Tk. So we must take care to replicate -the important features of the method ourselves. Here is how this -works in outline. - -We add a private NSAUtoreleasePool* property to our subclass of -NSApplication. (The subclass is called TKApplication but can be -referenced with the global variable NSApp). The TkpInit -function calls [NSApp _setup] which initializes this property by -creating an NSAutoreleasePool. A bit later on, TkpInit calls -[NSAPP _setupEventLoop] which in turn calls the -[NSApp finishLaunching] method. - -Each time that Tcl processes an event in its queue, it calls a -platform specific function which, in the case of Mac OSX, is named -TkMacOSXEventsCheckProc. In the unix implementations of Tk, including -the Mac OSX version, this function collects events from an "event -source", and transfers them to the Tcl event queue. In Mac OSX the -event source is the NSApplication event queue. Each NSEvent is -converted to a Tcl event which is added to the Tcl event queue. The -NSEvent is also passed to [NSApp sendevent], which sends the event on -to the application's NSWindows, which send it to their NSViews, etc. +The macOS Tk application does not call the [NSApp run] method at +all. Instead it uses the event loop built in to Tk. So the +application must take care to replicate the important features of the +method ourselves. The way that autorelease pools are handled is +discussed in 4.2 below. Here we discuss the event handling itself. + +The Tcl event loop simply consists of repeated calls to TclDoOneEvent. +Each call to TclDoOneEvent begins by collecting all pending events from +an "event source", converting them to Tcl events and adding them +to the Tcl event queue. For macOS, the event source is the NSApp +object, which maintains an event queue even though its run method +will never be called to process them. The NSApp provides methods for +inspecting the queue and removing events from it as well as the +[NSApp sendevent] which sends an event to all of the application's +NSWindows which can then send it to subwindows, etc. + +The event collection process consists of first calling a platform +specific SetupProc and then a platform specific CheckProc. In +the macOS port, these are named TkMacOSXEventsSetupProc and +TkMacOSXEventsCheckProc. + +It is important to understand that the Apple window manager does not +have the concept of an expose event. Their replacement for an expose +event is to have the window manager call the [NSView drawRect] method +in any situation where an expose event for that NSView would be +generated in X11. The [NSView drawRect] method is a no-op which is +expected to be overridden by any application. In the case of Tcl, the +replacement [NSView drawRect] method creates a Tcl expose event +for each dirty rectangle of the NSView, and then adds the expose +event to the Tcl queue. + + +4.2 Autorelease pools +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to carry out the job of managing autorelease pools, which +would normally be handled by the [NSApp run] method, a private +NSAUtoreleasePool* property is added to the TkApplication subclass of +NSApplication. The TkpInit function calls [NSApp _setup] which +initializes this property by creating an NSAutoreleasePool. A bit +later on, TkpInit calls [NSAPP _setupEventLoop] which in turn calls +the [NSApp finishLaunching] method. + Since the CheckProc function gets called for every Tk event, it is an appropriate place to drain the main NSAutoreleasePool and replace it -with a new pool. This is done by calling the method -[NSApp _resetAutoreleasePool], where _resetAutoreleasePool is a method -which we define for the subclass TKApplication. - -One minor caveat is that there are several steps of the Tk -initialization which precede the call to TkpInit. Notably, the font -package is initialized first. Since there is no NSAUtoreleasePool in -scope prior to calling TkpInit, the functions called in these -preliminary stages need to create and drain their own +with a new pool. This is done by calling the method [NSApp +_resetAutoreleasePool], where _resetAutoreleasePool is a method which +we define for the subclass. Unfortunately, by itself this is not +sufficient for safe memory managememt because, as was made painfully +evident with the release of OS X 10.13, it is possible for calls to +TclDoOneEvent, and hence to CheckProc, to be nested. Draining the +autorelease pool in a nested call leads to crashes as objects in use +by the outer call can get freed by the inner call and then reused later. +One particular situation where this happens is when a modal dialogue +gets posted by a Tk Application. To address this, the NSApp object +also implements a semaphore to prevent draining the autorelease pool +in nested calls to CheckProc. + +One additional minor caveat for developers is that there are several +steps of the Tk initialization which precede the call to TkpInit. +Notably, the font package is initialized first. Since there is no +NSAUtoreleasePool in scope prior to calling TkpInit, the functions +called in these preliminary stages need to create and drain their own NSAutoreleasePools whenever they call methods of Appkit objects (e.g. NSFont). -* https://developer.apple.com/library/mac/documentation/Cocoa/\ -Reference/ApplicationKit/Classes/NSApplication_Class +4.3 Clipping regions and "ghost windows" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Another unusual aspect of the macOS port is its use of clipping +regions. It was part of Daniel Steffen's original design that the +TkWindowPrivate struct maintains three HIShapeRef regions, named +visRgn, aboveVisRgn and drawRgn. These regions are used as clipping +masks whenever drawing into an NSView. The visRgn is the bounding box +of the window with a rectangle removed for each subwindow and for each +sibling window at a higher stacking level. The drawRgn is the +intersection of the visRgn with the clipping rectangle of the +window. (Normally, the clipping rectangle is the same as the bounding +rectangle, but drawing can be clipped to a smaller rectangle by +calling TkpClipDrawableToRect.) The aboveVisRgn is the intersection of +the window's bounding rectangle with the bounding rectangle of the +parent window. Much of the code in tkMacOSXSubindows.c is devoted to +rebuilding these clipping regions whenever something changes in the +layout of the windows. This turns out to be a tricky thing to do and +it is extremely prone to errors which can be difficult to trace. + +It is not entirely clear what the original reason for using these +clipping regions was. But one benefit is that if they are correctly +maintained then it allows windows to be drawn in any order. You do +not have to draw them in the order of the window hierarchy. Each +window can draw its entire rectangle through its own mask and never +have to worry about drawing in the wrong place. It is likely that +the need for using clipping regions arose because, as Apple explicitly +states in the documentation for [NSView subviews], + + "The order of the subviews may be considered as being + back-to-front, but this does not imply invalidation and drawing + behavior." + +In the early versions of the macOS port, buttons were implemented as +subviews of class TkButton. This probably exacerbated the likelihood +that Tk windows would need to be drawn in arbitrary order. + +The most obvious side effect caused by not maintaining the clipping +regions is the appearance of so-called "ghost windows". A common +situation where these may arise is when a window containing buttons +is being scrolled. A user may see two images of the same button on +the screen, one in the pre-scroll location and one in the post-scroll +location. + +To see how these 'ghost windows' can arise, think about what happens if +the clipping regions are not maintained correctly. A window might +have a rectangle missing from its clipping region because that +rectangle is the bounding rectangle for a subwindow, say a button. +The parent should not draw in the missing rectangle since doing so +would trash the button. The button is responsible for drawing +there. Now imagine that the button gets moved, say by a scroll, but +the missing rectangle in the parent's clipping region does not get +moved correctly, or it gets moved later on, after the parent has +redrawn itself. The parent would still not be allowed to draw in the +old rectangle, so the user would continue to see the image of the +button in its old location, as well as another image in the new +location. This is a prototypical example of a "ghost window". +Anytime you see a "ghost window", you should suspect problems with the +updates to the clipping region visRgn. It is natural to look for +timing issues, race conditions, or other "event loop problems". But +in fact, the whole design of the code is to make those timing issues +irrelevant. As long as the clipping regions are correctly maintained +the timing does not matter. And if they are not correctly maintained +then you will see "ghost windows". + +It is worth including a detailed description of one specific place +where the failure to correctly maintain clipping regions caused "ghost +window" artifacts that plagued the macOS port for years. These +occurred when scrolling a Text widget which contained embedded +subwindows. It involved some specific differences between the +low-level behavior of Apple's window manager versus those of the other +platforms, and the fix ultimately required changes in the generic Tk +implementation (documented in the comments in the DisplayText +function). + +The Text widget attempts to improve perfomance when scrolling by +minimizing the number of text lines which need to be redisplayed. It +does this by calling the platform-specific TkScrollWindow function +which uses a low-level routine to map one rectangle of the window to +another. The TkScrollWindow function returns a damage region which is +then used by the Text widget's DisplayText function to determine which +text lines need to be redrawn. On the unix and win platforms, this +damage region includes bounding rectangles for all embedded windows +inside the Text widget. The way that this works is system dependent. +On unix, the low level scrolling is done by XCopyRegion, which +generates a GraphicsExpose event for each embedded window. These +GraphicsExposed events are processsed within TkScrollWindow, using a +special handler which adds the bounding rectangle of each subwindow to +the damage region. On the win platform the damage region is built by +the low level function ScrollWindowEx, and it also includes bounding +rectangles for all embedded windows. This is possible because on X11 +and Windows every Tk widget is also known to the window manager as a +window. The situation is different on macOS. The underlying object +for a top level window on macOS is the NSView. However, Apple +explicitly warns in its documentation that performance degradation +occurs when an NSView has more than about 100 subviews. A Text widget +with thousands of lines of text could easily contain more than 100 +embedded windows. In fact, while the original Cocoa port of Tk did +use the NSButton object, which is derived from NSView, as the basis +for its Tk Buttons, that was changed in order to improve performance. +Moreover, the low level routine used for scrolling on macOS, namely +[NSView scrollrect:by], does not provide any damage information. So +TkScrollWindow needs to work differently on macOS. Since it would be +inefficient to iterate through all embedded windows in a Text widget, +looking for those which meet the scrolling area, the damage region +constructed by TkScrollWindow contains only the difference between the +source and destination rectangles for the scrolling. The embedded +windows are redrawn within the DisplayText function by some +conditional code which is only used for macOS. + Index: macosx/tkMacOSXDraw.c ================================================================== --- macosx/tkMacOSXDraw.c +++ macosx/tkMacOSXDraw.c @@ -1539,45 +1539,18 @@ dmgRgn = HIShapeCreateMutableWithRect(&srcRect); extraRgn = HIShapeCreateWithRect(&dstRect); ChkErr(HIShapeDifference, dmgRgn, extraRgn, (HIMutableShapeRef) dmgRgn); result = HIShapeIsEmpty(dmgRgn) ? 0 : 1; - /* Convert to Tk coordinates. */ + /* Convert to Tk coordinates, offset by the window origin. */ TkMacOSXSetWithNativeRegion(damageRgn, dmgRgn); - TkMacOSXOffsetRegion(damageRgn, -macDraw->xOff, -macDraw->yOff); if (extraRgn) { CFRelease(extraRgn); } /* Scroll the rectangle. */ [view scrollRect:scrollSrc by:NSMakeSize(dx, -dy)]; - - /* Shift the Tk children which meet the source rectangle. */ - TkWindow *winPtr = (TkWindow *)tkwin; - TkWindow *childPtr; - CGRect childBounds; - for (childPtr = winPtr->childList; childPtr != NULL; childPtr = childPtr->nextPtr) { - if (Tk_IsMapped(childPtr) && !Tk_IsTopLevel(childPtr)) { - TkMacOSXWinCGBounds(childPtr, &childBounds); - if (CGRectIntersectsRect(srcRect, childBounds)) { - MacDrawable *macChild = childPtr->privatePtr; - if (macChild) { - macChild->yOff += dy; - macChild->xOff += dx; - childPtr->changes.y = macChild->yOff; - childPtr->changes.x = macChild->xOff; - TkMacOSXInvalidateWindow(macChild, TK_PARENT_WINDOW); - } - } - } - } - TkMacOSXInvalidateWindow(macDraw, TK_WINDOW_ONLY); - - /* Queue up Expose events for the damage region. */ - int oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); - [view generateExposeEvents:dmgRgn childrenOnly:1]; - Tcl_SetServiceMode(oldMode); } } else { dmgRgn = HIShapeCreateEmpty(); TkMacOSXSetWithNativeRegion(damageRgn, dmgRgn); } Index: macosx/tkMacOSXPrivate.h ================================================================== --- macosx/tkMacOSXPrivate.h +++ macosx/tkMacOSXPrivate.h @@ -342,11 +342,10 @@ @end @interface TKContentView(TKWindowEvent) - (void) drawRect: (NSRect) rect; - (void) generateExposeEvents: (HIShapeRef) shape; -- (void) generateExposeEvents: (HIShapeRef) shape childrenOnly: (int) childrenOnly; - (void) viewDidEndLiveResize; - (void) tkToolbarButton: (id) sender; - (BOOL) isOpaque; - (BOOL) wantsDefaultClipping; - (BOOL) acceptsFirstResponder; Index: macosx/tkMacOSXWindowEvent.c ================================================================== --- macosx/tkMacOSXWindowEvent.c +++ macosx/tkMacOSXWindowEvent.c @@ -361,11 +361,11 @@ event.xexpose.x = damageBounds.origin.x - bounds.origin.x; event.xexpose.y = damageBounds.origin.y - bounds.origin.y; event.xexpose.width = damageBounds.size.width; event.xexpose.height = damageBounds.size.height; event.xexpose.count = 0; - Tk_HandleEvent(&event); + Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); #ifdef TK_MAC_DEBUG_DRAWING NSLog(@"Expose %p {{%d, %d}, {%d, %d}}", event.xany.window, event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height); #endif @@ -913,20 +913,14 @@ * immediately removed from the Tcl event loop and processed. Typically, they * should be queued, however. */ - (void) generateExposeEvents: (HIShapeRef) shape { - [self generateExposeEvents:shape childrenOnly:0]; -} - -- (void) generateExposeEvents: (HIShapeRef) shape - childrenOnly: (int) childrenOnly -{ - TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); unsigned long serial; CGRect updateBounds; int updatesNeeded; + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); if (!winPtr) { return; } Index: tests/textDisp.test ================================================================== --- tests/textDisp.test +++ tests/textDisp.test @@ -946,10 +946,32 @@ .t insert end xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n .t insert end xxxxxxxxxxxxxxxxxxxxxxxxxx update set scrollInfo } [list 0.0 [expr {4.0/11}]] +test textDisp-6.10 {DisplayText, redisplay embedded windows after scroll.} {aqua} { + .t configure -wrap char + .t delete 1.0 end + .t insert 1.0 "Line 1" + foreach i {2 3 4} { + .t insert end "\nLine $i" + } + .t insert end "\n" + .t window create end -create { + button %W.button_one -text "Button 1"} + .t insert end "\nLine 6\n" + .t window create end -create { + button %W.button_two -text "Button 2"} + .t insert end "\nLine 8\n" + .t window create end -create { + button %W.button_three -text "Button 3"} + update + .t delete 2.0 3.0 + update + list $tk_textEmbWinDisplay +} {{4.0 6.0}} + # The following group of tests is marked non-portable because # they result in a lot of extra redisplay under Ultrix. I don't # know why this is so.