summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorculler <culler>2018-11-30 22:11:48 (GMT)
committerculler <culler>2018-11-30 22:11:48 (GMT)
commitdbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0 (patch)
treeda98509c5fc6e022f17a7d355153b31cbbac4ae3
parent2f18a3f080b8f55b2b4be4a90e948531f203eb7d (diff)
downloadtk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.zip
tk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.tar.gz
tk-dbf1875126153a5dff05b89cbd7ab1d9ce6ef5d0.tar.bz2
Make tabbed windows work correctly on macOS.
-rw-r--r--library/demos/widget5
-rw-r--r--macosx/README17
-rw-r--r--macosx/tkMacOSXKeyEvent.c18
-rw-r--r--macosx/tkMacOSXMenu.c66
-rw-r--r--macosx/tkMacOSXMenus.c48
-rw-r--r--macosx/tkMacOSXNotify.c7
-rw-r--r--macosx/tkMacOSXWm.c87
7 files changed, 203 insertions, 45 deletions
diff --git a/library/demos/widget b/library/demos/widget
index 1d838ad..2b4e3f8 100644
--- a/library/demos/widget
+++ b/library/demos/widget
@@ -118,6 +118,11 @@ if {[tk windowingsystem] ne "aqua"} {
-command {exit} -accelerator [mc "Meta-Q"]
bind . <[mc "Meta-q"]> {exit}
}
+} else {
+ menu .menuBar.view -tearoff 0
+ .menuBar add cascade -label "View" -menu .menuBar.view
+ menu .menuBar.window -tearoff 0
+ .menuBar add cascade -label "Window" -menu .menuBar.window
}
. configure -menu .menuBar
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)