Index: generic/tkBind.c ================================================================== --- generic/tkBind.c +++ generic/tkBind.c @@ -73,11 +73,24 @@ * within Tk_BindEvent for more on this), for a total of 20 events to cover * the three button presses and two intervening releases. If you reduce * EVENT_BUFFER_SIZE too much, shift multi-clicks will be lost. */ -#define EVENT_BUFFER_SIZE 30 +/* + * NOTE: The changes which were needed to make Tk work on OSX 10.14 (Mojave) + * also demand that the event ring be a bit bigger. It might be wise to + * augment the current double-click pattern matching by adding a new + * DoubleClick modifier bit which could be set based on the clickCount of the + * Apple NSEvent object. + */ + +#ifndef TK_MAC_OSX + #define EVENT_BUFFER_SIZE 45 +#else + #define EVENT_BUFFER_SIZE 30 +#endif + typedef struct Tk_BindingTable_ { XEvent eventRing[EVENT_BUFFER_SIZE]; /* Circular queue of recent events (higher * indices are for more recent events). */ Detail detailRing[EVENT_BUFFER_SIZE]; Index: generic/tkButton.c ================================================================== --- generic/tkButton.c +++ generic/tkButton.c @@ -877,11 +877,17 @@ * TkpDisplayButton cleared the REDRAW_PENDING flag. */ Tcl_CancelIdleCall(TkpDisplayButton, butPtr); XFlush(butPtr->display); + #ifndef MAC_OSX_TK + /* + * On the mac you can not sleep in a display proc, and the + * flash command doesn't do anything anyway. + */ Tcl_Sleep(50); + #endif } } break; case COMMAND_INVOKE: Index: generic/tkTextDisp.c ================================================================== --- generic/tkTextDisp.c +++ generic/tkTextDisp.c @@ -22,10 +22,15 @@ #include "tkUnixInt.h" #endif #ifdef MAC_OSX_TK #include "tkMacOSXInt.h" +#define OK_TO_LOG (!TkpAppIsDrawing()) +#define FORCE_DISPLAY(winPtr) TkpDisplayWindow(winPtr) +#else +#define OK_TO_LOG 1 +#define FORCE_DISPLAY(winPtr) #endif /* * "Calculations of line pixel heights and the size of the vertical * scrollbar." @@ -201,16 +206,25 @@ #define FP_EQUAL_SCALE(double1, double2, scaleFactor) \ (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3) /* - * Macro to make debugging/testing logging a little easier. + * Macros to make debugging/testing logging a little easier. + * + * On OSX 10.14 Drawing procedures are sometimes run because the system has + * decided to redraw the window. This can corrupt the data that a test is + * trying to collect. So we don't write to the logging variables when the + * drawing procedure is being run that way. Other systems can always log. */ -#define LOG(toVar,what) \ - Tcl_SetVar2(textPtr->interp, toVar, NULL, (what), \ - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT) +#define LOG(toVar,what) \ + if (OK_TO_LOG) \ + Tcl_SetVar2(textPtr->interp, toVar, NULL, (what), \ + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT) +#define CLEAR(var) \ + if (OK_TO_LOG) \ + Tcl_SetVar2(interp, var, NULL, "", TCL_GLOBAL_ONLY) /* * The following structure describes one line of the display, which may be * either part or all of one line of the text. */ @@ -3119,10 +3133,22 @@ static void GenerateWidgetViewSyncEvent( TkText *textPtr, /* Information about text widget. */ Bool InSync) /* true if in sync, false otherwise */ { + /* + * OSX 10.14 needs to be told to display the window when the Text Widget + * is in sync. (That is, to run DisplayText inside of the drawRect + * method.) Otherwise the screen might not get updated until an event + * like a mouse click is received. But that extra drawing corrupts the + * data that the test suite is trying to collect. + */ + + if (!tkTextDebug) { + FORCE_DISPLAY(textPtr->tkwin); + } + TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync", Tcl_NewBooleanObj(InSync)); } /* @@ -4134,11 +4160,11 @@ interp = textPtr->interp; Tcl_Preserve(interp); if (tkTextDebug) { - Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY); + CLEAR("tk_textRelayout"); } if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) || (dInfoPtr->maxY <= dInfoPtr->y)) { UpdateDisplayInfo(textPtr); @@ -4145,11 +4171,11 @@ dInfoPtr->flags &= ~REDRAW_PENDING; goto doScrollbars; } numRedisplays++; if (tkTextDebug) { - Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY); + CLEAR("tk_textRedraw"); } /* * Choose a new current item if that is needed (this could cause event * handlers to be invoked, hence the preserve/release calls and the loop, @@ -5132,18 +5158,20 @@ * changed. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; GC newGC; XGCValues gcValues; + Bool inSync = 1; /* * Schedule the window redisplay. See TkTextChanged for the reason why * this has to be done before any calls to FreeDLines. */ if (!(dInfoPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayText, textPtr); + inSync = 0; } dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE |REPICK_NEEDED; /* @@ -5211,10 +5239,11 @@ dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; if (mask & TK_TEXT_LINE_GEOMETRY) { + /* * Set up line metric recalculation. * * Avoid the special zero value, since that is used to mark individual * lines as being out of date. @@ -5235,11 +5264,15 @@ if (dInfoPtr->lineUpdateTimer == NULL) { textPtr->refCount++; dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr); - GenerateWidgetViewSyncEvent(textPtr, 0); + inSync = 0; + } + + if (!inSync) { + GenerateWidgetViewSyncEvent(textPtr, 0); } } } /* Index: macosx/tkMacOSXButton.c ================================================================== --- macosx/tkMacOSXButton.c +++ macosx/tkMacOSXButton.c @@ -418,10 +418,13 @@ width += butPtr->borderWidth*2; } width += butPtr->inset*2; height += butPtr->inset*2; + if ([NSApp macMinorVersion] == 6) { + width += 12; + } Tk_GeometryRequest(butPtr->tkwin, width, height); Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); } Index: macosx/tkMacOSXClipboard.c ================================================================== --- macosx/tkMacOSXClipboard.c +++ macosx/tkMacOSXClipboard.c @@ -10,10 +10,11 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkMacOSXPrivate.h" +#include "tkMacOSXConstants.h" #include "tkSelect.h" static NSInteger changeCount = -1; static Tk_Window clipboardOwner = NULL; Index: macosx/tkMacOSXConstants.h ================================================================== --- macosx/tkMacOSXConstants.h +++ macosx/tkMacOSXConstants.h @@ -13,19 +13,32 @@ */ #ifndef _TKMACCONSTANTS #define _TKMACCONSTANTS +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +#define NSFullScreenWindowMask (1 << 14) +#endif + /* * Let's raise a glass for the project manager who improves our lives by * generating deprecation warnings about pointless changes of the names * of constants. */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 +#define kCTFontDefaultOrientation kCTFontOrientationDefault +#define kCTFontVerticalOrientation kCTFontOrientationVertical +#endif #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 #define NSOKButton NSModalResponseOK #endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 +#define kCTFontUserFixedPitchFontType kCTFontUIFontUserFixedPitch +#endif #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 #define NSAppKitDefined NSEventTypeAppKitDefined #define NSApplicationActivatedEventType NSEventSubtypeApplicationActivated #define NSApplicationDeactivatedEventType NSEventSubtypeApplicationDeactivated @@ -90,7 +103,17 @@ #define NSUnifiedTitleAndToolbarWindowMask NSWindowStyleMaskUnifiedTitleAndToolbar #define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable #define NSBorderlessWindowMask NSWindowStyleMaskBorderless #define NSFullScreenWindowMask NSWindowStyleMaskFullScreen #endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 +#define NSStringPboardType NSPasteboardTypeString +#define NSOnState NSControlStateValueOn +#define NSOffState NSControlStateValueOff +// Now we are also changing names of methods! +#define graphicsContextWithGraphicsPort graphicsContextWithCGContext +#endif + #endif + Index: macosx/tkMacOSXDraw.c ================================================================== --- macosx/tkMacOSXDraw.c +++ macosx/tkMacOSXDraw.c @@ -16,10 +16,16 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXDebug.h" #include "tkButton.h" +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 +#define GET_CGCONTEXT [[NSGraphicsContext currentContext] CGContext] +#else +#define GET_CGCONTEXT [[NSGraphicsContext currentContext] graphicsPort] +#endif + /* #ifdef TK_MAC_DEBUG #define TK_MAC_DEBUG_DRAWING #define TK_MAC_DEBUG_IMAGE_DRAWING #endif @@ -1487,11 +1493,11 @@ } if (dontDraw) { goto end; } dc.view = view; - dc.context = [[NSGraphicsContext currentContext] graphicsPort]; + dc.context = GET_CGCONTEXT; dc.portBounds = NSRectToCGRect([view bounds]); if (dc.clipRgn) { clipBounds = CGContextGetClipBoundingBox(dc.context); } } else { @@ -1656,11 +1662,11 @@ TkMacOSXUpdateClipRgn(macDraw->winPtr); #ifdef TK_MAC_DEBUG_DRAWING TkMacOSXDbgMsg("%s", macDraw->winPtr->pathName); NSView *view = TkMacOSXDrawableView(macDraw); if ([view lockFocusIfCanDraw]) { - CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextRef context = GET_CGCONTEXT; CGContextSaveGState(context); CGContextConcatCTM(context, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, [view bounds].size.height)); ChkErr(HIShapeReplacePathInCGContext, macDraw->visRgn, context); CGContextSetRGBFillColor(context, 0.0, 1.0, 0.0, 0.1); Index: macosx/tkMacOSXEvent.c ================================================================== --- macosx/tkMacOSXEvent.c +++ macosx/tkMacOSXEvent.c @@ -110,18 +110,22 @@ /* *---------------------------------------------------------------------- * * TkMacOSXFlushWindows -- * - * This routine flushes all the visible windows of the application. It is - * called by XSync(). + * This routine is a stub called by XSync, which is called during + * the Tk update command. It calls displayIfNeeded on all visible + * windows. This is necessary in order to insure that update will + * run all of the display procedures which have been registered as + * idle tasks. The test suite assumes that this is the case. * * Results: * None. * * Side effects: - * Flushes all visible Cocoa windows + * Calls the drawRect method of the contentView of each visible + * window. * *---------------------------------------------------------------------- */ MODULE_SCOPE void @@ -132,10 +136,11 @@ for (NSWindow *w in macWindows) { if (TkMacOSXGetXWindow(w)) { [w displayIfNeeded]; } } + } /* * Local Variables: Index: macosx/tkMacOSXFont.c ================================================================== --- macosx/tkMacOSXFont.c +++ macosx/tkMacOSXFont.c @@ -12,23 +12,15 @@ * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkMacOSXPrivate.h" #include "tkMacOSXFont.h" +#include "tkMacOSXConstants.h" -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1080 #define defaultOrientation kCTFontDefaultOrientation #define verticalOrientation kCTFontVerticalOrientation -#else -#define defaultOrientation kCTFontOrientationDefault -#define verticalOrientation kCTFontOrientationVertical -#endif -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 #define fixedPitch kCTFontUserFixedPitchFontType -#else -#define fixedPitch kCTFontUIFontUserFixedPitch -#endif /* #ifdef TK_MAC_DEBUG #define TK_MAC_DEBUG_FONTS #endif Index: macosx/tkMacOSXInit.c ================================================================== --- macosx/tkMacOSXInit.c +++ macosx/tkMacOSXInit.c @@ -14,11 +14,10 @@ */ #include "tkMacOSXPrivate.h" #include -#include #include #include static char tkLibPath[PATH_MAX + 1] = ""; @@ -27,12 +26,10 @@ * to the auto_path. */ static char scriptPath[PATH_MAX + 1] = ""; -long tkMacOSXMacOSXVersion = 0; - #pragma mark TKApplication(TKInit) @interface TKApplication(TKKeyboard) - (void) keyboardChanged: (NSNotification *) notification; @end @@ -46,10 +43,12 @@ - (void) _setupMenus; @end @implementation TKApplication @synthesize poolLock = _poolLock; +@synthesize macMinorVersion = _macMinorVersion; +@synthesize isDrawing = _isDrawing; @end /* * #define this to see a message on stderr whenever _resetAutoreleasePool is * called while the pool is locked. @@ -150,10 +149,29 @@ * Install the global autoreleasePool. */ _mainPool = [NSAutoreleasePool new]; [NSApp setPoolLock:0]; + /* + * Record the OS version we are running on. + */ + int minorVersion; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 + Gestalt(gestaltSystemVersionMinor, (SInt32*)&minorVersion); +#else + NSOperatingSystemVersion systemVersion; + systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; + minorVersion = systemVersion.minorVersion; +#endif + [NSApp setMacMinorVersion: minorVersion]; + + /* + * We are not drawing yet. + */ + + [NSApp setIsDrawing: NO]; + /* * Be our own delegate. */ [self setDelegate:self]; @@ -253,11 +271,10 @@ * don't want to do the following initialization multiple times we protect * against doing it more than once. */ if (!initialized) { - struct utsname name; struct stat st; initialized = 1; /* @@ -265,24 +282,10 @@ */ #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 # error Mac OS X 10.6 required #endif - - if (!uname(&name)) { - tkMacOSXMacOSXVersion = (strtod(name.release, NULL) + 96) * 10; - } - /*Check for new versioning scheme on Yosemite (10.10) and later.*/ - if (MAC_OS_X_VERSION_MIN_REQUIRED > 100000) { - tkMacOSXMacOSXVersion = MAC_OS_X_VERSION_MIN_REQUIRED/100; - } - if (tkMacOSXMacOSXVersion && MAC_OS_X_VERSION_MIN_REQUIRED < 100000 && - tkMacOSXMacOSXVersion/10 < MAC_OS_X_VERSION_MIN_REQUIRED/10) { - Tcl_Panic("Mac OS X 10.%d or later required !", - (MAC_OS_X_VERSION_MIN_REQUIRED/10)-100); - } - #ifdef TK_FRAMEWORK /* * When Tk is in a framework, force tcl_findLibrary to look in the * framework scripts directory. Index: macosx/tkMacOSXInt.h ================================================================== --- macosx/tkMacOSXInt.h +++ macosx/tkMacOSXInt.h @@ -197,10 +197,13 @@ MODULE_SCOPE void TkpClipDrawableToRect(Display *display, Drawable d, int x, int y, int width, int height); MODULE_SCOPE void TkpRetainRegion(TkRegion r); MODULE_SCOPE void TkpReleaseRegion(TkRegion r); MODULE_SCOPE void TkpShiftButton(NSButton *button, NSPoint delta); +MODULE_SCOPE Bool TkpAppIsDrawing(void); +MODULE_SCOPE void TkpDisplayWindow(Tk_Window tkwin); + /* * Include the stubbed internal platform-specific API. */ #include "tkIntPlatDecls.h" Index: macosx/tkMacOSXKeyEvent.c ================================================================== --- macosx/tkMacOSXKeyEvent.c +++ macosx/tkMacOSXKeyEvent.c @@ -328,11 +328,11 @@ pt.x = caret_x; pt.y = caret_y; pt = [self convertPoint: pt toView: nil]; - pt = [[self window] convertPointToScreen: pt]; + pt = [[self window] tkConvertPointToScreen: pt]; pt.y -= caret_height; rect.origin = pt; rect.size.width = caret_height; rect.size.height = caret_height; Index: macosx/tkMacOSXMenu.c ================================================================== --- macosx/tkMacOSXMenu.c +++ macosx/tkMacOSXMenu.c @@ -783,11 +783,11 @@ int oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); NSView *view = [win contentView]; NSRect frame = NSMakeRect(x + 9, tkMacOSXZeroScreenHeight - y - 9, 1, 1); frame.origin = [view convertPoint: - [win convertPointFromScreen:frame.origin] fromView:nil]; + [win tkConvertPointFromScreen:frame.origin] fromView:nil]; NSMenu *menu = (NSMenu *) menuPtr->platformData; NSPopUpButtonCell *popUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; Index: macosx/tkMacOSXMouseEvent.c ================================================================== --- macosx/tkMacOSXMouseEvent.c +++ macosx/tkMacOSXMouseEvent.c @@ -88,18 +88,18 @@ } /* Create an Xevent to add to the Tk queue. */ NSPoint global, local = [theEvent locationInWindow]; if (eventWindow) { /* local will be in window coordinates. */ - global = [eventWindow convertPointToScreen: local]; + global = [eventWindow tkConvertPointToScreen: local]; local.y = [eventWindow frame].size.height - local.y; global.y = tkMacOSXZeroScreenHeight - global.y; } else { /* local will be in screen coordinates. */ if (_windowWithMouse ) { eventWindow = _windowWithMouse; global = local; - local = [eventWindow convertPointFromScreen: local]; + local = [eventWindow tkConvertPointFromScreen: local]; local.y = [eventWindow frame].size.height - local.y; global.y = tkMacOSXZeroScreenHeight - global.y; } else { /* We have no window. Use the screen???*/ local.y = tkMacOSXZeroScreenHeight - local.y; global = local; @@ -371,11 +371,11 @@ NSWindow *win = TkMacOSXDrawableWindow(w); if (win) { NSPoint local; - local = [win convertPointFromScreen:global]; + local = [win tkConvertPointFromScreen:global]; local.y = [win frame].size.height - local.y; if (macWin->winPtr && macWin->winPtr->wmInfoPtr) { local.x -= macWin->winPtr->wmInfoPtr->xInParent; local.y -= macWin->winPtr->wmInfoPtr->yInParent; } @@ -469,11 +469,11 @@ med.local = med.global; if (win) { NSPoint local = NSMakePoint(x, tkMacOSXZeroScreenHeight - y); - local = [win convertPointFromScreen:local]; + local = [win tkConvertPointFromScreen:local]; local.y = [win frame].size.height - local.y; if (macWin->winPtr && macWin->winPtr->wmInfoPtr) { local.x -= macWin->winPtr->wmInfoPtr->xInParent; local.y -= macWin->winPtr->wmInfoPtr->yInParent; } Index: macosx/tkMacOSXNotify.c ================================================================== --- macosx/tkMacOSXNotify.c +++ macosx/tkMacOSXNotify.c @@ -29,51 +29,128 @@ Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)) static void TkMacOSXNotifyExitHandler(ClientData clientData); static void TkMacOSXEventsSetupProc(ClientData clientData, int flags); static void TkMacOSXEventsCheckProc(ClientData clientData, int flags); + +#ifdef TK_MAC_DEBUG_EVENTS +static char* Tk_EventName[39] = { + "", + "", + "KeyPress", /*2*/ + "KeyRelease", /*3*/ + "ButtonPress", /*4*/ + "ButtonRelease", /*5*/ + "MotionNotify", /*6*/ + "EnterNotify", /*7*/ + "LeaveNotify", /*8*/ + "FocusIn", /*9*/ + "FocusOut", /*10*/ + "KeymapNotify", /*11*/ + "Expose", /*12*/ + "GraphicsExpose", /*13*/ + "NoExpose", /*14*/ + "VisibilityNotify", /*15*/ + "CreateNotify", /*16*/ + "DestroyNotify", /*17*/ + "UnmapNotify", /*18*/ + "MapNotify", /*19*/ + "MapRequest", /*20*/ + "ReparentNotify", /*21*/ + "ConfigureNotify", /*22*/ + "ConfigureRequest", /*23*/ + "GravityNotify", /*24*/ + "ResizeRequest", /*25*/ + "CirculateNotify", /*26*/ + "CirculateRequest", /*27*/ + "PropertyNotify", /*28*/ + "SelectionClear", /*29*/ + "SelectionRequest", /*30*/ + "SelectionNotify", /*31*/ + "ColormapNotify", /*32*/ + "ClientMessage", /*33*/ + "MappingNotify", /*34*/ + "VirtualEvent", /*35*/ + "ActivateNotify", /*36*/ + "DeactivateNotify", /*37*/ + "MouseWheelEvent" /*38*/ +}; + +static Tk_RestrictAction +InspectQueueRestrictProc( + ClientData arg, + XEvent *eventPtr) +{ + XVirtualEvent* ve = (XVirtualEvent*) eventPtr; + const char *name; + long serial = ve->serial; + long time = eventPtr->xkey.time; + + if (eventPtr->type == VirtualEvent) { + name = ve->name; + } else { + name = Tk_EventName[eventPtr->type]; + } + printf(" > %s;serial = %lu; time=%lu)\n", name, serial, time); + return TK_DEFER_EVENT; +} + +/* + * Debugging tool which prints the current Tcl queue. + */ + +void DebugPrintQueue(void) +{ + ClientData oldArg; + Tk_RestrictProc *oldProc; + + oldProc = Tk_RestrictEvents(InspectQueueRestrictProc, NULL, &oldArg); + printf("Current queue:\n"); + while (Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT)) {}; + Tk_RestrictEvents(oldProc, oldArg, &oldArg); +} +# endif #pragma mark TKApplication(TKNotify) @interface NSApplication(TKNotify) /* We need to declare this hidden method. */ - (void) _modalSession: (NSModalSession) session sendEvent: (NSEvent *) event; @end -@implementation NSWindow(TKNotify) -- (id) tkDisplayIfNeeded -{ - if (![self isAutodisplay]) { - [self displayIfNeeded]; - } - return nil; -} -@end - @implementation TKApplication(TKNotify) -/* Display all windows each time an event is removed from the queue.*/ -- (NSEvent *) nextEventMatchingMask: (NSUInteger) mask - untilDate: (NSDate *) expiration inMode: (NSString *) mode - dequeue: (BOOL) deqFlag -{ - NSEvent *event = [super nextEventMatchingMask:mask - untilDate:expiration - inMode:mode - dequeue:deqFlag]; - /* Retain this event for later use. Must be released.*/ - [event retain]; - [NSApp makeWindowsPerform:@selector(tkDisplayIfNeeded) inOrder:NO]; - return event; -} +/* + * Earlier versions of Tk would override nextEventMatchingMask here, adding a + * call to displayIfNeeded on all windows after calling super. This would cause + * windows to be redisplayed (if necessary) each time that an event was + * received. This was intended to replace Apple's default autoDisplay + * mechanism, which the earlier versions of Tk would disable. When autoDisplay + * is set to the default value of YES, the Apple event loop will call + * displayIfNeeded on all windows at the beginning of each iteration of their + * event loop. Since Tk does not call the Apple event loop, it was thought + * that the autoDisplay behavior needed to be replicated. + * + * However, as of OSX 10.14 (Mojave) the autoDisplay property became + * deprecated. Luckily it turns out that, even though we don't ever start the + * Apple event loop, the Apple window manager still calls displayIfNeeded on + * all windows on a regular basis, perhaps each time the queue is empty. So we + * no longer, and perhaps never did need to set autoDisplay to NO, nor call + * displayIfNeeded on our windows. We can just leave all of that to the window + * manager. + */ /* * Call super then check the pasteboard. */ - (void) sendEvent: (NSEvent *) theEvent { [super sendEvent:theEvent]; [NSApp tkCheckPasteboard]; +#ifdef TK_MAC_DEBUG_EVENTS + printf("Sending event of type %d\n", (int)[theEvent type]); + DebugPrintQueue(); +#endif } @end #pragma mark - @@ -227,11 +304,10 @@ dequeue:NO]; if (currentEvent) { if (currentEvent.type > 0) { Tcl_SetMaxBlockTime(&zeroBlockTime); } - [currentEvent release]; } } } /* @@ -296,11 +372,10 @@ [NSApp _modalSession:modalSession sendEvent:currentEvent]; } else { [NSApp sendEvent:currentEvent]; } } - [currentEvent release]; } else { break; } } while (1); /* Now we can unlock the pool. */ Index: macosx/tkMacOSXPrivate.h ================================================================== --- macosx/tkMacOSXPrivate.h +++ macosx/tkMacOSXPrivate.h @@ -263,13 +263,17 @@ NSWindow *_windowWithMouse; NSAutoreleasePool *_mainPool; #ifdef __i386__ /* The Objective C runtime used on i386 requires this. */ int _poolLock; + int _macMinorVersion; + Bool _isDrawing; #endif } @property int poolLock; +@property int macMinorVersion; +@property Bool isDrawing; @end @interface TKApplication(TKInit) - (NSString *)tkFrameworkImagePath:(NSString*)image; - (void)_resetAutoreleasePool; @@ -342,12 +346,12 @@ VISIBILITY_HIDDEN @interface TKWindow : NSWindow @end @interface NSWindow(TKWm) -- (NSPoint) convertPointToScreen:(NSPoint)point; -- (NSPoint) convertPointFromScreen:(NSPoint)point; +- (NSPoint) tkConvertPointToScreen:(NSPoint)point; +- (NSPoint) tkConvertPointFromScreen:(NSPoint)point; @end #pragma mark NSMenu & NSMenuItem Utilities @interface NSMenu(TKUtils) Index: macosx/tkMacOSXScrlbr.c ================================================================== --- macosx/tkMacOSXScrlbr.c +++ macosx/tkMacOSXScrlbr.c @@ -6,10 +6,11 @@ * * Copyright (c) 1996 by Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen * Copyright (c) 2015 Kevin Walzer/WordTech Commununications LLC. + * Copyright (c) 2018 Marc Culler * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkInt.h" @@ -24,28 +25,43 @@ * always easy to grab with the mouse). */ #define MIN_SLIDER_LENGTH 5 -/*Borrowed from ttkMacOSXTheme.c to provide appropriate scaling of scrollbar values.*/ +/*Borrowed from ttkMacOSXTheme.c to provide appropriate scaling.*/ #ifdef __LP64__ #define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) #else #define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) #endif /* __LP64__ */ -#define MOUNTAIN_LION_STYLE (NSAppKitVersionNumber < 1138) +/* + * Apple reversed the scroll direction with the release of OSX 10.7 Lion. + */ + +#define SNOW_LEOPARD_STYLE (NSAppKitVersionNumber < 1138) /* - * Declaration of Mac specific scrollbar structure. + * Declaration of an extended scrollbar structure with Mac specific additions. */ typedef struct MacScrollbar { - TkScrollbar information; /* Generic scrollbar info. */ + TkScrollbar information; /* Generic scrollbar info. */ GC troughGC; /* For drawing trough. */ GC copyGC; /* Used for copying from pixmap onto screen. */ + Bool buttonDown; /* Is the mouse button down? */ + Bool mouseOver; /* Is the pointer over the scrollbar. */ + HIThemeTrackDrawInfo info; /* Controls how the scrollbar is drawn. */ } MacScrollbar; + +/* Used to initialize a MacScrollbar's info field. */ +HIThemeTrackDrawInfo defaultInfo = { + .version = 0, + .min = 0.0, + .max = 100.0, + .attributes = kThemeTrackShowThumb, +}; /* * The class procedure table for the scrollbar widget. All fields except size * are left initialized to NULL, which should happen automatically since the * variable is declared at this scope. @@ -57,11 +73,11 @@ NULL, /* createProc */ NULL /* modalProc */ }; -/*Information on scrollbar layout, metrics, and draw info.*/ +/* Information on scrollbar layout, metrics, and draw info.*/ typedef struct ScrollbarMetrics { SInt32 width, minThumbHeight; int minHeight, topArrowHeight, bottomArrowHeight; NSControlSize controlSize; } ScrollbarMetrics; @@ -69,24 +85,17 @@ static ScrollbarMetrics metrics = { 15, 54, 26, 14, 14, kControlSizeNormal /* kThemeScrollBarMedium */ }; -HIThemeTrackDrawInfo info = { - .version = 0, - .min = 0.0, - .max = 100.0, - .attributes = kThemeTrackShowThumb, -}; - /* - * Forward declarations for procedures defined later in this file: + * Declarations of static functions defined later in this file: */ static void ScrollbarEventProc(ClientData clientData, XEvent *eventPtr); -static int ScrollbarPress(TkScrollbar *scrollPtr, XEvent *eventPtr); +static int ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr); static void UpdateControlValues(TkScrollbar *scrollPtr); /* *---------------------------------------------------------------------- * @@ -110,12 +119,23 @@ MacScrollbar *scrollPtr = (MacScrollbar *)ckalloc(sizeof(MacScrollbar)); scrollPtr->troughGC = None; scrollPtr->copyGC = None; - - Tk_CreateEventHandler(tkwin,ExposureMask|StructureNotifyMask|FocusChangeMask|ButtonPressMask|ButtonReleaseMask|EnterWindowMask|LeaveWindowMask|VisibilityChangeMask, ScrollbarEventProc, scrollPtr); + scrollPtr->info = defaultInfo; + scrollPtr->buttonDown = false; + + Tk_CreateEventHandler(tkwin, + ExposureMask | + StructureNotifyMask | + FocusChangeMask | + ButtonPressMask | + ButtonReleaseMask | + EnterWindowMask | + LeaveWindowMask | + VisibilityChangeMask, + ScrollbarEventProc, scrollPtr); return (TkScrollbar *) scrollPtr; } /* @@ -129,20 +149,21 @@ * * Results: * None. * * Side effects: - * Information appears on the screen. + * Draws a scrollbar on the screen. * *-------------------------------------------------------------- */ void TkpDisplayScrollbar( ClientData clientData) /* Information about window. */ { register TkScrollbar *scrollPtr = (TkScrollbar *) clientData; + MacScrollbar *msPtr = (MacScrollbar *) scrollPtr; register Tk_Window tkwin = scrollPtr->tkwin; TkWindow *winPtr = (TkWindow *) tkwin; TkMacOSXDrawingContext dc; scrollPtr->flags &= ~REDRAW_PENDING; @@ -162,11 +183,11 @@ CGFloat viewHeight = [view bounds].size.height; CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, .ty = viewHeight}; CGContextConcatCTM(dc.context, t); - /*Draw Unix-style scroll trough to provide rect for native scrollbar.*/ + /*Draw a 3D rectangle to provide a base for the native scrollbar.*/ if (scrollPtr->highlightWidth != 0) { GC fgGC, bgGC; bgGC = Tk_GCForColor(scrollPtr->highlightBgColorPtr, (Pixmap) macWin); if (scrollPtr->flags & GOT_FOCUS) { @@ -186,16 +207,17 @@ Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder, scrollPtr->inset, scrollPtr->inset, Tk_Width(tkwin) - 2*scrollPtr->inset, Tk_Height(tkwin) - 2*scrollPtr->inset, 0, TK_RELIEF_FLAT); - /*Update values and draw in native rect.*/ + /* Update values and then draw the native scrollbar over the rectangle.*/ UpdateControlValues(scrollPtr); - if (MOUNTAIN_LION_STYLE) { - HIThemeDrawTrack (&info, 0, dc.context, kHIThemeOrientationInverted); + + if (SNOW_LEOPARD_STYLE) { + HIThemeDrawTrack (&(msPtr->info), 0, dc.context, kHIThemeOrientationInverted); } else { - HIThemeDrawTrack (&info, 0, dc.context, kHIThemeOrientationNormal); + HIThemeDrawTrack (&(msPtr->info), 0, dc.context, kHIThemeOrientationNormal); } TkMacOSXRestoreDrawingContext(&dc); scrollPtr->flags &= ~REDRAW_PENDING; } @@ -226,25 +248,33 @@ /* Scrollbar whose geometry may have * changed. */ { /* - * Using code from tkUnixScrlbr.c because Unix scroll bindings are - * driving the display at the script level. All the Mac scrollbar - * has to do is re-draw itself. - * There is a difference with Unix however: on macOS later than 10.6 - * (Snow Leopard) the scrollbars have no arrows at all. This is - * handled by having scrollPtr->arrowLength set to zero. + * The code below is borrowed from tkUnixScrlbr.c but has been adjusted to + * account for some differences between macOS and X11. The Unix scrollbar + * has an arrow button on each end. On macOS 10.6 (Snow Leopard) the + * scrollbars by default have both arrow buttons at the bottom or right. + * (There is a preferences setting to use the Unix layout, but we are not + * supporting that!) On more recent versions of macOS there are no arrow + * buttons at all. The case of no arrow buttons can be handled as a special + * case of having both buttons at the end, but where scrollPtr->arrowLength + * happens to be zero. To adjust for having both arrows at the same end we + * shift the scrollbar up by the arrowLength. */ int fieldLength; if (scrollPtr->highlightWidth < 0) { scrollPtr->highlightWidth = 0; } scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth; - scrollPtr->arrowLength = 0; + if ([NSApp macMinorVersion] == 6) { + scrollPtr->arrowLength = scrollPtr->width; + } else { + scrollPtr->arrowLength = 0; + } fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin) : Tk_Width(scrollPtr->tkwin)) - 2*(scrollPtr->arrowLength + scrollPtr->inset); if (fieldLength < 0) { fieldLength = 0; @@ -268,17 +298,18 @@ scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH; } if (scrollPtr->sliderLast > fieldLength) { scrollPtr->sliderLast = fieldLength; } - scrollPtr->sliderFirst += scrollPtr->arrowLength + scrollPtr->inset; - scrollPtr->sliderLast += scrollPtr->arrowLength + scrollPtr->inset; + scrollPtr->sliderFirst += -scrollPtr->arrowLength + scrollPtr->inset; + scrollPtr->sliderLast += scrollPtr->inset; /* - * Register the desired geometry for the window (leave enough space for - * the two arrows plus a minimum-size slider, plus border around the whole - * window, if any). Then arrange for the window to be redisplayed. + * Register the desired geometry for the window. Leave enough space for the + * two arrows, if there are any arrows, plus a minimum-size slider, plus + * border around the whole window, if any. Then arrange for the window to + * be redisplayed. */ if (scrollPtr->vertical) { Tk_GeometryRequest(scrollPtr->tkwin, scrollPtr->width + 2*scrollPtr->inset, @@ -319,37 +350,33 @@ Tk_FreeGC(scrollPtr->display, macScrollPtr->troughGC); } if (macScrollPtr->copyGC != None) { Tk_FreeGC(scrollPtr->display, macScrollPtr->copyGC); } - - macScrollPtr=NULL; } /* *---------------------------------------------------------------------- * * TkpConfigureScrollbar -- * * This procedure is called after the generic code has finished * processing configuration options, in order to configure platform - * specific options. + * specific options. There are no such option on the Mac, however. * * Results: * None. * * Side effects: - * Configuration info may get changed. + * Currently, none. * *---------------------------------------------------------------------- */ void TkpConfigureScrollbar( register TkScrollbar *scrollPtr) -/* Information about widget; may or may not - * already have values for some fields. */ { } /* @@ -376,13 +403,12 @@ /* Scrollbar widget record. */ int x, int y) /* Coordinates within scrollPtr's window. */ { /* - * Using code from tkUnixScrlbr.c because Unix scroll bindings are - * driving the display at the script level. All the Mac scrollbar - * has to do is re-draw itself. + * The code below is borrowed from tkUnixScrlbr.c and needs no adjustment + * since it does not involve the arrow buttons. */ int length, width, tmp; register const int inset = scrollPtr->inset; @@ -395,34 +421,36 @@ y = tmp; length = Tk_Width(scrollPtr->tkwin); width = Tk_Height(scrollPtr->tkwin); } - if (x=width-inset || y=length-inset) { + if (x < inset || x >= width - inset || + y < inset || y >= length - inset) { return OUTSIDE; } /* - * All of the calculations in this procedure mirror those in - * TkpDisplayScrollbar. Be sure to keep the two consistent. + * Here we assume that the scrollbar is layed out with both arrow buttons + * at the bottom (or right). Except on 10.6, however, the arrows do not + * actually exist, i.e. the arrowLength is 0. These are the same + * assumptions which are being made in TkpComputeScrollbarGeometry. */ - if (y < inset + scrollPtr->arrowLength) { - return TOP_ARROW; - } - if (y < scrollPtr->sliderFirst) { + if (y < scrollPtr->sliderFirst + scrollPtr->arrowLength) { return TOP_GAP; - } - if (y < scrollPtr->sliderLast) { + } + if (y < scrollPtr->sliderLast) { return SLIDER; - } - if (y >= length - (scrollPtr->arrowLength + inset)) { - return BOTTOM_ARROW; - } - - return BOTTOM_GAP; - + } + if (y < length - (2*scrollPtr->arrowLength + inset)) { + return BOTTOM_GAP; + } + /* On systems newer than 10.6 we have already returned. */ + if (y < length - (scrollPtr->arrowLength + inset)) { + return TOP_ARROW; + } + return BOTTOM_ARROW; } /* *-------------------------------------------------------------- * @@ -445,10 +473,11 @@ static void UpdateControlValues( TkScrollbar *scrollPtr) /* Scrollbar data struct. */ { + MacScrollbar *msPtr = (MacScrollbar *)scrollPtr; Tk_Window tkwin = scrollPtr->tkwin; MacDrawable *macWin = (MacDrawable *) Tk_WindowId(scrollPtr->tkwin); double dViewSize; HIRect contrlRect; short width, height; @@ -460,25 +489,25 @@ Tk_Height(tkwin)); frame = NSInsetRect(frame, scrollPtr->inset, scrollPtr->inset); frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); contrlRect = NSRectToCGRect(frame); - info.bounds = contrlRect; + msPtr->info.bounds = contrlRect; width = contrlRect.size.width; height = contrlRect.size.height; /* * Ensure we set scrollbar control bounds only once all size adjustments * have been computed. */ - info.bounds = contrlRect; + msPtr->info.bounds = contrlRect; if (scrollPtr->vertical) { - info.attributes &= ~kThemeTrackHorizontal; + msPtr->info.attributes &= ~kThemeTrackHorizontal; } else { - info.attributes |= kThemeTrackHorizontal; + msPtr->info.attributes |= kThemeTrackHorizontal; } /* * Given the Tk parameters for the fractions of the start and end of the * thumb, the following calculation determines the location for the @@ -491,59 +520,109 @@ double maximum = 100, factor; factor = RangeToFactor(maximum); dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction) * factor; - info.max = MIN_SCROLLBAR_VALUE + + msPtr->info.max = MIN_SCROLLBAR_VALUE + factor - dViewSize; - info.trackInfo.scrollbar.viewsize = dViewSize; + msPtr->info.trackInfo.scrollbar.viewsize = dViewSize; if (scrollPtr->vertical) { - if (MOUNTAIN_LION_STYLE) { - info.value = factor * scrollPtr->firstFraction; + if (SNOW_LEOPARD_STYLE) { + msPtr->info.value = factor * scrollPtr->firstFraction; } else { - info.value = info.max - factor * scrollPtr->firstFraction; + msPtr->info.value = msPtr->info.max - factor * scrollPtr->firstFraction; } } else { - info.value = MIN_SCROLLBAR_VALUE + factor * scrollPtr->firstFraction; + msPtr->info.value = MIN_SCROLLBAR_VALUE + factor * scrollPtr->firstFraction; } if((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0) || height <= metrics.minHeight) { - info.enableState = kThemeTrackHideTrack; + msPtr->info.enableState = kThemeTrackHideTrack; } else { - info.enableState = kThemeTrackActive; - info.attributes = kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost; + msPtr->info.enableState = kThemeTrackActive; + msPtr->info.attributes = kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost; } } /* *-------------------------------------------------------------- * - * ScrollbarPress -- + * ScrollbarEvent -- * * This procedure is invoked in response to , , - * , and events. Scrollbar appearance is modified. + * , and events. The Scrollbar appearance is + * modified for each event. * *-------------------------------------------------------------- */ static int -ScrollbarPress(TkScrollbar *scrollPtr, XEvent *eventPtr) -{ - - if (eventPtr->type == ButtonPress) { - UpdateControlValues(scrollPtr); - info.trackInfo.scrollbar.pressState = 1; - } - if (eventPtr->type == EnterNotify) { - info.trackInfo.scrollbar.pressState = 1; - } - if (eventPtr->type == ButtonRelease || eventPtr->type == LeaveNotify) { - info.trackInfo.scrollbar.pressState = 0; - } - return TCL_OK; +ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr) +{ + MacScrollbar *msPtr = (MacScrollbar *)scrollPtr; + + /* The pressState does not indicate whether the moused button was + * pressed at some location in the Scrollbar. Rather, it indicates + * that the scrollbar should appear as if it were pressed in that + * location. The standard Mac behavior is that once the button is + * pressed inside the Scrollbar the appearance should not change until + * the button is released, even if the mouse moves outside of the + * scrollbar. However, if the mouse lies over the scrollbar but the + * button is not pressed then the appearance should be the same as if + * the button had been pressed on the slider, i.e. kThemeThumbPressed. + * See the file Appearance.r, or HIToolbox.bridgesupport on 10.14. + */ + + if (eventPtr->type == ButtonPress) { + msPtr->buttonDown = true; + UpdateControlValues(scrollPtr); + int where = TkpScrollbarPosition(scrollPtr, + eventPtr->xbutton.x, + eventPtr->xbutton.y); + switch(where) { + case OUTSIDE: + msPtr->info.trackInfo.scrollbar.pressState = 0; + break; + case TOP_GAP: + msPtr->info.trackInfo.scrollbar.pressState = kThemeTopTrackPressed; + break; + case SLIDER: + msPtr->info.trackInfo.scrollbar.pressState = kThemeThumbPressed; + break; + case BOTTOM_GAP: + msPtr->info.trackInfo.scrollbar.pressState = kThemeBottomTrackPressed; + break; + case TOP_ARROW: + /* This looks wrong and the docs say it is wrong but it works. */ + msPtr->info.trackInfo.scrollbar.pressState = kThemeTopInsideArrowPressed; + break; + case BOTTOM_ARROW: + msPtr->info.trackInfo.scrollbar.pressState = kThemeBottomOutsideArrowPressed; + break; + } + } + if (eventPtr->type == ButtonRelease) { + msPtr->buttonDown = false; + if (!msPtr->mouseOver) { + msPtr->info.trackInfo.scrollbar.pressState = 0; + } + } + if (eventPtr->type == EnterNotify) { + msPtr->mouseOver = true; + if (!msPtr->buttonDown) { + msPtr->info.trackInfo.scrollbar.pressState = kThemeThumbPressed; + } + } + if (eventPtr->type == LeaveNotify) { + msPtr->mouseOver = false; + if (!msPtr->buttonDown) { + msPtr->info.trackInfo.scrollbar.pressState = 0; + } + } + return TCL_OK; } /* @@ -581,11 +660,20 @@ break; case ButtonPress: case ButtonRelease: case EnterNotify: case LeaveNotify: - ScrollbarPress(clientData, eventPtr); + ScrollbarEvent(clientData, eventPtr); break; default: TkScrollbarEventProc(clientData, eventPtr); } } + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ Index: macosx/tkMacOSXWindowEvent.c ================================================================== --- macosx/tkMacOSXWindowEvent.c +++ macosx/tkMacOSXWindowEvent.c @@ -295,10 +295,35 @@ #pragma mark - /* *---------------------------------------------------------------------- * + * TkpAppIsDrawing -- + * + * A widget display procedure can call this to determine whether it + * is being run inside of the drawRect method. This is needed for + * some tests, especially of the Text widget, which record data in + * a global Tcl variable and assume that display procedures will be + * run in a predictable sequence as Tcl idle tasks. + * + * Results: + * True only while running the drawRect method of a TKContentView; + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ +MODULE_SCOPE Bool +TkpAppIsDrawing(void) { + return [NSApp isDrawing]; +} + + +/* + *---------------------------------------------------------------------- + * * GenerateUpdates -- * * Given a Macintosh update region and a Tk window this function geneates * an X Expose event for the window if it meets the update region. The * function will then recursivly have each damaged window generate Expose @@ -799,38 +824,48 @@ - (void) drawRect: (NSRect) rect { const NSRect *rectsBeingDrawn; NSInteger rectsBeingDrawnCount; - [self getRectsBeingDrawn:&rectsBeingDrawn count:&rectsBeingDrawnCount]; - #ifdef TK_MAC_DEBUG_DRAWING - TKLog(@"-[%@(%p) %s%@]", [self class], self, _cmd, NSStringFromRect(rect)); - [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:.1] setFill]; - NSRectFillListUsingOperation(rectsBeingDrawn, rectsBeingDrawnCount, - NSCompositeSourceOver); + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + if (winPtr) printf("drawRect: drawing %s\n", Tk_PathName(winPtr)); #endif - + + [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 + printf("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]; + +#ifdef TK_MAC_DEBUG_DRAWING + printf("drawRect: done.\n"); +#endif + } -(void) setFrameSize: (NSSize)newsize { [super setFrameSize: newsize]; - if ([self inLiveResize]) { - NSWindow *w = [self window]; - TkWindow *winPtr = TkMacOSXGetTkWindow(w); - Tk_Window tkwin = (Tk_Window) winPtr; + NSWindow *w = [self window]; + TkWindow *winPtr = TkMacOSXGetTkWindow(w); + Tk_Window tkwin = (Tk_Window) winPtr; + if (winPtr) { unsigned int width = (unsigned int)newsize.width; unsigned int height=(unsigned int)newsize.height; ClientData oldArg; Tk_RestrictProc *oldProc; @@ -888,13 +923,14 @@ { unsigned long serial; CGRect updateBounds; int updatesNeeded; TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); - + ClientData oldArg; + Tk_RestrictProc *oldProc; if (!winPtr) { - return; + return; } /* * Generate Tk Expose events. */ @@ -912,13 +948,11 @@ /* * First process all of the Expose events. */ - ClientData oldArg; - Tk_RestrictProc *oldProc = Tk_RestrictEvents(ExposeRestrictProc, - UINT2PTR(serial), &oldArg); + oldProc = Tk_RestrictEvents(ExposeRestrictProc, UINT2PTR(serial), &oldArg); while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {}; Tk_RestrictEvents(oldProc, oldArg, &oldArg); /* * Starting with OSX 10.14, which uses Core Animation to draw windows, @@ -928,14 +962,16 @@ * with the current CGContext outside of the drawRect method has no * 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. + * were created when the expose events were processed. Unfortunately + * this does not work in macOS 10.13. */ - - while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {} + if ([NSApp macMinorVersion] > 13 || [self inLiveResize]) { + while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {} + } } } /* Index: macosx/tkMacOSXWm.c ================================================================== --- macosx/tkMacOSXWm.c +++ macosx/tkMacOSXWm.c @@ -202,28 +202,28 @@ * Conversion of coordinates between window and screen. */ @implementation NSWindow(TKWm) #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 -- (NSPoint) convertPointToScreen: (NSPoint) point +- (NSPoint) tkConvertPointToScreen: (NSPoint) point { return [self convertBaseToScreen:point]; } -- (NSPoint) convertPointFromScreen: (NSPoint)point +- (NSPoint) tkConvertPointFromScreen: (NSPoint)point { return [self convertScreenToBase:point]; } #else -- (NSPoint) convertPointToScreen: (NSPoint) point +- (NSPoint) tkConvertPointToScreen: (NSPoint) point { NSRect pointrect; pointrect.origin = point; pointrect.size.width = 0; pointrect.size.height = 0; return [self convertRectToScreen:pointrect].origin; } -- (NSPoint) convertPointFromScreen: (NSPoint)point +- (NSPoint) tkConvertPointFromScreen: (NSPoint)point { NSRect pointrect; pointrect.origin = point; pointrect.size.width = 0; pointrect.size.height = 0; @@ -385,41 +385,47 @@ } @end @implementation TKWindow: NSWindow -#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_12 -/* - * Override automatic fullscreen button on >10.12 because system fullscreen API - * confuses Tk window geometry. Custom implementation setting fullscreen status using - * Tk API and NSStatusItem in menubar to exit fullscreen status. - */ + +/* Custom fullscreen implementation on 10.13 and above. On older versions of + * macOS dating back to 10.7, the NSWindow fullscreen API was opt-in, requiring + * explicit calls to toggleFullScreen. On 10.13, the API became implicit, + * applying to all NSWindows unless they were marked non-resizable; this caused + * issues with Tk, which was not aware of changes in screen geometry. Here we + * override the toggleFullScreen call to hook directly into Tk's own fullscreen + * API, allowing Tk to function smoothly with the Mac's fullscreen button. +*/ NSStatusItem *exitFullScreen; + - (void)toggleFullScreen:(id)sender { - TkWindow *winPtr = TkMacOSXGetTkWindow(self); - Tcl_Interp *interp = Tk_Interp((Tk_Window)winPtr); - - if (([self styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask) { - TkMacOSXMakeFullscreen(winPtr, self, 0, interp); - } else { - TkMacOSXMakeFullscreen(winPtr, self, 1, interp); - } + TkWindow *winPtr = TkMacOSXGetTkWindow(self); + Tcl_Interp *interp = Tk_Interp((Tk_Window)winPtr); + if ([NSApp macMinorVersion] > 12) { + if (([self styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask) { + TkMacOSXMakeFullscreen(winPtr, self, 0, interp); + } else { + TkMacOSXMakeFullscreen(winPtr, self, 1, interp); + } + } else { + NSLog (@"toggleFullScreen is ignored by Tk on OSX versions < 10.13"); + } } -(void)restoreOldScreen:(id)sender { - TkWindow *winPtr = TkMacOSXGetTkWindow(self); - Tcl_Interp *interp = Tk_Interp((Tk_Window)winPtr); + TkWindow *winPtr = TkMacOSXGetTkWindow(self); + Tcl_Interp *interp = Tk_Interp((Tk_Window)winPtr); - TkMacOSXMakeFullscreen(winPtr, self, 0, interp); - [[NSStatusBar systemStatusBar] removeStatusItem: exitFullScreen]; + TkMacOSXMakeFullscreen(winPtr, self, 0, interp); + [[NSStatusBar systemStatusBar] removeStatusItem: exitFullScreen]; } -#endif @end @implementation TKWindow(TKWm) - (BOOL) canBecomeKeyWindow @@ -804,11 +810,10 @@ XMapWindow(winPtr->display, winPtr->window); /*Add window to Window menu.*/ NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); [win setExcludedFromWindowsMenu:NO]; - } /* *---------------------------------------------------------------------- * @@ -5647,11 +5652,10 @@ [window setContentView:contentView]; [contentView release]; [window setDelegate:NSApp]; [window setAcceptsMouseMovedEvents:YES]; [window setReleasedWhenClosed:NO]; - [window setAutodisplay:NO]; if (styleMask & NSUtilityWindowMask) { [(NSPanel*)window setFloatingPanel:YES]; } if ((styleMask & (NSTexturedBackgroundWindowMask|NSHUDWindowMask)) && !(styleMask & NSDocModalWindowMask)) { @@ -5674,10 +5678,37 @@ geometry.size.height); [window setFrame:geometry display:NO]; TkMacOSXRegisterOffScreenWindow((Window) macWin, window); macWin->flags |= TK_HOST_EXISTS; } + +/* + *---------------------------------------------------------------------- + * + * TkpDisplayWindow -- + * + * Mark the contentView of this window as needing display so the + * window will be drawn by the window manager. If this is called + * within the drawRect method, do nothing. + * + * Results: + * None. + * + * Side effects: + * The window's contentView is marked as needing display. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE void +TkpDisplayWindow(Tk_Window tkwin) { + if (![NSApp isDrawing]) { + TkWindow *winPtr = (TkWindow*)tkwin; + NSWindow *w = TkMacOSXDrawableWindow(winPtr->window); + [[w contentView] setNeedsDisplay: YES]; + } +} /* *---------------------------------------------------------------------- * * TkMacOSXRegisterOffScreenWindow -- @@ -6489,17 +6520,14 @@ NSWindow *window, int fullscreen, Tcl_Interp *interp) { WmInfo *wmPtr = winPtr->wmInfoPtr; - int result = TCL_OK, wasFullscreen = (wmPtr->flags & WM_FULLSCREEN); - static unsigned long prevMask = 0, prevPres = 0; - + int screenWidth = WidthOfScreen(Tk_Screen(winPtr)); + int screenHeight = HeightOfScreen(Tk_Screen(winPtr)); if (fullscreen) { - int screenWidth = WidthOfScreen(Tk_Screen(winPtr)); - int screenHeight = HeightOfScreen(Tk_Screen(winPtr)); /* * Check max width and height if set by the user. */ @@ -6510,74 +6538,77 @@ "can't set fullscreen attribute for \"%s\": max" " width/height is too small", winPtr->pathName)); Tcl_SetErrorCode(interp, "TK", "FULLSCREEN", "CONSTRAINT_FAILURE", NULL); } - result = TCL_ERROR; wmPtr->flags &= ~WM_FULLSCREEN; - } else { - Tk_UnmapWindow((Tk_Window) winPtr); - NSRect bounds = [window contentRectForFrameRect:[window frame]]; - NSRect screenBounds = NSMakeRect(0, 0, screenWidth, screenHeight); - - if (!NSEqualRects(bounds, screenBounds) && !wasFullscreen) { - wmPtr->configX = wmPtr->x; - wmPtr->configY = wmPtr->y; - wmPtr->configAttributes = wmPtr->attributes; - wmPtr->attributes &= ~kWindowResizableAttribute; - ApplyWindowAttributeFlagChanges(winPtr, window, - wmPtr->configAttributes, wmPtr->flags, 1, 0); - wmPtr->flags |= WM_SYNC_PENDING; - [window setFrame:[window frameRectForContentRect: - screenBounds] display:YES]; - wmPtr->flags &= ~WM_SYNC_PENDING; - } - wmPtr->flags |= WM_FULLSCREEN; - } - - prevMask = [window styleMask]; - prevPres = [NSApp presentationOptions]; + return TCL_ERROR; + } + + /* + * Save the current window state. + */ + + wmPtr->cachedBounds = [window frame]; + wmPtr->cachedStyle = [window styleMask]; + wmPtr->cachedPresentation = [NSApp presentationOptions]; + + /* + * Adjust the window style so it looks like a Fullscreen window. + */ + [window setStyleMask: NSFullScreenWindowMask]; - [NSApp setPresentationOptions: NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar]; + [NSApp setPresentationOptions: (NSApplicationPresentationAutoHideDock | + NSApplicationPresentationAutoHideMenuBar)]; - /*Fullscreen implementation for 10.13 and later.*/ - #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_12 - exitFullScreen = [[[NSStatusBar systemStatusBar] + /*For 10.13 and later add a button for exiting Fullscreen.*/ + if ([NSApp macMinorVersion] > 12) { +#if MAC_OS_X_VERSION_MAX_ALLOWED > 101200 + exitFullScreen = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain]; - NSImage *exitIcon = [NSImage imageNamed:@"NSExitFullScreenTemplate"]; - [exitFullScreen setImage:exitIcon]; - [exitFullScreen setHighlightMode:YES]; - [exitFullScreen setToolTip:@"Exit Full Screen"]; - [exitFullScreen setTarget:window]; - [exitFullScreen setAction:@selector(restoreOldScreen:)]; - #endif - - Tk_MapWindow((Tk_Window) winPtr); + NSImage *exitIcon = [NSImage imageNamed:@"NSExitFullScreenTemplate"]; + exitFullScreen.button.image = exitIcon; + exitFullScreen.button.cell.highlighted = NO; + exitFullScreen.button.toolTip = @"Exit Full Screen"; + exitFullScreen.button.target = window; + exitFullScreen.button.action = @selector(restoreOldScreen:); +#endif + } + + /* + * Resize the window to fill the screen. (After setting the style!) + */ + + wmPtr->flags |= WM_SYNC_PENDING; + NSRect screenBounds = NSMakeRect(0, 0, screenWidth, screenHeight); + [window setFrame:screenBounds display:YES]; + wmPtr->flags &= ~WM_SYNC_PENDING; + wmPtr->flags |= WM_FULLSCREEN; } else { - wmPtr->flags &= ~WM_FULLSCREEN; - [NSApp setPresentationOptions: prevPres]; - [window setStyleMask: prevMask]; - } + + /* + * Restore the previous styles and attributes. + */ - if (wasFullscreen && !(wmPtr->flags & WM_FULLSCREEN)) { - Tk_UnmapWindow((Tk_Window) winPtr); + [NSApp setPresentationOptions: wmPtr->cachedPresentation]; + [window setStyleMask: wmPtr->cachedStyle]; UInt64 oldAttributes = wmPtr->attributes; - NSRect bounds = NSMakeRect(wmPtr->configX, tkMacOSXZeroScreenHeight - - (wmPtr->configY + wmPtr->yInParent + wmPtr->configHeight), - wmPtr->xInParent + wmPtr->configWidth, - wmPtr->yInParent + wmPtr->configHeight); - + wmPtr->flags &= ~WM_FULLSCREEN; wmPtr->attributes |= wmPtr->configAttributes & kWindowResizableAttribute; ApplyWindowAttributeFlagChanges(winPtr, window, oldAttributes, wmPtr->flags, 1, 0); + + /* + * Resize the window to its previous size. + */ + wmPtr->flags |= WM_SYNC_PENDING; - [window setFrame:[window frameRectForContentRect:bounds] display:YES]; + [window setFrame:wmPtr->cachedBounds display:YES]; wmPtr->flags &= ~WM_SYNC_PENDING; - Tk_MapWindow((Tk_Window) winPtr); } - return result; + return TCL_OK; } /* *---------------------------------------------------------------------- * Index: macosx/tkMacOSXWm.h ================================================================== --- macosx/tkMacOSXWm.h +++ macosx/tkMacOSXWm.h @@ -183,10 +183,19 @@ WindowClass macClass; UInt64 attributes, configAttributes; TkWindow *scrollWinPtr; /* Ptr to scrollbar handling grow widget. */ TkMenu *menuPtr; NSWindow *window; + + /* + * Space to cache current window state when window becomes Fullscreen. + */ + + unsigned long cachedStyle; + unsigned long cachedPresentation; + NSRect cachedBounds; + } WmInfo; /* * Flag values for WmInfo structures: * Index: macosx/tkMacOSXXStubs.c ================================================================== --- macosx/tkMacOSXXStubs.c +++ macosx/tkMacOSXXStubs.c @@ -176,11 +176,11 @@ } display->vendor = vendor; { int major, minor, patch; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1080 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 Gestalt(gestaltSystemVersionMajor, (SInt32*)&major); Gestalt(gestaltSystemVersionMinor, (SInt32*)&minor); Gestalt(gestaltSystemVersionBugFix, (SInt32*)&patch); #else NSOperatingSystemVersion systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; Index: tests/bind.test ================================================================== --- tests/bind.test +++ tests/bind.test @@ -37,14 +37,14 @@ # This function fills the pattern matcher's ring buffer with events of # the specified type. This can be used when testing with generated # events to make sure that there are no stray events in the ring # buffer which might cause the pattern matcher to find unintended # matches. The size of the ring buffer is EVENT_BUFFER_SIZE, which is -# currently set to 30. If this changes, the code below will need to -# change. +# currently set to 30 (or 45 on macOS). If this changes, the code +# below will need to change. proc clearRingBuffer {{event}} { - for {set i 0} {$i < 30} {incr i} { + for {set i 0} {$i < 45} {incr i} { event generate . $event } } # move the mouse pointer away of the testing area