diff options
author | culler <culler> | 2019-10-25 17:58:58 (GMT) |
---|---|---|
committer | culler <culler> | 2019-10-25 17:58:58 (GMT) |
commit | d4a398392db6a69b9a99d89a9aa8e10b5d423a65 (patch) | |
tree | e857a8584a2ec1571b0d6da9d05d1f932a0d8178 | |
parent | 87f5fe063fabe957d6b3b116952d83b7c575e794 (diff) | |
parent | c7c2b3b52832d1b78f18359507d3b971ec1506e2 (diff) | |
download | tk-d4a398392db6a69b9a99d89a9aa8e10b5d423a65.zip tk-d4a398392db6a69b9a99d89a9aa8e10b5d423a65.tar.gz tk-d4a398392db6a69b9a99d89a9aa8e10b5d423a65.tar.bz2 |
Aqua: fix IME behavior; make accent menus work; rework Services; deal with non-BMP pastes.
-rw-r--r-- | library/entry.tcl | 24 | ||||
-rw-r--r-- | library/text.tcl | 27 | ||||
-rw-r--r-- | library/tk.tcl | 8 | ||||
-rw-r--r-- | library/ttk/entry.tcl | 26 | ||||
-rw-r--r-- | macosx/tkMacOSXClipboard.c | 23 | ||||
-rw-r--r-- | macosx/tkMacOSXInit.c | 6 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyEvent.c | 271 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyboard.c | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXServices.c | 131 | ||||
-rw-r--r-- | macosx/tkMacOSXWindowEvent.c | 22 |
11 files changed, 330 insertions, 222 deletions
diff --git a/library/entry.tcl b/library/entry.tcl index 0cc9ffb..2aab934 100644 --- a/library/entry.tcl +++ b/library/entry.tcl @@ -74,11 +74,6 @@ bind Entry <<TraverseIn>> { # Standard Motif bindings: -bind Entry <Map> { - if {[tk windowingsystem] eq "aqua"} { - ::tk::RegisterServiceWidget %W - } -} bind Entry <1> { tk::EntryButton1 %W %x %W selection clear @@ -277,6 +272,25 @@ bind Entry <Meta-Delete> { } } +# Bindings for IME text input and accents. + +bind Entry <<TkStartIMEMarkedText>> { + dict set ::tk::Priv(IMETextMark) "%W" [%W index insert] +} +bind Entry <<TkEndIMEMarkedText>> { + if { [catch {dict get $::tk::Priv(IMETextMark) "%W"} mark] } { + bell + } else { + %W selection range $mark insert + } +} +bind Entry <<TkClearIMEMarkedText>> { + %W delete [dict get $::tk::Priv(IMETextMark) "%W"] [%W index insert] +} +bind Entry <<TkAccentBackspace>> { + tk::EntryBackspace %W +} + # A few additional bindings of my own. bind Entry <2> { diff --git a/library/text.tcl b/library/text.tcl index 964a5fb..e9c8bfe 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -42,12 +42,6 @@ # Standard Motif bindings: -bind Text <Map> { - if {[tk windowingsystem] eq "aqua"} { - ::tk::RegisterServiceWidget %W - } -} - bind Text <1> { tk::TextButton1 %W %x %y %W tag remove sel 0.0 end @@ -397,6 +391,26 @@ bind Text <Meta-Delete> { } } +# Bindings for IME text input. + +bind Text <<TkStartIMEMarkedText>> { + dict set ::tk::Priv(IMETextMark) "%W" [%W index insert] +} +bind Text <<TkEndIMEMarkedText>> { + if { [catch {dict get $::tk::Priv(IMETextMark) "%W"} mark] } { + bell + } else { + %W tag add IMEmarkedtext $mark insert + %W tag configure IMEmarkedtext -underline on + } +} +bind Text <<TkClearIMEMarkedText>> { + %W delete IMEmarkedtext.first IMEmarkedtext.last +} +bind Text <<TkAccentBackspace>> { + %W delete insert-1c +} + # Macintosh only bindings: if {[tk windowingsystem] eq "aqua"} { @@ -1223,7 +1237,6 @@ proc ::tk::TextScanDrag {w x y} { $w scan dragto $x $y } } - # ::tk::TextUndoRedoProcessMarks -- # # This proc is executed after an undo or redo action. diff --git a/library/tk.tcl b/library/tk.tcl index 047ea63..bc021d2 100644 --- a/library/tk.tcl +++ b/library/tk.tcl @@ -687,11 +687,6 @@ if {[tk windowingsystem] eq "aqua"} { if {[tk windowingsystem] eq "aqua"} { - #register to send data to macOS Services - proc ::tk::RegisterServiceWidget {w} { - ::tk::mac::registerServiceWidget $w - } - #stub procedures to respond to "do script" Apple Events proc ::tk::mac::DoScriptFile {file} { source $file @@ -701,7 +696,10 @@ if {[tk windowingsystem] eq "aqua"} { } } +# Create a dictionary to store the starting index of the IME marked +# text in an Entry or Text widget. +set ::tk::Priv(IMETextMark) [dict create] # Run the Ttk themed widget set initialization if {$::ttk::library ne ""} { diff --git a/library/ttk/entry.tcl b/library/ttk/entry.tcl index 383eebd..0a61ffa 100644 --- a/library/ttk/entry.tcl +++ b/library/ttk/entry.tcl @@ -58,13 +58,6 @@ option add *TEntry.cursor [ttk::cursor text] widgetDefault # and I'll put it back. # -##Bindings to register with macOS Services API. -bind T.Entry <Map> { - if {[tk windowingsystem] eq "aqua"} { - ::tk::RegisterServiceWidget %W - } -} - ## Clipboard events: # bind TEntry <<Cut>> { ttk::entry::Cut %W } @@ -152,6 +145,25 @@ bind TEntry <Control-Key-d> { ttk::entry::Delete %W } bind TEntry <Control-Key-h> { ttk::entry::Backspace %W } bind TEntry <Control-Key-k> { %W delete insert end } +# Bindings for IME text input. + +bind TEntry <<TkStartIMEMarkedText>> { + dict set ::tk::Priv(IMETextMark) "%W" [%W index insert] +} +bind TEntry <<TkEndIMEMarkedText>> { + if { [catch {dict get $::tk::Priv(IMETextMark) "%W"} mark] } { + bell + } else { + %W selection range $mark insert + } +} +bind TEntry <<TkClearIMEMarkedText>> { + %W delete [dict get $::tk::Priv(IMETextMark) "%W"] [%W index insert] +} +bind Entry <<TkAccentBackspace>> { + tk::EntryBackspace %W +} + ### Clipboard procedures. # diff --git a/macosx/tkMacOSXClipboard.c b/macosx/tkMacOSXClipboard.c index 452b32f..6717afa 100644 --- a/macosx/tkMacOSXClipboard.c +++ b/macosx/tkMacOSXClipboard.c @@ -130,6 +130,7 @@ TkSelGetSelection( && selection == dispPtr->clipboardAtom && (target == XA_STRING || target == dispPtr->utf8Atom)) { NSString *string = nil; + NSString *clean; NSPasteboard *pb = [NSPasteboard generalPasteboard]; NSString *type = [pb availableTypeFromArray:[NSArray arrayWithObject: NSStringPboardType]]; @@ -137,7 +138,27 @@ TkSelGetSelection( if (type) { string = [pb stringForType:type]; } - result = proc(clientData, interp, string ? [string UTF8String] : ""); + if (string) { + /* + * Replace all non-BMP characters by the replacement character 0xfffd. + * This is a workaround until Tcl supports TCL_UTF_MAX > 3. + */ + int i, j, len = [string length]; + CFRange all = CFRangeMake(0, len); + UniChar *buffer = ckalloc(len*sizeof(UniChar)); + CFStringGetCharacters((CFStringRef) string, all, buffer); + for (i = 0, j = 0 ; j < len ; i++, j++) { + if (CFStringIsSurrogateHighCharacter(buffer[j])) { + buffer[i] = 0xfffd; + j++; + } else { + buffer[i] = buffer[j]; + } + } + clean = (NSString *)CFStringCreateWithCharacters(NULL, buffer, i); + ckfree(buffer); + result = proc(clientData, interp, [clean UTF8String]); + } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s selection doesn't exist or form \"%s\" not defined", diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c index a787e13..3d15442 100644 --- a/macosx/tkMacOSXInit.c +++ b/macosx/tkMacOSXInit.c @@ -413,8 +413,7 @@ TkpInit( /* * Initialize the NSServices object here. Apple's docs say to do this * in applicationDidFinishLaunching, but the Tcl interpreter is not - * initialized until this function call. Only the main interpreter - * is allowed to provide services. + * initialized until this function call. */ TkMacOSXServices_Init(interp); @@ -435,9 +434,6 @@ TkpInit( TkMacOSXIconBitmapObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "::tk::mac::GetAppPath", TkMacOSXGetAppPathCmd, NULL, NULL); - Tcl_CreateObjCommand(interp, "::tk::mac::registerServiceWidget", - TkMacOSXRegisterServiceWidgetObjCmd, NULL, NULL); - return TCL_OK; } diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index ac03db2..e71d3ab 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -7,7 +7,7 @@ * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * Copyright (c) 2012 Adrian Robert. - * Copyright 2015 Marc Culler. + * Copyright 2015-2019 Marc Culler. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -31,13 +31,12 @@ static NSWindow *keyboardGrabNSWindow = nil; * window. */ static NSModalSession modalSession = nil; static BOOL processingCompose = NO; -static BOOL finishedCompose = NO; +static Tk_Window composeWin = NULL; static int caret_x = 0, caret_y = 0, caret_height = 0; -static void setupXEvent(XEvent *xEvent, NSWindow *w, - unsigned int state); -static unsigned isFunctionKey(unsigned int code); +static unsigned short releaseCode; -unsigned short releaseCode; +static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state); +static unsigned isFunctionKey(unsigned int code); #pragma mark TKApplication(TKKeyEvent) @@ -210,15 +209,19 @@ unsigned short releaseCode; } /* - * For command key, take input manager's word so things like - * dvorak / qwerty layout work. + * For the command key, take the input manager's word so things + * like dvorak / qwerty layout work. */ if ((modifiers & NSCommandKeyMask) == NSCommandKeyMask && (modifiers & NSAlternateKeyMask) != NSAlternateKeyMask && len > 0 && !isFunctionKey(code)) { - // head off keycode-based translation in tkMacOSXKeyboard.c - xEvent.xkey.nbytes = [characters length]; //len + + /* + * Prevent keycode-based translation in tkMacOSXKeyboard.c + */ + + xEvent.xkey.nbytes = [characters length]; } if ([characters length] > 0) { @@ -242,7 +245,7 @@ unsigned short releaseCode; Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); savedModifiers = modifiers; return theEvent; - } /* if send straight to TK */ + } /* if this is a function key or has modifiers */ } /* if not processing compose */ if (type == NSKeyDown) { @@ -250,12 +253,29 @@ unsigned short releaseCode; TKLog(@"keyDown: %s compose sequence.\n", processingCompose == YES ? "Continue" : "Begin"); } - processingCompose = YES; - [nsEvArray addObject: theEvent]; - [[w contentView] interpretKeyEvents: nsEvArray]; - [nsEvArray removeObject: theEvent]; - } + /* + * Call the interpretKeyEvents method to interpret composition key + * strokes. When it detects a complete composition sequence it will + * call our implementation of insertText: replacementRange, which + * generates a key down XEvent with the appropriate character. In IME + * when multiple characters have the same composition sequence and the + * chosen character is not the default it may be necessary to hit the + * enter key multiple times before the character is accepted and + * rendered. We send enter key events until inputText has cleared + * the processingCompose flag. + */ + + processingCompose = YES; + while(processingCompose) { + [nsEvArray addObject: theEvent]; + [[w contentView] interpretKeyEvents: nsEvArray]; + [nsEvArray removeObject: theEvent]; + if ([theEvent keyCode] != 36) { + break; + } + } + } savedModifiers = modifiers; return theEvent; } @@ -272,25 +292,34 @@ unsigned short releaseCode; return self; } -/* <NSTextInput> implementation (called through interpretKeyEvents:]). */ +/* + * Implementation of the NSTextInputClient protocol. + */ -/* <NSTextInput>: called when done composing; - NOTE: also called when we delete over working text, followed immed. - by doCommandBySelector: deleteBackward: */ +/* [NSTextInputClient inputText: replacementRange:] is called by + * interpretKeyEvents when a composition sequence is complete. It is also + * called when we delete over working text. In that case the call is followed + * immediately by doCommandBySelector: deleteBackward: + */ - (void)insertText: (id)aString + replacementRange: (NSRange)repRange { - int i, len = [(NSString *) aString length]; + int i, len; XEvent xEvent; + NSString *str; + + str = ([aString isKindOfClass: [NSAttributedString class]]) ? + str = [aString string] : aString; + len = [str length]; if (NS_KEYLOG) { TKLog(@"insertText '%@'\tlen = %d", aString, len); } processingCompose = NO; - finishedCompose = YES; /* - * First, clear any working text. + * Clear any working text. */ if (privateWorkingText != nil) { @@ -298,32 +327,104 @@ unsigned short releaseCode; } /* - * Now insert the string as keystrokes. + * Insert the string as a sequence of keystrokes. */ setupXEvent(&xEvent, [self window], 0); xEvent.xany.type = KeyPress; - for (i =0; i<len; i++) { - xEvent.xkey.keycode = (UInt16) [aString characterAtIndex: i]; - [[aString substringWithRange: NSMakeRange(i,1)] - getCString: xEvent.xkey.trans_chars - maxLength: XMaxTransChars encoding: NSUTF8StringEncoding]; - xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars); + /* + * Apple evidently sets location to 0 to signal that an accented letter has + * been selected from the accent menu. An unaccented letter has already + * been displayed and we need to erase it before displaying the accented + * letter. + */ + + if (repRange.location == 0) { + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + Tk_Window focusWin = (Tk_Window) winPtr->dispPtr->focusPtr; + TkSendVirtualEvent(focusWin, "TkAccentBackspace", NULL); + } + + /* + * NSString represents a non-BMP character as a string of length 2 where + * the first character is the high surrogate and the second character is + * the low surrogate. We could record this in the XEvent by setting the + * keycode to the unicode code point and setting the trans_chars to the + * 4-byte UTF-8 string. However, that will not help as long as TCL_UTF_MAX + * is set to 3. Until that changes, we just replace non-BMP characters by + * the "replacement character" U+FFFD. + */ + + for (i = 0; i < len; i++) { + UniChar nextChar = [str characterAtIndex: i]; + if (CFStringIsSurrogateHighCharacter(nextChar)) { +#if 0 + UniChar lowChar = [str characterAtIndex: ++i]; + xEvent.xkey.keycode = CFStringGetLongCharacterForSurrogatePair( + nextChar, lowChar); + xEvent.xkey.nbytes = TkUniCharToUtf(xEvent.xkey.keycode, + &xEvent.xkey.trans_chars); +#else + i++; + xEvent.xkey.keycode = 0xfffd; + strcpy(xEvent.xkey.trans_chars, "\xef\xbf\xbd"); + xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars); +#endif + } else { + xEvent.xkey.keycode = (int) nextChar; + [[str substringWithRange: NSMakeRange(i,1)] + getCString: xEvent.xkey.trans_chars + maxLength: XMaxTransChars encoding: NSUTF8StringEncoding]; + xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars); + } xEvent.xany.type = KeyPress; - releaseCode = (UInt16) [aString characterAtIndex: 0]; + releaseCode = (UInt16) nextChar; Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); } - releaseCode = (UInt16) [aString characterAtIndex: 0]; + releaseCode = (UInt16) [str characterAtIndex: 0]; +} + +/* + * This required method is allowed to return nil. + */ + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange + actualRange:(NSRangePointer)thePointer +{ + return nil; } +/* + * This method is supposed to insert (or replace selected text with) the string + * argument. If the argument is an NSString, it should be displayed with a + * distinguishing appearance, e.g underlined. + */ -/* <NSTextInput>: inserts display of composing characters */ -- (void)setMarkedText: (id)aString selectedRange: (NSRange)selRange +- (void)setMarkedText: (id)aString + selectedRange: (NSRange)selRange + replacementRange: (NSRange)repRange { - NSString *str = [aString respondsToSelector: @selector (string)] ? - [aString string] : aString; + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + Tk_Window focusWin = (Tk_Window) winPtr->dispPtr->focusPtr; + NSString *temp; + NSString *str; + + str = ([aString isKindOfClass: [NSAttributedString class]]) ? + str = [aString string] : aString; + + if (focusWin) { + + /* + * Remember the widget where the composition is happening, in case it + * gets defocussed during the composition. + */ + + composeWin = focusWin; + } else { + return; + } if (NS_KEYLOG) { TKLog(@"setMarkedText '%@' len =%lu range %lu from %lu", str, (unsigned long) [str length], (unsigned long) selRange.length, @@ -333,17 +434,23 @@ unsigned short releaseCode; if (privateWorkingText != nil) { [self deleteWorkingText]; } + if ([str length] == 0) { return; } - processingCompose = YES; - privateWorkingText = [str copy]; + /* + * Use our insertText method to display the marked text. + */ - //PENDING: insert workingText underlined + TkSendVirtualEvent(focusWin, "TkStartIMEMarkedText", NULL); + temp = [str copy]; + [self insertText: temp replacementRange:repRange]; + privateWorkingText = temp; + processingCompose = YES; + TkSendVirtualEvent(focusWin, "TkEndIMEMarkedText", NULL); } - - (BOOL)hasMarkedText { return privateWorkingText != nil; @@ -362,23 +469,34 @@ unsigned short releaseCode; return rng; } +- (void)cancelComposingText +{ + if (NS_KEYLOG) { + TKLog(@"cancelComposingText"); + } + [self deleteWorkingText]; + processingCompose = NO; +} - (void)unmarkText { if (NS_KEYLOG) { - TKLog(@"unmark (accept) text"); + TKLog(@"unmarkText"); } [self deleteWorkingText]; processingCompose = NO; } -/* used to position char selection windows, etc. */ +/* + * Called by the system to get a position for popup character selection windows + * such as a Character Palette, or a selection menu for IME. + */ - (NSRect)firstRectForCharacterRange: (NSRange)theRange + actualRange: (NSRangePointer)thePointer { NSRect rect; NSPoint pt; - pt.x = caret_x; pt.y = caret_y; @@ -387,7 +505,7 @@ unsigned short releaseCode; pt.y -= caret_height; rect.origin = pt; - rect.size.width = caret_height; + rect.size.width = 0; rect.size.height = caret_height; return rect; } @@ -398,7 +516,6 @@ unsigned short releaseCode; return (NSInteger) self; } - - (void)doCommandBySelector: (SEL)aSelector { if (NS_KEYLOG) { @@ -406,54 +523,41 @@ unsigned short releaseCode; } processingCompose = NO; if (aSelector == @selector (deleteBackward:)) { - /* - * Happens when user backspaces over an ongoing composition: - * throw a 'delete' into the event queue. - */ - - XEvent xEvent; - - setupXEvent(&xEvent, [self window], 0); - xEvent.xany.type = KeyPress; - xEvent.xkey.nbytes = 1; - xEvent.xkey.keycode = (0x33 << 16) | 0x7F; - xEvent.xkey.trans_chars[0] = 0x7F; - xEvent.xkey.trans_chars[1] = 0x0; - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + Tk_Window focusWin = (Tk_Window) winPtr->dispPtr->focusPtr; + TkSendVirtualEvent(focusWin, "TkAccentBackspace", NULL); } } - - (NSArray *)validAttributesForMarkedText { static NSArray *arr = nil; - if (arr == nil) { - arr = [NSArray new]; + arr = [[NSArray alloc] initWithObjects: + NSUnderlineStyleAttributeName, + NSUnderlineColorAttributeName, + nil]; + [arr retain]; } - /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */ return arr; } - - (NSRange)selectedRange { if (NS_KEYLOG) { TKLog(@"selectedRange request"); } - return NSMakeRange(NSNotFound, 0); + return NSMakeRange(0, 0); } - - (NSUInteger)characterIndexForPoint: (NSPoint)thePoint { if (NS_KEYLOG) { TKLog(@"characterIndexForPoint request"); } - return 0; + return NSNotFound; } - - (NSAttributedString *)attributedSubstringFromRange: (NSRange)theRange { static NSAttributedString *str = nil; @@ -465,28 +569,37 @@ unsigned short releaseCode; } return str; } -/* End <NSTextInput> impl. */ +/* End of NSTextInputClient implementation. */ @synthesize needsRedisplay = _needsRedisplay; @end @implementation TKContentView(TKKeyEvent) -/* delete display of composing characters [not in <NSTextInput>] */ + +/* + * Tell the widget to erase the displayed composing characters. This + * is not part of the NSTextInputClient protocol. + */ + - (void)deleteWorkingText { if (privateWorkingText == nil) { return; - } - if (NS_KEYLOG) { - TKLog(@"deleteWorkingText len = %lu\n", - (unsigned long)[privateWorkingText length]); - } - [privateWorkingText release]; - privateWorkingText = nil; - processingCompose = NO; + } else { + + if (NS_KEYLOG) { + TKLog(@"deleteWorkingText len = %lu\n", + (unsigned long)[privateWorkingText length]); + } - //PENDING: delete working text + [privateWorkingText release]; + privateWorkingText = nil; + processingCompose = NO; + if (composeWin) { + TkSendVirtualEvent(composeWin, "TkClearIMEMarkedText", NULL); + } + } } @end diff --git a/macosx/tkMacOSXKeyboard.c b/macosx/tkMacOSXKeyboard.c index 3272657..ef4a4e2 100644 --- a/macosx/tkMacOSXKeyboard.c +++ b/macosx/tkMacOSXKeyboard.c @@ -803,7 +803,7 @@ TkpGetKeySym( /* If nbytes has been set, it's not a function key, but a regular key that has been translated in tkMacOSXKeyEvent.c; just use that. */ if (eventPtr->xkey.nbytes) { - return eventPtr->xkey.keycode & 0xFFFF; + return eventPtr->xkey.keycode; } /* diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index cb80dd8..7aa6840 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -340,7 +340,12 @@ VISIBILITY_HIDDEN @end VISIBILITY_HIDDEN -@interface TKContentView : NSView <NSTextInput> +/* + * Subclass TKContentView from NSTextInputClient to enable composition and + * input from the Character Palette. + */ + +@interface TKContentView : NSView <NSTextInputClient> { @private NSString *privateWorkingText; @@ -354,13 +359,8 @@ VISIBILITY_HIDDEN @end @interface TKContentView(TKWindowEvent) -- (void) drawRect: (NSRect) rect; - (void) generateExposeEvents: (HIShapeRef) shape; - (void) tkToolbarButton: (id) sender; -- (BOOL) isOpaque; -- (BOOL) wantsDefaultClipping; -- (BOOL) acceptsFirstResponder; -- (void) keyDown: (NSEvent *) theEvent; @end @interface NSWindow(TKWm) diff --git a/macosx/tkMacOSXServices.c b/macosx/tkMacOSXServices.c index f1e5951..e92158f 100644 --- a/macosx/tkMacOSXServices.c +++ b/macosx/tkMacOSXServices.c @@ -4,20 +4,18 @@ * This file allows the integration of Tk and the Cocoa NSServices API. * * Copyright (c) 2010-2019 Kevin Walzer/WordTech Communications LLC. + * Copyright (c) 2019 Marc Culler. * Copyright (c) 2010 Adrian Robert. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ -#include <CoreServices/CoreServices.h> #include <tkInt.h> #include <tkMacOSXInt.h> -static Tcl_Interp *ServicesInterp; - /* - * Event proc which calls the PerformService procedure + * Event proc which calls the PerformService procedure. */ static int @@ -25,22 +23,28 @@ ServicesEventProc( Tcl_Event *event, int flags) { - Tcl_GlobalEval(ServicesInterp, "::tk::mac::PerformService"); + TkMainInfo *info = TkGetMainInfoList(); + Tcl_GlobalEval(info->interp, "::tk::mac::PerformService"); return 1; } /* - * Class declarations for TkService class. + * The Wish application can send the current selection in the Tk clipboard + * to other applications which accept messages of type NSString. The TkService + * object provides this service via its provideService method. (The method + * must be specified in the application's Info.plist file for this to work.) */ -@interface TkService : NSView { +@interface TkService : NSObject { } + (void) initialize; -- (void)provideService:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error; -- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType; -- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types; +- (void) provideService:(NSPasteboard *)pboard + userData:(NSString *)data + error:(NSString **)error; +- (BOOL) writeSelectionToPasteboard:(NSPasteboard *)pboard + types:(NSArray *)types; @end @@ -57,32 +61,8 @@ ServicesEventProc( return; } - -- (id)validRequestorForSendType:(NSString *)sendType - returnType:(NSString *)returnType -{ - if ([sendType isEqualToString:@"NSStringPboardType"] || - [sendType isEqualToString:@"NSPasteboardTypeString"]) { - return self; - } - return [super validRequestorForSendType:sendType returnType:returnType]; -} - -/* - * Make sure the view accepts events. - */ - -- (BOOL)acceptsFirstResponder -{ - return YES; -} -- (BOOL)becomeFirstResponder -{ - return YES; -} - /* - * Get selected text, copy to pasteboard. + * Get the current Tk selection and copy it to the system pasteboard. */ - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard @@ -90,6 +70,7 @@ ServicesEventProc( { NSArray *typesDeclared = nil; NSString *pboardType = nil; + TkMainInfo *info = TkGetMainInfoList(); for (NSString *typeString in types) { if ([typeString isEqualToString:@"NSStringPboardType"] || @@ -102,9 +83,9 @@ ServicesEventProc( if (!typesDeclared) { return NO; } - Tcl_Eval(ServicesInterp, "selection get"); + Tcl_Eval(info->interp, "selection get"); - char *copystring = Tcl_GetString(Tcl_GetObjResult(ServicesInterp)); + char *copystring = Tcl_GetString(Tcl_GetObjResult(info->interp)); NSString *writestring = [NSString stringWithUTF8String:copystring]; [pboard declareTypes:typesDeclared owner:nil]; @@ -112,8 +93,8 @@ ServicesEventProc( } /* - * This is the method that actually calls the Tk service; this is the method - * that must be defined in info.plist. + * This is the method that actually calls the Tk service; it must be specified + * in Info.plist. */ - (void)provideService:(NSPasteboard *)pboard @@ -125,8 +106,8 @@ ServicesEventProc( Tcl_Event *event; /* - * Get string from private pasteboard, write to general pasteboard to make - * available to Tcl service. + * Get a string from the private pasteboard and copy it to the general + * pasteboard to make it available to other applications. */ for (NSString *typeString in types) { @@ -150,69 +131,9 @@ ServicesEventProc( @end /* - * Register a specific widget to access the Services menu. - */ - -int -TkMacOSXRegisterServiceWidgetObjCmd( - ClientData cd, - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ - /* - * Need proper number of args. - */ - - if (objc != 2) { - Tcl_WrongNumArgs(interp, 1, objv, "path?"); - return TCL_ERROR; - } - /* - * Get the object that holds this Tk Window... - */ - - Rect bounds; - NSRect frame; - Tk_Window path = Tk_NameToWindow(interp, - Tcl_GetString(objv[1]), Tk_MainWindow(interp)); - - if (path == NULL) { - return TCL_ERROR; - } - - Tk_MakeWindowExist(path); - Tk_MapWindow(path); - Drawable d = Tk_WindowId(path); - - /* - * Get NSView from Tk window and add subview. - */ - - TkService *serviceview = [[TkService alloc] init]; - NSView *view = TkMacOSXGetRootControl(d); - - if ([serviceview superview] != view) { - [view addSubview:serviceview]; - } - TkMacOSXWinBounds((TkWindow*)path, &bounds); - - /* - * Hack to make sure subview is set to take up entire geometry of window. - */ - - frame = NSMakeRect(bounds.left, bounds.top, 100000, 100000); - frame.origin.y = 0; - if (!NSEqualRects(frame, [serviceview frame])) { - [serviceview setFrame:frame]; - } - [serviceview release]; - return TCL_OK; -} - -/* - * Initalize the package in the Tcl interpreter, create Tcl commands. + * Instantiate a TkService object and register it with the NSApplication. + * This is called exactly one time from TkpInit. */ int @@ -220,12 +141,10 @@ TkMacOSXServices_Init( Tcl_Interp *interp) { /* - * Initialize instance of TclServices to provide service functionality. + * Initialize an instance of TkService and register it with the NSApp. */ TkService *service = [[TkService alloc] init]; - - ServicesInterp = interp; [NSApp setServicesProvider:service]; return TCL_OK; } diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index 2666939..fe6981d 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -1186,6 +1186,11 @@ RedisplayView( return YES; } +/* + * This keyDown method does nothing, which is a huge improvement over the + * default keyDown method which beeps every time a key is pressed. + */ + - (void) keyDown: (NSEvent *) theEvent { #ifdef TK_MAC_DEBUG_EVENTS @@ -1193,6 +1198,23 @@ RedisplayView( #endif } +/* + * When the services menu is opened this is called for each Responder in + * the Responder chain until a service provider is found. The TkContentView + * should be the first (and generally only) Responder in the chain. We + * return the TkServices object that was created in TkpInit. + */ + +- (id)validRequestorForSendType:(NSString *)sendType + returnType:(NSString *)returnType +{ + if ([sendType isEqualToString:@"NSStringPboardType"] || + [sendType isEqualToString:@"NSPasteboardTypeString"]) { + return [NSApp servicesProvider]; + } + return [super validRequestorForSendType:sendType returnType:returnType]; +} + @end /* |