diff options
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/README | 6 | ||||
-rw-r--r-- | macosx/tkMacOSXClipboard.c | 23 | ||||
-rw-r--r-- | macosx/tkMacOSXDraw.c | 37 | ||||
-rw-r--r-- | macosx/tkMacOSXFont.c | 29 | ||||
-rw-r--r-- | macosx/tkMacOSXInit.c | 6 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyEvent.c | 301 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyboard.c | 9 | ||||
-rw-r--r-- | macosx/tkMacOSXMenus.c | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXMouseEvent.c | 32 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXScrlbr.c | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXServices.c | 131 | ||||
-rw-r--r-- | macosx/tkMacOSXTest.c | 30 | ||||
-rw-r--r-- | macosx/tkMacOSXWindowEvent.c | 22 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 88 | ||||
-rw-r--r-- | macosx/ttkMacOSXTheme.c | 19 |
16 files changed, 436 insertions, 323 deletions
diff --git a/macosx/README b/macosx/README index c2dabae..4989ec7 100644 --- a/macosx/README +++ b/macosx/README @@ -49,8 +49,8 @@ brings up the Tk console window at startup. This is the case when double clicking Wish in the Finder (or using 'open Wish.app' from the Terminal). - Tcl extensions can be installed in any of: - $HOME/Library/Tcl /Library/Tcl /System/Library/Tcl - $HOME/Library/Frameworks /Library/Frameworks /System/Library/Frameworks + $HOME/Library/Tcl /Library/Tcl + $HOME/Library/Frameworks /Library/Frameworks (searched in that order). Given a potential package directory $pkg, Tcl on OSX checks for the file $pkg/Resources/Scripts/pkgIndex.tcl as well as the usual $pkg/pkgIndex.tcl. @@ -65,7 +65,7 @@ No nroff manpages are installed by default by the GNUmakefile. - The Tcl and Tk frameworks can be installed in any of the system's standard framework directories: - $HOME/Library/Frameworks /Library/Frameworks /System/Library/Frameworks + $HOME/Library/Frameworks /Library/Frameworks - ${prefix}/bin/wish8.x is a script that calls a copy of 'Wish' contained in Tk.framework/Resources 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/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c index af0cc05..724f637 100644 --- a/macosx/tkMacOSXDraw.c +++ b/macosx/tkMacOSXDraw.c @@ -501,9 +501,12 @@ TkMacOSXGetNSImageWithTkImage( int width, int height) { - Pixmap pixmap = Tk_GetPixmap(display, None, width, height, 0); + Pixmap pixmap; NSImage *nsImage; - + if (width == 0 || height == 0) { + return nsImage = [[NSImage alloc] initWithSize:NSMakeSize(0,0)]; + } + pixmap = Tk_GetPixmap(display, None, width, height, 0); Tk_RedrawImage(image, 0, 0, width, height, pixmap, 0, 0); nsImage = CreateNSImageWithPixmap(pixmap, width, height); Tk_FreePixmap(display, pixmap); @@ -1611,16 +1614,36 @@ TkMacOSXSetupDrawingContext( * a view's drawRect or setFrame methods. The isDrawing attribute * tells us whether we are being called from one of those methods. * - * If the CGContext is not valid, or belongs to a different View, then - * we mark our view as needing display and return failure. It should - * get drawn in a later call to drawRect. + * If the CGContext is not valid then we mark our view as needing + * display in the bounding rectangle of the clipping region and + * return failure. That rectangle should get drawn in a later call + * to drawRect. + * + * As an exception to the above, if mouse buttons are pressed at the + * moment when we fail to obtain a valid context we schedule the entire + * view for a redraw rather than just the clipping region. The purpose + * of this is to make sure that scrollbars get updated correctly. */ - if (view != [NSView focusView]) { - [view setNeedsDisplay:YES]; + if (![NSApp isDrawing] || view != [NSView focusView]) { + NSRect bounds = [view bounds]; + NSRect dirtyNS = bounds; + if ([NSEvent pressedMouseButtons]) { + [view setNeedsDisplay:YES]; + } else { + CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, + .ty = dirtyNS.size.height}; + if (dc.clipRgn) { + CGRect dirtyCG = NSRectToCGRect(dirtyNS); + HIShapeGetBounds(dc.clipRgn, &dirtyCG); + dirtyNS = NSRectToCGRect(CGRectApplyAffineTransform(dirtyCG, t)); + } + [view setNeedsDisplayInRect:dirtyNS]; + } canDraw = false; goto end; } + dc.view = view; dc.context = GET_CGCONTEXT; dc.portBounds = NSRectToCGRect([view bounds]); diff --git a/macosx/tkMacOSXFont.c b/macosx/tkMacOSXFont.c index 7659163..41da390 100644 --- a/macosx/tkMacOSXFont.c +++ b/macosx/tkMacOSXFont.c @@ -128,11 +128,11 @@ GetTkFontAttributesForNSFont( { NSFontTraitMask traits = [[NSFontManager sharedFontManager] traitsOfFont:nsFont]; - faPtr->family = Tk_GetUid([[nsFont familyName] UTF8String]); faPtr->size = [nsFont pointSize]; faPtr->weight = (traits & NSBoldFontMask ? TK_FW_BOLD : TK_FW_NORMAL); faPtr->slant = (traits & NSItalicFontMask ? TK_FS_ITALIC : TK_FS_ROMAN); + } /* @@ -176,6 +176,18 @@ FindNSFont( size = [defaultFont pointSize]; } nsFont = [fm fontWithFamily:family traits:traits weight:weight size:size]; + + /* + * A second bug in NSFontManager that Apple created for the Catalina OS + * causes requests as above to sometimes return fonts with additional + * traits that were not requested, even though fonts without those unwanted + * traits exist on the system. See bug [90d555e088]. As a workaround + * we ask the font manager to remove any unrequested traits. + */ + + if (nsFont) { + nsFont = [fm convertFont:nsFont toNotHaveTrait:~traits]; + } if (!nsFont) { NSArray *availableFamilies = [fm availableFontFamilies]; NSString *caseFamily = nil; @@ -394,10 +406,25 @@ TkpFontPkgInit( systemFont++; } TkInitFontAttributes(&fa); +#if 0 + + /* + * In macOS 10.15.1 Apple introduced a bug in NSFontManager which caused + * it to not recognize the familyName ".SF NSMono" which is the familyName + * of the default fixed pitch system fault on that system. See bug [855049e799]. + * As a workaround we call [NSFont userFixedPitchFontOfSize:11] instead. + * This returns a user font in the "Menlo" family. + */ + nsFont = (NSFont*) CTFontCreateUIFontForLanguage(fixedPitch, 11, NULL); +#else + nsFont = [NSFont userFixedPitchFontOfSize:11]; +#endif if (nsFont) { GetTkFontAttributesForNSFont(nsFont, &fa); +#if 0 CFRelease(nsFont); +#endif } else { fa.family = Tk_GetUid("Monaco"); fa.size = 11; 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 b1c1689..e3aed98 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) @@ -137,9 +136,9 @@ unsigned short releaseCode; } /* - * Events are only received for the front Window on the Macintosh. So + * Key events are only received for the front Window on the Macintosh. So * to build an XEvent we look up the Tk window associated to the Front - * window. If a different window has a local grab we ignore the event. + * window. */ TkWindow *winPtr = TkMacOSXGetTkWindow(w); @@ -148,11 +147,18 @@ unsigned short releaseCode; if (tkwin) { TkWindow *grabWinPtr = winPtr->dispPtr->grabWinPtr; - if (grabWinPtr - && grabWinPtr != winPtr - && !winPtr->dispPtr->grabFlags /* this means the grab is local. */ - && grabWinPtr->mainPtr == winPtr->mainPtr) { - return theEvent; + /* + * If a local grab is in effect, key events for windows in the + * grabber's application are redirected to the grabber. Key events + * for other applications are delivered normally. If a global + * grab is in effect all key events are redirected to the grabber. + */ + + if (grabWinPtr) { + if (winPtr->dispPtr->grabFlags || /* global grab */ + grabWinPtr->mainPtr == winPtr->mainPtr){ /* same appl. */ + tkwin = (Tk_Window) winPtr->dispPtr->focusPtr; + } } } else { tkwin = (Tk_Window) winPtr->dispPtr->focusPtr; @@ -202,18 +208,6 @@ unsigned short releaseCode; xEvent.xany.type = KeyPress; } - /* - * For command key, take 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 - } - if ([characters length] > 0) { xEvent.xkey.keycode = (keyCode << 16) | (UInt16) [characters characterAtIndex:0]; @@ -235,7 +229,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) { @@ -243,12 +237,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; } @@ -265,25 +276,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]]) ? + [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) { @@ -291,32 +311,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]]) ? + [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, @@ -326,17 +418,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; @@ -355,23 +453,35 @@ 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; @@ -380,18 +490,16 @@ 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; } - - (NSInteger)conversationIdentifier { return (NSInteger) self; } - - (void)doCommandBySelector: (SEL)aSelector { if (NS_KEYLOG) { @@ -399,54 +507,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; @@ -458,34 +553,44 @@ 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 { - //PENDING: delete working text + if (NS_KEYLOG) { + TKLog(@"deleteWorkingText len = %lu\n", + (unsigned long)[privateWorkingText length]); + } + + [privateWorkingText release]; + privateWorkingText = nil; + processingCompose = NO; + if (composeWin) { + TkSendVirtualEvent(composeWin, "TkClearIMEMarkedText", NULL); + } + } } @end /* * Set up basic fields in xevent for keyboard input. */ + static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state) { @@ -498,17 +603,15 @@ setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state) memset(xEvent, 0, sizeof(XEvent)); xEvent->xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); - xEvent->xany.send_event = false; xEvent->xany.display = Tk_Display(tkwin); xEvent->xany.window = Tk_WindowId(tkwin); xEvent->xkey.root = XRootWindow(Tk_Display(tkwin), 0); - xEvent->xkey.subwindow = None; xEvent->xkey.time = TkpGetMS(); xEvent->xkey.state = state; xEvent->xkey.same_screen = true; - xEvent->xkey.trans_chars[0] = 0; - xEvent->xkey.nbytes = 0; + /* No need to initialize other fields implicitly here, + * because of the memset() above. */ } #pragma mark - diff --git a/macosx/tkMacOSXKeyboard.c b/macosx/tkMacOSXKeyboard.c index 3272657..e91dfe2 100644 --- a/macosx/tkMacOSXKeyboard.c +++ b/macosx/tkMacOSXKeyboard.c @@ -443,8 +443,11 @@ TkpGetString( * result. */ { (void) winPtr; /*unused*/ + int ch; + Tcl_DStringInit(dsPtr); - return Tcl_DStringAppend(dsPtr, eventPtr->xkey.trans_chars, -1); + return Tcl_DStringAppend(dsPtr, eventPtr->xkey.trans_chars, + TkUtfToUniChar(eventPtr->xkey.trans_chars, &ch)); } /* @@ -717,7 +720,7 @@ TkpSetKeycodeAndState( } if (keysym <= LATIN1_MAX) { - int done = Tcl_UniCharToUtf(keysym, eventPtr->xkey.trans_chars); + int done = TkUniCharToUtf(keysym, eventPtr->xkey.trans_chars); eventPtr->xkey.trans_chars[done] = 0; } else { @@ -803,7 +806,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/tkMacOSXMenus.c b/macosx/tkMacOSXMenus.c index 99c1191..82bcda7 100644 --- a/macosx/tkMacOSXMenus.c +++ b/macosx/tkMacOSXMenus.c @@ -70,7 +70,7 @@ static Tcl_Obj * GetWidgetDemoPath(Tcl_Interp *interp); [NSMenuItem itemWithTitle: [NSString stringWithFormat:@"About %@", aboutName] action:@selector(orderFrontStandardAboutPanel:)] atIndex:0]; - _defaultFileMenuItems = + _defaultFileMenuItems = [[NSArray arrayWithObjects: [NSMenuItem itemWithTitle: [NSString stringWithFormat:@"Source%C", 0x2026] diff --git a/macosx/tkMacOSXMouseEvent.c b/macosx/tkMacOSXMouseEvent.c index 89cdf1b..b18b4ef 100644 --- a/macosx/tkMacOSXMouseEvent.c +++ b/macosx/tkMacOSXMouseEvent.c @@ -130,7 +130,8 @@ enum { } /* - * Make sure tkwin is the toplevel which should receive the event. + * If we still don't have a window, try using the toplevel that + * manages the NSWindow. */ if (!tkwin) { @@ -138,10 +139,15 @@ enum { tkwin = (Tk_Window) winPtr; } if (!tkwin) { + + /* + * We can't find a window for this event. We have to ignore it. + */ + #ifdef TK_MAC_DEBUG_EVENTS TkMacOSXDbgMsg("tkwin == NULL"); #endif - return theEvent; /* Give up. No window for this event. */ + return theEvent; } /* @@ -174,13 +180,25 @@ enum { * coordinates. */ - local.x -= winPtr->wmInfoPtr->xInParent; - local.y -= winPtr->wmInfoPtr->yInParent; + if (Tk_IsEmbedded(winPtr)) { + TkWindow *contPtr = TkpGetOtherWindow(winPtr); + if (Tk_IsTopLevel(contPtr)) { + local.x -= contPtr->wmInfoPtr->xInParent; + local.y -= contPtr->wmInfoPtr->yInParent; + } else { + TkWindow *topPtr = TkMacOSXGetHostToplevel(winPtr)->winPtr; + local.x -= (topPtr->wmInfoPtr->xInParent + contPtr->changes.x); + local.y -= (topPtr->wmInfoPtr->yInParent + contPtr->changes.y); + } + } else { + local.x -= winPtr->wmInfoPtr->xInParent; + local.y -= winPtr->wmInfoPtr->yInParent; + } /* - * Find the containing Tk window, and convert local into the coordinates - * of the Tk window. (The converted local coordinates are only needed - * for scrollwheel events.) + * Use the toplevel coordinates to find the containing Tk window. Then + * convert local into the coordinates of that window. (The converted + * local coordinates are only needed for scrollwheel events.) */ int win_x, win_y; 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/tkMacOSXScrlbr.c b/macosx/tkMacOSXScrlbr.c index 66619c2..dff6cc9 100644 --- a/macosx/tkMacOSXScrlbr.c +++ b/macosx/tkMacOSXScrlbr.c @@ -214,12 +214,20 @@ static void drawMacScrollbar( CGContextStrokeLineSegments(context, outer, 2); /* - * Do not display the thumb unless scrolling is possible. + * Do not display the thumb unless scrolling is possible, in accordance + * with macOS behavior. + * + * Native scrollbars and Ttk scrollbars are always 15 pixels wide, but we + * allow Tk scrollbars to have any width, even if it looks bad. To prevent + * sporadic assertion errors when drawing skinny thumbs we must make sure + * the radius is at most half the width. */ if (scrollPtr->firstFraction > 0.0 || scrollPtr->lastFraction < 1.0) { CGRect thumbBounds = {thumbOrigin, thumbSize}; - path = CGPathCreateWithRoundedRect(thumbBounds, 4, 4, NULL); + int width = scrollPtr->vertical ? thumbSize.width : thumbSize.height; + int radius = width >= 8 ? 4 : width >> 1; + path = CGPathCreateWithRoundedRect(thumbBounds, radius, radius, NULL); CGContextBeginPath(context); CGContextAddPath(context, path); if (msPtr->info.trackInfo.scrollbar.pressState != 0) { 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/tkMacOSXTest.c b/macosx/tkMacOSXTest.c index 09736e6..c353efe 100644 --- a/macosx/tkMacOSXTest.c +++ b/macosx/tkMacOSXTest.c @@ -161,7 +161,7 @@ PressButtonObjCmd( if (screens && [screens count]) { ScreenHeight = [[screens objectAtIndex:0] frame].size.height; } - + if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "x y"); return TCL_ERROR; @@ -186,34 +186,34 @@ PressButtonObjCmd( loc.y = ScreenHeight - y; wNum = 0; CGWarpMouseCursorPosition(pt); - motion = [NSEvent mouseEventWithType:NSMouseMoved + motion = [NSEvent mouseEventWithType:NSMouseMoved location:loc - modifierFlags:0 - timestamp:GetCurrentEventTime() + modifierFlags:0 + timestamp:GetCurrentEventTime() windowNumber:wNum - context:nil + context:nil eventNumber:0 - clickCount:1 + clickCount:1 pressure:0.0]; [NSApp postEvent:motion atStart:NO]; - press = [NSEvent mouseEventWithType:NSLeftMouseDown + press = [NSEvent mouseEventWithType:NSLeftMouseDown location:loc - modifierFlags:0 - timestamp:GetCurrentEventTime() + modifierFlags:0 + timestamp:GetCurrentEventTime() windowNumber:wNum - context:nil + context:nil eventNumber:1 - clickCount:1 + clickCount:1 pressure:0.0]; [NSApp postEvent:press atStart:NO]; release = [NSEvent mouseEventWithType:NSLeftMouseUp location:loc - modifierFlags:0 - timestamp:GetCurrentEventTime() + modifierFlags:0 + timestamp:GetCurrentEventTime() windowNumber:wNum - context:nil + context:nil eventNumber:2 - clickCount:1 + clickCount:1 pressure:0.0]; [NSApp postEvent:release atStart:NO]; 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 /* diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 70e9cd9..4e53282 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -2518,6 +2518,7 @@ WmIconphotoCmd( { Tk_Image tk_icon; int width, height, isDefault = 0; + NSImage *newIcon = NULL; if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, @@ -2563,12 +2564,16 @@ WmIconphotoCmd( return TCL_ERROR; } - NSImage *newIcon; Tk_SizeOfImage(tk_icon, &width, &height); - newIcon = TkMacOSXGetNSImageWithTkImage(winPtr->display, tk_icon, - width, height); + if (width != 0 && height != 0) { + newIcon = TkMacOSXGetNSImageWithTkImage(winPtr->display, tk_icon, + width, height); + } Tk_FreeImage(tk_icon); if (newIcon == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "failed to create an iconphoto with image \"%s\"", icon)); + Tcl_SetErrorCode(interp, "TK", "WM", "ICONPHOTO", "IMAGE", NULL); return TCL_ERROR; } [NSApp setApplicationIconImage: newIcon]; @@ -3311,18 +3316,18 @@ WmStackorderCmd( if (objc == 3) { windows = TkWmStackorderToplevel(winPtr); - if (windows == NULL) { - Tcl_Panic("TkWmStackorderToplevel failed"); - } - - resultObj = Tcl_NewObj(); - for (windowPtr = windows; *windowPtr ; windowPtr++) { - Tcl_ListObjAppendElement(NULL, resultObj, + if (windows != NULL) { + resultObj = Tcl_NewObj(); + for (windowPtr = windows; *windowPtr ; windowPtr++) { + Tcl_ListObjAppendElement(NULL, resultObj, TkNewWindowObj((Tk_Window) *windowPtr)); + } + Tcl_SetObjResult(interp, resultObj); + ckfree(windows); + return TCL_OK; + } else { + return TCL_ERROR; } - Tcl_SetObjResult(interp, resultObj); - ckfree(windows); - return TCL_OK; } else { TkWindow *winPtr2; int index1 = -1, index2 = -1, result; @@ -6623,7 +6628,7 @@ WmStackorderToplevelWrapperMap( Tcl_HashEntry *hPtr; int newEntry; - if (Tk_IsMapped(winPtr) && Tk_IsTopLevel(winPtr) + if (Tk_IsMapped(winPtr) && Tk_IsTopLevel(winPtr) && !Tk_IsEmbedded(winPtr) && (winPtr->display == display)) { hPtr = Tcl_CreateHashEntry(table, (char*) TkMacOSXDrawableWindow(winPtr->window), &newEntry); @@ -6644,8 +6649,8 @@ WmStackorderToplevelWrapperMap( * This procedure returns the stack order of toplevel windows. * * Results: - * An array of pointers to tk window objects in stacking order or else - * NULL if there was an error. + * A NULL terminated array of pointers to tk window objects in stacking + * order or else NULL if there was an error. * * Side effects: * None. @@ -6660,57 +6665,24 @@ TkWmStackorderToplevel( TkWindow *childWinPtr, **windows, **windowPtr; Tcl_HashTable table; Tcl_HashEntry *hPtr; - Tcl_HashSearch search; - - /* - * Map mac windows to a TkWindow of the wrapped toplevel. - */ - - Tcl_InitHashTable(&table, TCL_ONE_WORD_KEYS); - WmStackorderToplevelWrapperMap(parentPtr, parentPtr->display, &table); - - windows = ckalloc((table.numEntries+1) * sizeof(TkWindow *)); - - /* - * Special cases: If zero or one toplevels were mapped there is no need to - * enumerate Windows. - */ - - switch (table.numEntries) { - case 0: - windows[0] = NULL; - goto done; - case 1: - hPtr = Tcl_FirstHashEntry(&table, &search); - windows[0] = Tcl_GetHashValue(hPtr); - windows[1] = NULL; - goto done; - } - NSArray *macWindows = [NSApp orderedWindows]; + NSArray* backToFront = [[macWindows reverseObjectEnumerator] allObjects]; NSInteger windowCount = [macWindows count]; - if (!windowCount) { - ckfree(windows); - windows = NULL; - } else { - windowPtr = windows + table.numEntries; - *windowPtr-- = NULL; - for (NSWindow *w in macWindows) { + windows = windowPtr = ckalloc((windowCount + 1) * sizeof(TkWindow *)); + if (windows != NULL) { + Tcl_InitHashTable(&table, TCL_ONE_WORD_KEYS); + WmStackorderToplevelWrapperMap(parentPtr, parentPtr->display, &table); + for (NSWindow *w in backToFront) { hPtr = Tcl_FindHashEntry(&table, (char*) w); if (hPtr != NULL) { childWinPtr = Tcl_GetHashValue(hPtr); - *windowPtr-- = childWinPtr; + *windowPtr++ = childWinPtr; } } - if (windowPtr != windows-1) { - Tcl_Panic("num matched toplevel windows does not equal num " - "children"); - } + *windowPtr = NULL; + Tcl_DeleteHashTable(&table); } - - done: - Tcl_DeleteHashTable(&table); return windows; } diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c index 3d88ef7..52534ce 100644 --- a/macosx/ttkMacOSXTheme.c +++ b/macosx/ttkMacOSXTheme.c @@ -1409,8 +1409,10 @@ static void ButtonElementDraw( } else if (info.kind == kThemePushButton && (state & TTK_STATE_PRESSED)) { bounds.size.height += 2; - GradientFillRoundedRectangle(dc.context, bounds, 4, - pressedPushButtonGradient, 2); + if ([NSApp macMinorVersion] > 8) { + GradientFillRoundedRectangle(dc.context, bounds, 4, + pressedPushButtonGradient, 2); + } } else { /* @@ -1859,18 +1861,17 @@ static void ComboboxElementDraw( BEGIN_DRAWING(d) bounds.origin.y += 1; if (TkMacOSXInDarkMode(tkwin)) { - bounds.size.height += 1; + bounds.size.height += 1; DrawDarkButton(bounds, info.kind, state, dc.context); - } else if ([NSApp macMinorVersion] > 8) { - if ((state & TTK_STATE_BACKGROUND) && - !(state & TTK_STATE_DISABLED)) { + } else if ([NSApp macMinorVersion] > 8) { + if ((state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED)) { NSColor *background = [NSColor textBackgroundColor]; CGRect innerBounds = CGRectInset(bounds, 1, 2); SolidFillRoundedRectangle(dc.context, innerBounds, 4, background); } - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, - NULL); } + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); END_DRAWING } @@ -2095,7 +2096,7 @@ static void TrackElementDraw( Tcl_GetDoubleFromObj(NULL, elem->fromObj, &from); Tcl_GetDoubleFromObj(NULL, elem->toObj, &to); Tcl_GetDoubleFromObj(NULL, elem->valueObj, &value); - factor = RangeToFactor(to - from); + factor = RangeToFactor(to); HIThemeTrackDrawInfo info = { .version = 0, |