Index: doc/tk_mac.n ================================================================== --- doc/tk_mac.n +++ doc/tk_mac.n @@ -11,10 +11,12 @@ '\" Note: do not modify the .SH NAME line immediately below! .SH NAME tk::mac \- Access Mac-Specific Functionality on OS X from Tk .SH SYNOPSIS .nf +\fB::tk::mac::DoScriptFile\fR +\fB::tk::mac::DoScriptText\fR \fB::tk::mac::ShowPreferences\fR \fB::tk::mac::OpenApplication\fR \fB::tk::mac::ReopenApplication\fR \fB::tk::mac::OpenDocument \fIfile...\fR \fB::tk::mac::PrintDocument \fIfile...\fR @@ -42,10 +44,24 @@ The Aqua/Mac OS X application environment defines a number of additional events that applications should respond to. These events are mapped by Tk to calls to commands in the \fB::tk::mac\fR namespace; unless otherwise noted, if the command is absent, no action will be taken. .TP +\fB::tk::mac::DoScriptFile\fR +. +The default Apple Event handler for AEDoScriptHandler. This command, +if defined, executes a Tcl file when an AppleScript sends a +.QW "do script" +command to Wish with a file path as a parameter. +.TP +\fB::tk::mac::DoScriptText\fR +. +The default Apple Event handler for AEDoScriptHandler. This command, +if defined, executes Tcl code when an AppleScript sends a +.QW "do script" +command to Wish with Tcl code or a Tcl procedure as a parameter. +.TP \fB::tk::mac::ShowPreferences\fR . The default Apple Event handler for kAEShowPreferences, .QW pref . The application menu Index: macosx/tkMacOSXScrlbr.c ================================================================== --- macosx/tkMacOSXScrlbr.c +++ macosx/tkMacOSXScrlbr.c @@ -6,28 +6,27 @@ * * Copyright (c) 1996 by Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen * Copyright (c) 2015 Kevin Walzer/WordTech Commununications LLC. - * Copyright (c) 2018 Marc Culler + * Copyright (c) 2018-2019 Marc Culler * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkInt.h" #include "tkScrollbar.h" #include "tkMacOSXPrivate.h" -#define MIN_SCROLLBAR_VALUE 0 - /* * Minimum slider length, in pixels (designed to make sure that the slider is * always easy to grab with the mouse). */ -#define MIN_SLIDER_LENGTH 5 +#define MIN_SLIDER_LENGTH 18 +#define MIN_GAP 4 /* * Borrowed from ttkMacOSXTheme.c to provide appropriate scaling. */ @@ -86,11 +85,12 @@ int minHeight, topArrowHeight, bottomArrowHeight; NSControlSize controlSize; } ScrollbarMetrics; static ScrollbarMetrics metrics = { - 15, 54, 26, 14, 14, kControlSizeNormal /* kThemeScrollBarMedium */ + /* kThemeScrollBarMedium */ + 15, MIN_SLIDER_LENGTH, 26, 14, 14, kControlSizeNormal }; /* * Declarations of static functions defined later in this file: */ @@ -158,10 +158,84 @@ * Draws a scrollbar on the screen. * *-------------------------------------------------------------- */ +#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080 + +/* + * This stand-alone drawing function is used on macOS 10.9 and newer because + * the HIToolbox does not draw the scrollbar thumb at the expected size on + * those systems. The thumb is drawn too large, causing a mouse click on the + * thumb to be interpreted as a mouse click in the trough. + */ + +static void drawMacScrollbar( + TkScrollbar *scrollPtr, + MacScrollbar *msPtr, + CGContextRef context) +{ + MacDrawable *macWin = (MacDrawable *) Tk_WindowId(scrollPtr->tkwin); + NSView *view = TkMacOSXDrawableView(macWin); + CGPathRef path; + CGPoint inner[2], outer[2], thumbOrigin; + CGSize thumbSize; + CGRect troughBounds = msPtr->info.bounds; + troughBounds.origin.y = [view bounds].size.height - + (troughBounds.origin.y + troughBounds.size.height); + if (scrollPtr->vertical) { + thumbOrigin.x = troughBounds.origin.x + MIN_GAP; + thumbOrigin.y = troughBounds.origin.y + scrollPtr->sliderFirst; + thumbSize.width = troughBounds.size.width - 2*MIN_GAP + 1; + thumbSize.height = scrollPtr->sliderLast - scrollPtr->sliderFirst; + inner[0] = troughBounds.origin; + inner[1] = CGPointMake(inner[0].x, + inner[0].y + troughBounds.size.height); + outer[0] = CGPointMake(inner[0].x + troughBounds.size.width - 1, + inner[0].y); + outer[1] = CGPointMake(outer[0].x, inner[1].y); + } else { + thumbOrigin.x = troughBounds.origin.x + scrollPtr->sliderFirst; + thumbOrigin.y = troughBounds.origin.y + MIN_GAP; + thumbSize.width = scrollPtr->sliderLast - scrollPtr->sliderFirst; + thumbSize.height = troughBounds.size.height - 2*MIN_GAP + 1; + inner[0] = troughBounds.origin; + inner[1] = CGPointMake(inner[0].x + troughBounds.size.width, + inner[0].y + 1); + outer[0] = CGPointMake(inner[0].x, + inner[0].y + troughBounds.size.height); + outer[1] = CGPointMake(inner[1].x, outer[0].y); + } + CGContextSetShouldAntialias(context, false); + CGContextSetGrayFillColor(context, 250.0 / 255, 1.0); + CGContextFillRect(context, troughBounds); + CGContextSetGrayStrokeColor(context, 232.0 / 255, 1.0); + CGContextStrokeLineSegments(context, inner, 2); + CGContextSetGrayStrokeColor(context, 238.0 / 255, 1.0); + CGContextStrokeLineSegments(context, outer, 2); + + /* + * Do not display the thumb unless scrolling is possible. + */ + + if (scrollPtr->firstFraction > 0.0 || scrollPtr->lastFraction < 1.0) { + CGRect thumbBounds = {thumbOrigin, thumbSize}; + path = CGPathCreateWithRoundedRect(thumbBounds, 4, 4, NULL); + CGContextBeginPath(context); + CGContextAddPath(context, path); + if (msPtr->info.trackInfo.scrollbar.pressState != 0) { + CGContextSetGrayFillColor(context, 133.0 / 255, 1.0); + } else { + CGContextSetGrayFillColor(context, 200.0 / 255, 1.0); + } + CGContextSetShouldAntialias(context, true); + CGContextFillPath(context); + CFRelease(path); + } +} +#endif + void TkpDisplayScrollbar( ClientData clientData) /* Information about window. */ { register TkScrollbar *scrollPtr = clientData; @@ -183,10 +257,14 @@ || (macWin->flags & TK_DO_NOT_DRAW) || !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { return; } + /* + * Transform NSView coordinates to CoreGraphics coordinates. + */ + CGFloat viewHeight = [view bounds].size.height; CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, .ty = viewHeight @@ -227,17 +305,26 @@ UpdateControlValues(scrollPtr); if (SNOW_LEOPARD_STYLE) { HIThemeDrawTrack(&msPtr->info, 0, dc.context, - kHIThemeOrientationInverted); - } else { + kHIThemeOrientationInverted); + } else if ([NSApp macMinorVersion] <= 8) { HIThemeDrawTrack(&msPtr->info, 0, dc.context, - kHIThemeOrientationNormal); + kHIThemeOrientationNormal); + } else { +#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080 + + /* + * Switch back to NSView coordinates and draw a modern scrollbar. + */ + + CGContextConcatCTM(dc.context, t); + drawMacScrollbar(scrollPtr, msPtr, dc.context); +#endif } TkMacOSXRestoreDrawingContext(&dc); - scrollPtr->flags &= ~REDRAW_PENDING; } /* *---------------------------------------------------------------------- @@ -295,27 +382,28 @@ } scrollPtr->sliderFirst = fieldLength*scrollPtr->firstFraction; scrollPtr->sliderLast = fieldLength*scrollPtr->lastFraction; /* - * Adjust the slider so that some piece of it is always displayed in the - * scrollbar and so that it has at least a minimal width (so it can be - * grabbed with the mouse). + * Adjust the slider so that it has at least a minimal size and so there + * is a small gap on either end which can be used to scroll by one page. */ + if (scrollPtr->sliderFirst < MIN_GAP) { + scrollPtr->sliderFirst = MIN_GAP; + scrollPtr->sliderLast += MIN_GAP; + } + if (scrollPtr->sliderLast > fieldLength - MIN_GAP) { + scrollPtr->sliderLast = fieldLength - MIN_GAP; + scrollPtr->sliderFirst -= MIN_GAP; + } if (scrollPtr->sliderFirst > fieldLength - MIN_SLIDER_LENGTH) { scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH; } - if (scrollPtr->sliderFirst < 0) { - scrollPtr->sliderFirst = 0; - } if (scrollPtr->sliderLast < scrollPtr->sliderFirst + MIN_SLIDER_LENGTH) { scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH; } - if (scrollPtr->sliderLast > fieldLength) { - scrollPtr->sliderLast = fieldLength; - } scrollPtr->sliderFirst += -scrollPtr->arrowLength + scrollPtr->inset; scrollPtr->sliderLast += scrollPtr->inset; /* * Register the desired geometry for the window. Leave enough space for the @@ -509,11 +597,11 @@ contrlRect = NSRectToCGRect(frame); msPtr->info.bounds = contrlRect; width = contrlRect.size.width; - height = contrlRect.size.height; + height = contrlRect.size.height - scrollPtr->arrowLength; /* * Ensure we set scrollbar control bounds only once all size adjustments * have been computed. */ @@ -533,25 +621,23 @@ * area in the content area being scrolled. The maximum value of the * control is therefore the dimension of the content area less the size of * the view area. */ - double maximum = 100, factor = RangeToFactor(maximum); - + double factor = RangeToFactor(100.0); dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction) * factor; - msPtr->info.max = MIN_SCROLLBAR_VALUE + factor - dViewSize; + msPtr->info.max = factor - dViewSize; msPtr->info.trackInfo.scrollbar.viewsize = dViewSize; if (scrollPtr->vertical) { if (SNOW_LEOPARD_STYLE) { msPtr->info.value = factor * scrollPtr->firstFraction; } else { msPtr->info.value = msPtr->info.max - factor * scrollPtr->firstFraction; } } else { - msPtr->info.value = MIN_SCROLLBAR_VALUE + - factor * scrollPtr->firstFraction; + msPtr->info.value = factor * scrollPtr->firstFraction; } if ((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0) || height <= metrics.minHeight) { msPtr->info.enableState = kThemeTrackHideTrack; @@ -614,10 +700,11 @@ case BOTTOM_GAP: msPtr->info.trackInfo.scrollbar.pressState = kThemeBottomTrackPressed; break; case TOP_ARROW: + /* * This looks wrong and the docs say it is wrong but it works. */ msPtr->info.trackInfo.scrollbar.pressState = @@ -645,10 +732,11 @@ msPtr->mouseOver = false; if (!msPtr->buttonDown) { msPtr->info.trackInfo.scrollbar.pressState = 0; } } + TkScrollbarEventuallyRedraw(scrollPtr); return TCL_OK; } /* *-------------------------------------------------------------- Index: macosx/ttkMacOSXTheme.c ================================================================== --- macosx/ttkMacOSXTheme.c +++ macosx/ttkMacOSXTheme.c @@ -2345,11 +2345,17 @@ { ScrollbarElement *scrollbar = elementRecord; int orientation = TTK_ORIENT_HORIZONTAL; Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); - *minHeight = *minWidth = 8; + if (orientation == TTK_ORIENT_VERTICAL) { + *minHeight = 18; + *minWidth = 8; + } else { + *minHeight = 8; + *minWidth = 18; + } } static void ThumbElementDraw( void *clientData, void *elementRecord, @@ -2366,19 +2372,15 @@ /* * In order to make ttk scrollbars work correctly it is necessary to be * able to display the thumb element at the size and location which the ttk * scrollbar widget requests. The algorithm that HIToolbox uses to * determine the thumb geometry from the input values of min, max, value - * and viewSize is, of course, undocumented. And this turns out to be a - * hard reverse engineering problem. A seemingly natural algorithm is - * implemented below, but it does not correctly compute the same thumb - * geometry as HITools (which also apparently does not agree with - * NSScrollbar). This code uses that algorithm for older OS versions, + * and viewSize is undocumented. A seemingly natural algorithm is + * implemented below. This code uses that algorithm for older OS versions, * because using HITools also handles drawing the buttons and 3D thumb used - * on those systems. The incorrect geometry is annoying but not completely - * unusable. For newer systems the cleanest approach is to just draw the - * thumb directly. + * on those systems. For newer systems the cleanest approach is to just + * draw the thumb directly. */ if ([NSApp macMinorVersion] > 8) { CGRect thumbBounds = BoxToRect(d, b); NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; @@ -2402,11 +2404,11 @@ count: 4]; BEGIN_DRAWING(d) SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor); END_DRAWING } else { - double thumbSize, trackSize, visibleSize, viewSize; + double thumbSize, trackSize, visibleSize, factor, fraction; MacDrawable *macWin = (MacDrawable *) Tk_WindowId(tkwin); CGRect troughBounds = {{macWin->xOff, macWin->yOff}, {Tk_Width(tkwin), Tk_Height(tkwin)}}; /* @@ -2416,32 +2418,36 @@ * an arbitrary scale factor. To avoid roundoff error we scale so * that the viewSize is a large float which is smaller than the * largest int. */ - viewSize = RangeToFactor(100.0); HIThemeTrackDrawInfo info = { .version = 0, .bounds = troughBounds, .min = 0, .attributes = kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost, .enableState = kThemeTrackActive }; - info.trackInfo.scrollbar.viewsize = viewSize * .8; + factor = RangeToFactor(100.0); if (orientation == TTK_ORIENT_HORIZONTAL) { trackSize = troughBounds.size.width; thumbSize = b.width; - visibleSize = (thumbSize / trackSize) * viewSize; - info.max = viewSize - visibleSize; - info.value = info.max * (b.x / (trackSize - thumbSize)); + fraction = b.x / trackSize; } else { + trackSize = troughBounds.size.height; thumbSize = b.height; - trackSize = troughBounds.size.height; - visibleSize = (thumbSize / trackSize) * viewSize; - info.max = viewSize - visibleSize; - info.value = info.max * (b.y / (trackSize - thumbSize)); + fraction = b.y / trackSize; + } + visibleSize = (thumbSize / trackSize) * factor; + info.max = factor - visibleSize; + info.trackInfo.scrollbar.viewsize = visibleSize; + if ([NSApp macMinorVersion] < 8 || + orientation == TTK_ORIENT_HORIZONTAL) { + info.value = factor * fraction; + } else { + info.value = info.max - factor * fraction; } if ((state & TTK_STATE_PRESSED) || (state & TTK_STATE_HOVER)) { info.trackInfo.scrollbar.pressState = kThemeThumbPressed; } else { @@ -3115,12 +3121,11 @@ Ttk_RegisterElementSpec(themePtr, "sizegrip", &SizegripElementSpec, 0); /* * <> - * In some themes the Layouts for a progress bar has a trough element and - *a + * In some themes the Layouts for a progress bar has a trough element and a * pbar element. But in our case the appearance manager draws both parts * of the progress bar, so we just have a single element called ".track". */ Ttk_RegisterElementSpec(themePtr, "Progressbar.track", &PbarElementSpec,