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