1 /*
2  * tkMacOSXScrollbar.c --
3  *
4  *	This file implements the Macintosh specific portion of the scrollbar
5  *	widget.
6  *
7  * Copyright (c) 1996 by Sun Microsystems, Inc.
8  * Copyright 2001-2009, Apple Inc.
9  * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
10  * Copyright (c) 2015 Kevin Walzer/WordTech Commununications LLC.
11  * Copyright (c) 2018-2019 Marc Culler
12  *
13  * See the file "license.terms" for information on usage and redistribution
14  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15  */
16 
17 #include "tkInt.h"
18 #include "tkScrollbar.h"
19 #include "tkMacOSXPrivate.h"
20 
21 /*
22  * Minimum slider length, in pixels (designed to make sure that the slider is
23  * always easy to grab with the mouse).
24  */
25 
26 #define MIN_SLIDER_LENGTH	18
27 #define MIN_GAP			4
28 
29 /*
30  * Borrowed from ttkMacOSXTheme.c to provide appropriate scaling.
31  */
32 
33 #ifdef __LP64__
34 #define RangeToFactor(maximum)	(((double) (INT_MAX >> 1)) / (maximum))
35 #else
36 #define RangeToFactor(maximum)	(((double) (LONG_MAX >> 1)) / (maximum))
37 #endif /* __LP64__ */
38 
39 /*
40  * Apple reversed the scroll direction with the release of OSX 10.7 Lion.
41  */
42 
43 #define SNOW_LEOPARD_STYLE	(NSAppKitVersionNumber < 1138)
44 
45 /*
46  * Declaration of an extended scrollbar structure with Mac specific additions.
47  */
48 
49 typedef struct MacScrollbar {
50     TkScrollbar information;	/* Generic scrollbar info. */
51     GC troughGC;		/* For drawing trough. */
52     GC copyGC;			/* Used for copying from pixmap onto screen. */
53     Bool buttonDown;            /* Is the mouse button down? */
54     Bool mouseOver;             /* Is the pointer over the scrollbar. */
55     HIThemeTrackDrawInfo info;	/* Controls how the scrollbar is drawn. */
56 } MacScrollbar;
57 
58 /* Used to initialize a MacScrollbar's info field. */
59 HIThemeTrackDrawInfo defaultInfo = {
60     .version = 0,
61     .min = 0.0,
62     .max = 100.0,
63     .attributes = kThemeTrackShowThumb,
64 };
65 
66 /*
67  * The class procedure table for the scrollbar widget. All fields except size
68  * are left initialized to NULL, which should happen automatically since the
69  * variable is declared at this scope.
70  */
71 
72 const Tk_ClassProcs tkpScrollbarProcs = {
73     sizeof(Tk_ClassProcs),	/* size */
74     NULL,			/* worldChangedProc */
75     NULL,			/* createProc */
76     NULL			/* modalProc */
77 };
78 
79 /*
80  * Information on scrollbar layout, metrics, and draw info.
81  */
82 
83 typedef struct ScrollbarMetrics {
84     SInt32 width, minThumbHeight;
85     int minHeight, topArrowHeight, bottomArrowHeight;
86     NSControlSize controlSize;
87 } ScrollbarMetrics;
88 
89 static ScrollbarMetrics metrics = {
90     /* kThemeScrollBarMedium */
91     15, MIN_SLIDER_LENGTH, 26, 14, 14, kControlSizeNormal
92 };
93 
94 /*
95  * Declarations of static functions defined later in this file:
96  */
97 
98 static void		ScrollbarEventProc(ClientData clientData,
99 			    XEvent *eventPtr);
100 static int		ScrollbarEvent(TkScrollbar *scrollPtr,
101 			    XEvent *eventPtr);
102 static void		UpdateControlValues(TkScrollbar *scrollPtr);
103 
104 /*
105  *----------------------------------------------------------------------
106  *
107  * TkpCreateScrollbar --
108  *
109  *	Allocate a new TkScrollbar structure.
110  *
111  * Results:
112  *	Returns a newly allocated TkScrollbar structure.
113  *
114  * Side effects:
115  *	Registers an event handler for the widget.
116  *
117  *----------------------------------------------------------------------
118  */
119 
120 TkScrollbar *
TkpCreateScrollbar(Tk_Window tkwin)121 TkpCreateScrollbar(
122     Tk_Window tkwin)
123 {
124     MacScrollbar *scrollPtr = (MacScrollbar *)ckalloc(sizeof(MacScrollbar));
125 
126     scrollPtr->troughGC = NULL;
127     scrollPtr->copyGC = NULL;
128     scrollPtr->info = defaultInfo;
129     scrollPtr->buttonDown = false;
130 
131     Tk_CreateEventHandler(tkwin,
132 	    ExposureMask        |
133 	    StructureNotifyMask |
134 	    FocusChangeMask     |
135 	    ButtonPressMask     |
136 	    ButtonReleaseMask   |
137 	    EnterWindowMask     |
138 	    LeaveWindowMask     |
139 	    VisibilityChangeMask,
140 	    ScrollbarEventProc, scrollPtr);
141 
142     return (TkScrollbar *) scrollPtr;
143 }
144 
145 /*
146  *--------------------------------------------------------------
147  *
148  * TkpDisplayScrollbar --
149  *
150  *	This procedure redraws the contents of a scrollbar window. It is
151  *	invoked as a do-when-idle handler, so it only runs when there's nothing
152  *	else for the application to do.
153  *
154  * Results:
155  *	None.
156  *
157  * Side effects:
158  *	Draws a scrollbar on the screen.
159  *
160  *--------------------------------------------------------------
161  */
162 
163 #if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
164 
165 /*
166  * This stand-alone drawing function is used on macOS 10.9 and newer because
167  * the HIToolbox does not draw the scrollbar thumb at the expected size on
168  * those systems.  The thumb is drawn too large, causing a mouse click on the
169  * thumb to be interpreted as a mouse click in the trough.
170  */
171 
drawMacScrollbar(TkScrollbar * scrollPtr,MacScrollbar * msPtr,CGContextRef context)172 static void drawMacScrollbar(
173     TkScrollbar *scrollPtr,
174     MacScrollbar *msPtr,
175     CGContextRef context)
176 {
177     Drawable d = Tk_WindowId(scrollPtr->tkwin);
178     NSView *view = TkMacOSXGetNSViewForDrawable(d);
179     CGPathRef path;
180     CGPoint inner[2], outer[2], thumbOrigin;
181     CGSize thumbSize;
182     CGRect troughBounds = msPtr->info.bounds;
183     troughBounds.origin.y = [view bounds].size.height -
184 	(troughBounds.origin.y + troughBounds.size.height);
185     if (scrollPtr->vertical) {
186 	thumbOrigin.x = troughBounds.origin.x + MIN_GAP;
187 	thumbOrigin.y = troughBounds.origin.y + scrollPtr->sliderFirst;
188 	thumbSize.width = troughBounds.size.width - 2*MIN_GAP + 1;
189 	thumbSize.height = scrollPtr->sliderLast - scrollPtr->sliderFirst;
190 	inner[0] = troughBounds.origin;
191 	inner[1] = CGPointMake(inner[0].x,
192 			       inner[0].y + troughBounds.size.height);
193 	outer[0] = CGPointMake(inner[0].x + troughBounds.size.width - 1,
194 			       inner[0].y);
195 	outer[1] = CGPointMake(outer[0].x, inner[1].y);
196     } else {
197 	thumbOrigin.x = troughBounds.origin.x + scrollPtr->sliderFirst;
198 	thumbOrigin.y = troughBounds.origin.y + MIN_GAP;
199 	thumbSize.width = scrollPtr->sliderLast - scrollPtr->sliderFirst;
200 	thumbSize.height = troughBounds.size.height - 2*MIN_GAP + 1;
201 	inner[0] = troughBounds.origin;
202 	inner[1] = CGPointMake(inner[0].x + troughBounds.size.width,
203 			       inner[0].y + 1);
204 	outer[0] = CGPointMake(inner[0].x,
205 			       inner[0].y + troughBounds.size.height);
206 	outer[1] = CGPointMake(inner[1].x, outer[0].y);
207     }
208     CGContextSetShouldAntialias(context, false);
209     CGContextSetGrayFillColor(context, 250.0 / 255, 1.0);
210     CGContextFillRect(context, troughBounds);
211     CGContextSetGrayStrokeColor(context, 232.0 / 255, 1.0);
212     CGContextStrokeLineSegments(context, inner, 2);
213     CGContextSetGrayStrokeColor(context, 238.0 / 255, 1.0);
214     CGContextStrokeLineSegments(context, outer, 2);
215 
216     /*
217      * Do not display the thumb unless scrolling is possible, in accordance
218      * with macOS behavior.
219      *
220      * Native scrollbars and Ttk scrollbars are always 15 pixels wide, but we
221      * allow Tk scrollbars to have any width, even if it looks bad. To prevent
222      * sporadic assertion errors when drawing skinny thumbs we must make sure
223      * the radius is at most half the width.
224      */
225 
226     if (scrollPtr->firstFraction > 0.0 || scrollPtr->lastFraction < 1.0) {
227 	CGRect thumbBounds = {thumbOrigin, thumbSize};
228 	int width = scrollPtr->vertical ? thumbSize.width : thumbSize.height;
229 	int radius = width >= 8 ? 4 : width >> 1;
230 	path = CGPathCreateWithRoundedRect(thumbBounds, radius, radius, NULL);
231 	CGContextBeginPath(context);
232 	CGContextAddPath(context, path);
233 	if (msPtr->info.trackInfo.scrollbar.pressState != 0) {
234 	    CGContextSetGrayFillColor(context, 133.0 / 255, 1.0);
235 	} else {
236 	    CGContextSetGrayFillColor(context, 200.0 / 255, 1.0);
237 	}
238 	CGContextSetShouldAntialias(context, true);
239 	CGContextFillPath(context);
240 	CFRelease(path);
241     }
242 }
243 #endif
244 
245 void
TkpDisplayScrollbar(ClientData clientData)246 TkpDisplayScrollbar(
247     ClientData clientData)	/* Information about window. */
248 {
249     TkScrollbar *scrollPtr = (TkScrollbar *)clientData;
250     MacScrollbar *msPtr = (MacScrollbar *) scrollPtr;
251     Tk_Window tkwin = scrollPtr->tkwin;
252     TkWindow *winPtr = (TkWindow *) tkwin;
253     TkMacOSXDrawingContext dc;
254 
255     scrollPtr->flags &= ~REDRAW_PENDING;
256 
257     if (tkwin == NULL || !Tk_IsMapped(tkwin)) {
258 	return;
259     }
260 
261     MacDrawable *macWin = (MacDrawable *)winPtr->window;
262     NSView *view = TkMacOSXGetNSViewForDrawable(macWin);
263 
264     if ((view == NULL)
265 	    || (macWin->flags & TK_DO_NOT_DRAW)
266 	    || !TkMacOSXSetupDrawingContext((Drawable)macWin, NULL, &dc)) {
267 	return;
268     }
269 
270     /*
271      * Transform NSView coordinates to CoreGraphics coordinates.
272      */
273 
274     CGFloat viewHeight = [view bounds].size.height;
275     CGAffineTransform t = {
276 	.a = 1, .b = 0,
277 	.c = 0, .d = -1,
278 	.tx = 0, .ty = viewHeight
279     };
280 
281     CGContextConcatCTM(dc.context, t);
282 
283     /*
284      * Draw a 3D rectangle to provide a base for the native scrollbar.
285      */
286 
287     if (scrollPtr->highlightWidth != 0) {
288     	GC fgGC, bgGC;
289 
290     	bgGC = Tk_GCForColor(scrollPtr->highlightBgColorPtr, (Pixmap) macWin);
291     	if (scrollPtr->flags & GOT_FOCUS) {
292     	    fgGC = Tk_GCForColor(scrollPtr->highlightColorPtr, (Pixmap) macWin);
293     	} else {
294     	    fgGC = bgGC;
295     	}
296     	TkpDrawHighlightBorder(tkwin, fgGC, bgGC, scrollPtr->highlightWidth,
297     		(Pixmap) macWin);
298     }
299 
300     Tk_Draw3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder,
301 	    scrollPtr->highlightWidth, scrollPtr->highlightWidth,
302 	    Tk_Width(tkwin) - 2*scrollPtr->highlightWidth,
303 	    Tk_Height(tkwin) - 2*scrollPtr->highlightWidth,
304 	    scrollPtr->borderWidth, scrollPtr->relief);
305     Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder,
306 	    scrollPtr->inset, scrollPtr->inset,
307 	    Tk_Width(tkwin) - 2*scrollPtr->inset,
308 	    Tk_Height(tkwin) - 2*scrollPtr->inset, 0, TK_RELIEF_FLAT);
309 
310     /*
311      * Update values and then draw the native scrollbar over the rectangle.
312      */
313 
314     UpdateControlValues(scrollPtr);
315 
316     if (SNOW_LEOPARD_STYLE) {
317 	HIThemeDrawTrack(&msPtr->info, 0, dc.context,
318 			 kHIThemeOrientationInverted);
319     } else if ([NSApp macOSVersion] <= 100800) {
320 	HIThemeDrawTrack(&msPtr->info, 0, dc.context,
321 			 kHIThemeOrientationNormal);
322     } else {
323 #if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
324 
325 	/*
326 	 * Switch back to NSView coordinates and draw a modern scrollbar.
327 	 */
328 
329 	CGContextConcatCTM(dc.context, t);
330 	drawMacScrollbar(scrollPtr, msPtr, dc.context);
331 #endif
332     }
333     TkMacOSXRestoreDrawingContext(&dc);
334     scrollPtr->flags &= ~REDRAW_PENDING;
335 }
336 
337 /*
338  *----------------------------------------------------------------------
339  *
340  * TkpComputeScrollbarGeometry --
341  *
342  *	After changes in a scrollbar's size or configuration, this procedure
343  *	recomputes various geometry information used in displaying the
344  *	scrollbar.
345  *
346  * Results:
347  *	None.
348  *
349  * Side effects:
350  *	The scrollbar will be displayed differently.
351  *
352  *----------------------------------------------------------------------
353  */
354 
355 extern void
TkpComputeScrollbarGeometry(TkScrollbar * scrollPtr)356 TkpComputeScrollbarGeometry(
357     TkScrollbar *scrollPtr)
358 				/* Scrollbar whose geometry may have
359 				 * changed. */
360 {
361     /*
362      * The code below is borrowed from tkUnixScrlbr.c but has been adjusted to
363      * account for some differences between macOS and X11. The Unix scrollbar
364      * has an arrow button on each end.  On macOS 10.6 (Snow Leopard) the
365      * scrollbars by default have both arrow buttons at the bottom or right.
366      * (There is a preferences setting to use the Unix layout, but we are not
367      * supporting that!)  On more recent versions of macOS there are no arrow
368      * buttons at all. The case of no arrow buttons can be handled as a special
369      * case of having both buttons at the end, but where scrollPtr->arrowLength
370      * happens to be zero.  To adjust for having both arrows at the same end we
371      * shift the scrollbar up by the arrowLength.
372      */
373 
374     int fieldLength;
375 
376     if (scrollPtr->highlightWidth < 0) {
377 	scrollPtr->highlightWidth = 0;
378     }
379     scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth;
380     if ([NSApp macOSVersion] == 100600) {
381 	scrollPtr->arrowLength = scrollPtr->width;
382     } else {
383 	scrollPtr->arrowLength = 0;
384     }
385     fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin)
386 	    : Tk_Width(scrollPtr->tkwin))
387 	    - 2*(scrollPtr->arrowLength + scrollPtr->inset);
388     if (fieldLength < 0) {
389 	fieldLength = 0;
390     }
391     scrollPtr->sliderFirst = fieldLength*scrollPtr->firstFraction;
392     scrollPtr->sliderLast = fieldLength*scrollPtr->lastFraction;
393 
394     /*
395      * Adjust the slider so that it has at least a minimal size and so there
396      * is a small gap on either end which can be used to scroll by one page.
397      */
398 
399     if (scrollPtr->sliderFirst < MIN_GAP) {
400 	scrollPtr->sliderFirst = MIN_GAP;
401 	scrollPtr->sliderLast += MIN_GAP;
402     }
403     if (scrollPtr->sliderLast > fieldLength - MIN_GAP) {
404 	scrollPtr->sliderLast = fieldLength - MIN_GAP;
405 	scrollPtr->sliderFirst -= MIN_GAP;
406     }
407     if (scrollPtr->sliderFirst > fieldLength - MIN_SLIDER_LENGTH) {
408 	scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH;
409     }
410     if (scrollPtr->sliderLast < scrollPtr->sliderFirst + MIN_SLIDER_LENGTH) {
411 	scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH;
412     }
413     scrollPtr->sliderFirst += -scrollPtr->arrowLength + scrollPtr->inset;
414     scrollPtr->sliderLast += scrollPtr->inset;
415 
416     /*
417      * Register the desired geometry for the window. Leave enough space for the
418      * two arrows, if there are any arrows, plus a minimum-size slider, plus
419      * border around the whole window, if any. Then arrange for the window to
420      * be redisplayed.
421      */
422 
423     if (scrollPtr->vertical) {
424 	Tk_GeometryRequest(scrollPtr->tkwin,
425 		scrollPtr->width + 2*scrollPtr->inset,
426 		2*(scrollPtr->arrowLength + scrollPtr->borderWidth
427 		+ scrollPtr->inset) + metrics.minThumbHeight);
428     } else {
429 	Tk_GeometryRequest(scrollPtr->tkwin,
430 		2*(scrollPtr->arrowLength + scrollPtr->borderWidth
431 		+ scrollPtr->inset) + metrics.minThumbHeight,
432 		scrollPtr->width + 2*scrollPtr->inset);
433     }
434     Tk_SetInternalBorder(scrollPtr->tkwin, scrollPtr->inset);
435 }
436 
437 /*
438  *----------------------------------------------------------------------
439  *
440  * TkpDestroyScrollbar --
441  *
442  *	Free data structures associated with the scrollbar control.
443  *
444  * Results:
445  *	None.
446  *
447  * Side effects:
448  *	Frees the GCs associated with the scrollbar.
449  *
450  *----------------------------------------------------------------------
451  */
452 
453 void
TkpDestroyScrollbar(TkScrollbar * scrollPtr)454 TkpDestroyScrollbar(
455     TkScrollbar *scrollPtr)
456 {
457     MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr;
458 
459     if (macScrollPtr->troughGC != NULL) {
460 	Tk_FreeGC(scrollPtr->display, macScrollPtr->troughGC);
461     }
462     if (macScrollPtr->copyGC != NULL) {
463 	Tk_FreeGC(scrollPtr->display, macScrollPtr->copyGC);
464     }
465 }
466 
467 /*
468  *----------------------------------------------------------------------
469  *
470  * TkpConfigureScrollbar --
471  *
472  *	This procedure is called after the generic code has finished processing
473  *	configuration options, in order to configure platform specific options.
474  *	There are no such option on the Mac, however.
475  *
476  * Results:
477  *	None.
478  *
479  * Side effects:
480  *	Currently, none.
481  *
482  *----------------------------------------------------------------------
483  */
484 
485 void
TkpConfigureScrollbar(TkScrollbar * scrollPtr)486 TkpConfigureScrollbar(
487     TkScrollbar *scrollPtr)
488 {
489     /* empty */
490 }
491 
492 /*
493  *--------------------------------------------------------------
494  *
495  * TkpScrollbarPosition --
496  *
497  *	Determine the scrollbar element corresponding to a given position.
498  *
499  * Results:
500  *	One of TOP_ARROW, TOP_GAP, etc., indicating which element of the
501  *	scrollbar covers the position given by (x, y). If (x,y) is outside the
502  *	scrollbar entirely, then OUTSIDE is returned.
503  *
504  * Side effects:
505  *	None.
506  *
507  *--------------------------------------------------------------
508  */
509 
510 int
TkpScrollbarPosition(TkScrollbar * scrollPtr,int x,int y)511 TkpScrollbarPosition(
512     TkScrollbar *scrollPtr,
513 				/* Scrollbar widget record. */
514     int x, int y)		/* Coordinates within scrollPtr's window. */
515 {
516     /*
517      * The code below is borrowed from tkUnixScrlbr.c and needs no adjustment
518      * since it does not involve the arrow buttons.
519      */
520 
521     int length, width, tmp;
522     const int inset = scrollPtr->inset;
523 
524     if (scrollPtr->vertical) {
525 	length = Tk_Height(scrollPtr->tkwin);
526 	width = Tk_Width(scrollPtr->tkwin);
527     } else {
528 	tmp = x;
529 	x = y;
530 	y = tmp;
531 	length = Tk_Width(scrollPtr->tkwin);
532 	width = Tk_Height(scrollPtr->tkwin);
533     }
534 
535     if (x < inset || x >= width - inset ||
536 	    y < inset || y >= length - inset) {
537 	return OUTSIDE;
538     }
539 
540     /*
541      * Here we assume that the scrollbar is layed out with both arrow buttons
542      * at the bottom (or right).  Except on 10.6, however, the arrows do not
543      * actually exist, i.e. the arrowLength is 0.  These are the same
544      * assumptions which are being made in TkpComputeScrollbarGeometry.
545      */
546 
547     if (y < scrollPtr->sliderFirst + scrollPtr->arrowLength) {
548 	return TOP_GAP;
549     }
550     if (y < scrollPtr->sliderLast) {
551 	return SLIDER;
552     }
553     if (y < length - (2*scrollPtr->arrowLength + inset)) {
554 	return BOTTOM_GAP;
555     }
556 
557     /*
558      * On systems newer than 10.6 we have already returned.
559      */
560 
561     if (y < length - (scrollPtr->arrowLength + inset)) {
562 	return TOP_ARROW;
563     }
564     return BOTTOM_ARROW;
565 }
566 
567 /*
568  *--------------------------------------------------------------
569  *
570  * UpdateControlValues --
571  *
572  *	This procedure updates the Macintosh scrollbar control to display the
573  *	values defined by the Tk scrollbar. This is the key interface to the
574  *	Mac-native scrollbar; the Unix bindings drive scrolling in the Tk
575  *	window and all the Mac scrollbar has to do is redraw itself.
576  *
577  * Results:
578  *	None.
579  *
580  * Side effects:
581  *	The Macintosh control is updated.
582  *
583  *--------------------------------------------------------------
584  */
585 
586 static void
UpdateControlValues(TkScrollbar * scrollPtr)587 UpdateControlValues(
588     TkScrollbar *scrollPtr)		/* Scrollbar data struct. */
589 {
590     MacScrollbar *msPtr = (MacScrollbar *) scrollPtr;
591     Tk_Window tkwin = scrollPtr->tkwin;
592     MacDrawable *macWin = (MacDrawable *)Tk_WindowId(scrollPtr->tkwin);
593     double dViewSize;
594     HIRect contrlRect;
595     short width, height;
596 
597     NSView *view = TkMacOSXGetNSViewForDrawable(macWin);
598     CGFloat viewHeight = [view bounds].size.height;
599     NSRect frame;
600 
601     frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin),
602 	    Tk_Height(tkwin));
603     frame = NSInsetRect(frame, scrollPtr->inset, scrollPtr->inset);
604     frame.origin.y = viewHeight - (frame.origin.y + frame.size.height);
605 
606     contrlRect = NSRectToCGRect(frame);
607     msPtr->info.bounds = contrlRect;
608 
609     width = contrlRect.size.width;
610     height = contrlRect.size.height - scrollPtr->arrowLength;
611 
612     /*
613      * Ensure we set scrollbar control bounds only once all size adjustments
614      * have been computed.
615      */
616 
617     msPtr->info.bounds = contrlRect;
618     if (scrollPtr->vertical) {
619 	msPtr->info.attributes &= ~kThemeTrackHorizontal;
620     } else {
621 	msPtr->info.attributes |= kThemeTrackHorizontal;
622     }
623 
624     /*
625      * Given the Tk parameters for the fractions of the start and end of the
626      * thumb, the following calculation determines the location for the
627      * Macintosh thumb. The Aqua scroll control works as follows. The
628      * scrollbar's value is the position of the left (or top) side of the view
629      * area in the content area being scrolled. The maximum value of the
630      * control is therefore the dimension of the content area less the size of
631      * the view area.
632      */
633 
634     double factor = RangeToFactor(100.0);
635     dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction) * factor;
636     msPtr->info.max = factor - dViewSize;
637     msPtr->info.trackInfo.scrollbar.viewsize = dViewSize;
638     if (scrollPtr->vertical) {
639 	if (SNOW_LEOPARD_STYLE) {
640 	    msPtr->info.value = factor * scrollPtr->firstFraction;
641 	} else {
642 	    msPtr->info.value = msPtr->info.max -
643 		    factor * scrollPtr->firstFraction;
644 	}
645     } else {
646 	msPtr->info.value = factor * scrollPtr->firstFraction;
647     }
648 
649     if ((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0)
650 	    || height <= metrics.minHeight) {
651     	msPtr->info.enableState = kThemeTrackHideTrack;
652     } else {
653         msPtr->info.enableState = kThemeTrackActive;
654     	msPtr->info.attributes =
655 		kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost;
656     }
657 }
658 
659 /*
660  *--------------------------------------------------------------
661  *
662  * ScrollbarEvent --
663  *
664  *	This procedure is invoked in response to <ButtonPress>,
665  *      <ButtonRelease>, <EnterNotify>, and <LeaveNotify> events.  The
666  *      Scrollbar appearance is modified for each event.
667  *
668  *--------------------------------------------------------------
669  */
670 
671 static int
ScrollbarEvent(TkScrollbar * scrollPtr,XEvent * eventPtr)672 ScrollbarEvent(
673     TkScrollbar *scrollPtr,
674     XEvent *eventPtr)
675 {
676     MacScrollbar *msPtr = (MacScrollbar *) scrollPtr;
677 
678     /*
679      * The pressState does not indicate whether the moused button was pressed
680      * at some location in the Scrollbar.  Rather, it indicates that the
681      * scrollbar should appear as if it were pressed in that location. The
682      * standard Mac behavior is that once the button is pressed inside the
683      * Scrollbar the appearance should not change until the button is released,
684      * even if the mouse moves outside of the scrollbar.  However, if the mouse
685      * lies over the scrollbar but the button is not pressed then the
686      * appearance should be the same as if the button had been pressed on the
687      * slider, i.e. kThemeThumbPressed.  See the file Appearance.r, or
688      * HIToolbox.bridgesupport on 10.14.
689      */
690 
691     if (eventPtr->type == ButtonPress) {
692 	msPtr->buttonDown = true;
693 	UpdateControlValues(scrollPtr);
694 
695 	int where = TkpScrollbarPosition(scrollPtr,
696 		eventPtr->xbutton.x, eventPtr->xbutton.y);
697 
698 	switch (where) {
699 	case OUTSIDE:
700 	    msPtr->info.trackInfo.scrollbar.pressState = 0;
701 	    break;
702 	case TOP_GAP:
703 	    msPtr->info.trackInfo.scrollbar.pressState = kThemeTopTrackPressed;
704 	    break;
705 	case SLIDER:
706 	    msPtr->info.trackInfo.scrollbar.pressState = kThemeThumbPressed;
707 	    break;
708 	case BOTTOM_GAP:
709 	    msPtr->info.trackInfo.scrollbar.pressState =
710 		    kThemeBottomTrackPressed;
711 	    break;
712 	case TOP_ARROW:
713 
714 	    /*
715 	     * This looks wrong and the docs say it is wrong but it works.
716 	     */
717 
718 	    msPtr->info.trackInfo.scrollbar.pressState =
719 		    kThemeTopInsideArrowPressed;
720 	    break;
721 	case BOTTOM_ARROW:
722 	    msPtr->info.trackInfo.scrollbar.pressState =
723 		    kThemeBottomOutsideArrowPressed;
724 	    break;
725 	}
726     }
727     if (eventPtr->type == ButtonRelease) {
728 	msPtr->buttonDown = false;
729 	if (!msPtr->mouseOver) {
730 	    msPtr->info.trackInfo.scrollbar.pressState = 0;
731 	}
732     }
733     if (eventPtr->type == EnterNotify) {
734 	msPtr->mouseOver = true;
735 	if (!msPtr->buttonDown) {
736 	    msPtr->info.trackInfo.scrollbar.pressState = kThemeThumbPressed;
737 	}
738     }
739     if (eventPtr->type == LeaveNotify) {
740 	msPtr->mouseOver = false;
741 	if (!msPtr->buttonDown) {
742 	    msPtr->info.trackInfo.scrollbar.pressState = 0;
743 	}
744     }
745     TkScrollbarEventuallyRedraw(scrollPtr);
746     return TCL_OK;
747 }
748 
749 /*
750  *--------------------------------------------------------------
751  *
752  * ScrollbarEventProc --
753  *
754  *	This procedure is invoked by the Tk dispatcher for various events on
755  *	scrollbars.
756  *
757  * Results:
758  *	None.
759  *
760  * Side effects:
761  *	When the window gets deleted, internal structures get cleaned up. When
762  *	it gets exposed, it is redisplayed.
763  *
764  *--------------------------------------------------------------
765  */
766 
767 static void
ScrollbarEventProc(ClientData clientData,XEvent * eventPtr)768 ScrollbarEventProc(
769     ClientData clientData,	/* Information about window. */
770     XEvent *eventPtr)		/* Information about event. */
771 {
772     TkScrollbar *scrollPtr = (TkScrollbar *)clientData;
773 
774     switch (eventPtr->type) {
775     case UnmapNotify:
776 	TkMacOSXSetScrollbarGrow((TkWindow *) scrollPtr->tkwin, false);
777 	break;
778     case ActivateNotify:
779     case DeactivateNotify:
780 	TkScrollbarEventuallyRedraw(scrollPtr);
781 	break;
782     case ButtonPress:
783     case ButtonRelease:
784     case EnterNotify:
785     case LeaveNotify:
786     	ScrollbarEvent(scrollPtr, eventPtr);
787 	break;
788     default:
789 	TkScrollbarEventProc(scrollPtr, eventPtr);
790     }
791 }
792 
793 /*
794  * Local Variables:
795  * mode: objc
796  * c-basic-offset: 4
797  * fill-column: 79
798  * coding: utf-8
799  * End:
800  */
801