diff options
author | culler <culler> | 2018-11-30 22:11:48 (GMT) |
---|---|---|
committer | culler <culler> | 2018-11-30 22:11:48 (GMT) |
commit | dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0 (patch) | |
tree | da98509c5fc6e022f17a7d355153b31cbbac4ae3 /macosx | |
parent | 2f18a3f080b8f55b2b4be4a90e948531f203eb7d (diff) | |
download | tk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.zip tk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.tar.gz tk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.tar.bz2 |
Make tabbed windows work correctly on macOS.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/README | 17 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyEvent.c | 18 | ||||
-rw-r--r-- | macosx/tkMacOSXMenu.c | 66 | ||||
-rw-r--r-- | macosx/tkMacOSXMenus.c | 48 | ||||
-rw-r--r-- | macosx/tkMacOSXNotify.c | 7 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 87 |
6 files changed, 198 insertions, 45 deletions
diff --git a/macosx/README b/macosx/README index c63b8ae..3da5c75 100644 --- a/macosx/README +++ b/macosx/README @@ -185,7 +185,22 @@ Support for the 3 argument form was added with the Cocoa-based Tk 8.5.7, at the same time support for some legacy Carbon-specific classes and attributes was removed (they are still accepted by the command but no longer have any effect). -If you want to use Remote Debugging with Xcode, you need to set the +- Another command available in the tk::unsupported::MacWindowStyle namespace is +tk::unsupported::MacWindowStyle tabbingid window ?newId? which can be used to +get or set the tabbingIdentifier for the NSWindow associated with a Tk Window. +(This is only available for OSX 10.12 and later, since the tabbingIdentifier +does not exist on earlier systems.) The command returns the tabbingIdentifier +of the window prior to calling this function. If the optional newId argument +is omitted, the window's tabbingIdentifier is not changed. + +The purpose of the tabbingIdentifier is that NSWindows may only be grouped +together as tabs if they all have the same tabbingIdentifier. In particular, +by giving a window a unique tabbingIdentifier one can prevent it from becoming +a tab in any other window. Note, however, that changing the tabbingIdentifier +of a window which is already a tab does not cause it to become a separate +window. + +- 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. diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index 31fffa1..a153797 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -55,9 +55,11 @@ unsigned short releaseCode; #endif NSWindow* w; NSEventType type = [theEvent type]; - NSUInteger modifiers, len = 0; + NSUInteger modifiers = ([theEvent modifierFlags] & + NSDeviceIndependentModifierFlagsMask); + NSUInteger len = 0; BOOL repeat = NO; - unsigned short keyCode; + unsigned short keyCode = [theEvent keyCode]; NSString *characters = nil, *charactersIgnoringModifiers = nil; static NSUInteger savedModifiers = 0; static NSMutableArray *nsEvArray; @@ -77,6 +79,16 @@ unsigned short releaseCode; return theEvent; } + /* + * Control-Tab and Control-Shift-Tab are used to switch tabs in a tabbed + * window. We do not want to generate an Xevent for these since that might + * cause the deselected tab to be reactivated. + */ + + if (keyCode == 48 && (modifiers & NSControlKeyMask) == NSControlKeyMask) { + return theEvent; + } + switch (type) { case NSKeyUp: /*Fix for bug #1ba71a86bb: key release firing on key press.*/ @@ -90,8 +102,6 @@ unsigned short releaseCode; charactersIgnoringModifiers = [theEvent charactersIgnoringModifiers]; len = [charactersIgnoringModifiers length]; case NSFlagsChanged: - modifiers = [theEvent modifierFlags]; - keyCode = [theEvent keyCode]; #if defined(TK_MAC_DEBUG_EVENTS) || NS_KEYLOG == 1 TKLog(@"-[%@(%p) %s] r=%d mods=%u '%@' '%@' code=%u c=%d %@ %d", [self class], self, _cmd, repeat, modifiers, characters, charactersIgnoringModifiers, keyCode,([charactersIgnoringModifiers length] == 0) ? 0 : [charactersIgnoringModifiers characterAtIndex: 0], w, type); diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 590402a..ec32a78 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -227,14 +227,16 @@ static int ModifierCharWidth(Tk_Font tkfont); @end @implementation TKMenu(TKMenuActions) -// target methods - (BOOL) validateMenuItem: (NSMenuItem *) menuItem { return [menuItem isEnabled]; } -// Workaround for bug 3572016; leaves menu items enabled during modal dialog. +/* + * Workaround for bug 3572016; leave menu items enabled during modal dialog. + */ + - (BOOL)worksWhenModal { return YES; @@ -244,8 +246,8 @@ static int ModifierCharWidth(Tk_Font tkfont); { /* * With the delegate matching key equivalents, when a menu action is sent - * in response to a key equivalent, sender is the whole menu and not the - * the specific menu item, use this to ignore key equivalents for our + * in response to a key equivalent, the sender is the whole menu and not the + * specific menu item. We use this to ignore key equivalents for Tk * menus (as Tk handles them directly via bindings). */ @@ -256,10 +258,12 @@ static int ModifierCharWidth(Tk_Font tkfont); if (menuPtr && mePtr) { Tcl_Interp *interp = menuPtr->interp; + /*Add time for errors to fire if necessary. This is sub-optimal *but avoids issues with Tcl/Cocoa event loop integration. */ - Tcl_Sleep(100); + + //Tcl_Sleep(100); Tcl_Preserve(interp); Tcl_Preserve(menuPtr); @@ -278,40 +282,56 @@ static int ModifierCharWidth(Tk_Font tkfont); @end @implementation TKMenu(TKMenuDelegate) -#define keyEquivModifiersMatch(km, m) (( \ - ((km) & NSCommandKeyMask) != ((m) & NSCommandKeyMask) || \ - ((km) & NSAlternateKeyMask) != ((m) & NSAlternateKeyMask) || \ - ((km) & NSControlKeyMask) != ((m) & NSControlKeyMask) || \ - (((km) & NSShiftKeyMask) != ((m) & NSShiftKeyMask) && \ - ((m) & NSFunctionKeyMask))) ? NO : YES) - (BOOL) menuHasKeyEquivalent: (NSMenu *) menu forEvent: (NSEvent *) event target: (id *) target action: (SEL *) action { - /*Use lowercaseString to keep "shift" from firing twice if bound to different procedure.*/ + /* + * Use lowercaseString when comparing keyEquivalents since the notion of + * a shifted upper case letter does not make much sense. + */ + NSString *key = [[event charactersIgnoringModifiers] lowercaseString]; NSUInteger modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; if (modifiers == (NSCommandKeyMask | NSShiftKeyMask) && [key compare:@"?"] == NSOrderedSame) { - return NO; - } + /* + * Command-Shift-? has not been allowed as a keyboard equivalent since + * the first aqua port, for some mysterious reason. + */ - // For command key, take input manager's word so things - // like dvorak / qwerty layout work. - if (([event modifierFlags] & NSCommandKeyMask) == NSCommandKeyMask) { - key = [event characters]; + return NO; + } else if (modifiers == (NSControlKeyMask | NSShiftKeyMask) && + [event keyCode] == 48) { + + /* Starting with OSX 10.12 Control-Tab and Control-Shift-Tab are used + * to select window tabs. But for some even more mysterious reason the + * Control-Shift-Tab event has character 0x19 = NSBackTabCharacter + * rather than 0x09 = NSTabCharacter. At the same time, the + * keyEquivalent must be \0x09 in order for it to be displayed + * correctly in the menu. This makes it impossible for the standard + * "Select Previous Tab" to work correctly, unless we intervene. + */ + + key = @"\t"; + } else if (([event modifierFlags] & NSCommandKeyMask) == NSCommandKeyMask) { + + /* + * If the command modifier is set, use the full character string so + * things like the dvorak / qwerty layout will work. + */ + + key = [event characters]; } NSArray *itemArray = [self itemArray]; - for (NSMenuItem *item in itemArray) { - if ([item isEnabled] && [[item keyEquivalent] compare:key] == - NSOrderedSame) { + if ([item isEnabled] && + [[item keyEquivalent] compare:key] == NSOrderedSame) { NSUInteger keyEquivModifiers = [item keyEquivalentModifierMask]; - - if (keyEquivModifiersMatch(keyEquivModifiers, modifiers)) { + if (keyEquivModifiers == modifiers) { *target = [item target]; *action = [item action]; return YES; diff --git a/macosx/tkMacOSXMenus.c b/macosx/tkMacOSXMenus.c index 15dbad4..5fd932f 100644 --- a/macosx/tkMacOSXMenus.c +++ b/macosx/tkMacOSXMenus.c @@ -17,6 +17,7 @@ static void GenerateEditEvent(const char *name); static Tcl_Obj * GetWidgetDemoPath(Tcl_Interp *interp); + #pragma mark TKApplication(TKMenus) @@ -97,19 +98,44 @@ static Tcl_Obj * GetWidgetDemoPath(Tcl_Interp *interp); target:nil], nil]]; - _defaultWindowsMenuItems = [[NSArray arrayWithObjects: - [NSMenuItem itemWithTitle:@"Minimize" - action:@selector(performMiniaturize:) target:nil - keyEquivalent:@"m"], - [NSMenuItem itemWithTitle:@"Zoom" action:@selector(performZoom:) - target:nil], - [NSMenuItem separatorItem], + _defaultWindowsMenuItems = [NSArray arrayWithObjects: + [NSMenuItem itemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) target:nil + keyEquivalent:@"m"], + [NSMenuItem itemWithTitle:@"Zoom" action:@selector(performZoom:) + target:nil], + nil]; + if ([NSApp macMinorVersion] > 11) { + _defaultWindowsMenuItems = [_defaultWindowsMenuItems + arrayByAddingObjectsFromArray: + [NSArray arrayWithObjects: + [NSMenuItem separatorItem], + [NSMenuItem itemWithTitle:@"Show Previous Tab" + action:@selector(selectPreviousTab:) + target:nil + keyEquivalent:@"\t" + keyEquivalentModifierMask: + NSControlKeyMask|NSShiftKeyMask], + [NSMenuItem itemWithTitle:@"Show Next Tab" + action:@selector(selectNextTab:) + target:nil + keyEquivalent:@"\t" + keyEquivalentModifierMask:NSControlKeyMask], + [NSMenuItem itemWithTitle:@"Move Tab To New Window" + action:@selector(moveTabToNewWindow:) + target:nil], + [NSMenuItem itemWithTitle:@"Merge All Windows" + action:@selector(mergeAllWindows:) + target:nil], + [NSMenuItem separatorItem], + nil]]; + } + _defaultWindowsMenuItems = [_defaultWindowsMenuItems arrayByAddingObject: [NSMenuItem itemWithTitle:@"Bring All to Front" - action:@selector(arrangeInFront:)], - nil] retain]; - + action:@selector(arrangeInFront:)]]; + [_defaultWindowsMenuItems retain]; TKMenu *windowsMenu = [TKMenu menuWithTitle:@"Window" menuItems: - _defaultWindowsMenuItems]; + _defaultWindowsMenuItems]; _defaultHelpMenuItems = [[NSArray arrayWithObjects: [NSMenuItem itemWithTitle: diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c index 8c01fe3..88e7c76 100644 --- a/macosx/tkMacOSXNotify.c +++ b/macosx/tkMacOSXNotify.c @@ -135,7 +135,9 @@ void DebugPrintQueue(void) */ /* - * Call super then check the pasteboard. + * Since the contentView is the first responder for a Tk Window, it is + * responsible for sending events up the responder chain. We also check + * the pasteboard here. */ - (void) sendEvent: (NSEvent *) theEvent { @@ -360,7 +362,7 @@ TkMacOSXEventsCheckProc( int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent]; Tcl_SetServiceMode(oldServiceMode); - if (processedEvent) { /* Should always be non-NULL. */ + if (processedEvent) { #ifdef TK_MAC_DEBUG_EVENTS TKLog(@" event: %@", currentEvent); #endif @@ -370,6 +372,7 @@ TkMacOSXEventsCheckProc( [NSApp sendEvent:currentEvent]; } } + } else { break; } diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 15742ee..97fd203 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -309,6 +309,8 @@ static int WmWithdrawCmd(Tk_Window tkwin, TkWindow *winPtr, static void WmUpdateGeom(WmInfo *wmPtr, TkWindow *winPtr); static int WmWinStyle(Tcl_Interp *interp, TkWindow *winPtr, int objc, Tcl_Obj *const objv[]); +static int WmWinTabbingId(Tcl_Interp *interp, TkWindow *winPtr, + int objc, Tcl_Obj *const objv[]); static void ApplyWindowAttributeFlagChanges(TkWindow *winPtr, NSWindow *macWindow, UInt64 oldAttributes, int oldFlags, int create, int initial); @@ -365,18 +367,27 @@ static void RemapWindows(TkWindow *winPtr, @implementation TKWindow(TKWm) +/* + * This method synchronizes Tk's understanding of the bounds of a contentView + * with the window's. It is needed because there are situations when the + * window manager can change the layout of an NSWindow without having been + * requested to do so by Tk. Examples are when a window goes FullScreen or + * shows a tab bar. NSWindow methods which involve such layout changes should + * be overridden or protected by methods which call this. + */ + - (void) tkLayoutChanged { TkWindow *winPtr = TkMacOSXGetTkWindow(self); if (winPtr) { NSRect frameRect; - + /* * This avoids including the title bar for full screen windows * but does include it for normal windows. */ - + if ([self styleMask] & NSFullScreenWindowMask) { frameRect = [NSWindow frameRectForContentRect:NSZeroRect styleMask:[self styleMask]]; @@ -5270,10 +5281,10 @@ TkUnsupported1ObjCmd( Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { - "style", NULL + "style", "tabbingid", NULL }; enum SubCmds { - TKMWS_STYLE + TKMWS_STYLE, TKMWS_TABID }; Tk_Window tkwin = clientData; TkWindow *winPtr; @@ -5307,6 +5318,17 @@ TkUnsupported1ObjCmd( return TCL_ERROR; } return WmWinStyle(interp, winPtr, objc, objv); + } else if (((enum SubCmds) index) == TKMWS_TABID) { + if ([NSApp macMinorVersion] < 12) { + Tcl_AddErrorInfo(interp, + "\n (TabbingIdentifiers only exist on OSX 10.12 or later)"); + return TCL_ERROR; + } + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "tabbingid window ?newid?"); + return TCL_ERROR; + } + return WmWinTabbingId(interp, winPtr, objc, objv); } /* won't be reached */ return TCL_ERROR; @@ -5515,6 +5537,63 @@ WmWinStyle( /* *---------------------------------------------------------------------- * + * WmWinTabbingId -- + * + * This procedure is invoked to process the + * "::tk::unsupported::MacWindowStyle tabbingid" subcommand. The command + * allows you to get or set the tabbingIdentifier for the NSWindow + * associated with a Tk Window. The syntax is: + * tk::unsupported::MacWindowStyle tabbingid window ?newId? + * + * Results: + * Returns the tabbingIdentifier of the window prior to calling this + * function. If the optional newId argument is omitted, the window's + * tabbingIdentifier is not changed. + * + * Side effects: + * Windows may only be grouped together as tabs if they all have the same + * tabbingIdentifier. In particular, by giving a window a unique + * tabbingIdentifier one can prevent it from becoming a tab in any other + * window. Note, however, that changing the tabbingIdentifier of a window + * which is already a tab does not cause it to become a separate window. + * + * + *---------------------------------------------------------------------- + */ + +static int +WmWinTabbingId( + Tcl_Interp *interp, /* Current interpreter. */ + TkWindow *winPtr, /* Window to be manipulated. */ + int objc, /* Number of arguments. */ + Tcl_Obj * const objv[]) /* Argument objects. */ +{ + Tcl_Obj *result = NULL; + NSString *idString; + NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); + if (win) { + idString = win.tabbingIdentifier; + result = Tcl_NewStringObj(idString.UTF8String, [idString length]); + } + if (result == NULL) { + Tcl_Panic("Failed to read tabbing identifier."); + } + Tcl_SetObjResult(interp, result); + if (objc == 3) { + return TCL_OK; + } else if (objc == 4) { + int len; + char *newId = Tcl_GetStringFromObj(objv[3], &len); + NSString *newIdString = [NSString stringWithUTF8String:newId]; + [win setTabbingIdentifier: newIdString]; + return TCL_OK; + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * * TkpMakeMenuWindow -- * * Configure the window to be either a undecorated pull-down (or pop-up) |