1/*
2 * tkMacOSXScrollbar.c --
3 *
4 *	This file implements the Macintosh specific portion of the scrollbar
5 *	widget. The Macintosh scrollbar may also draw a windows grow
6 *	region under certain cases.
7 *
8 * Copyright (c) 1996 by Sun Microsystems, Inc.
9 * Copyright 2001, Apple Computer, Inc.
10 * Copyright (c) 2006-2007 Daniel A. Steffen <das@users.sourceforge.net>
11 *
12 * See the file "license.terms" for information on usage and redistribution
13 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id: tkMacOSXScrlbr.c,v 1.5.2.15 2007/06/29 03:22:02 das Exp $
16 */
17
18#include "tkMacOSXPrivate.h"
19#include "tkScrollbar.h"
20#include "tkMacOSXDebug.h"
21
22#define MIN_SCROLLBAR_VALUE		0
23#define SCROLLBAR_SCALING_VALUE		((double)(LONG_MAX>>1))
24
25/*
26 * Declaration of Mac specific scrollbar structure.
27 */
28
29typedef struct MacScrollbar {
30    TkScrollbar info;		/* Generic scrollbar info */
31    ControlRef	sbHandle;	/* Scrollbar control */
32    int		macFlags;	/* Various flags; see below */
33    Rect	eraseRect;	/* Rect to erase before drawing control */
34} MacScrollbar;
35
36/*
37 * Flag bits for scrollbars on the Mac:
38 *
39 * ALREADY_DEAD:		Non-zero means this scrollbar has been
40 *				destroyed, but has not been cleaned up.
41 * IN_MODAL_LOOP:		Non-zero means this scrollbar is in the middle
42 *				of a modal loop.
43 * ACTIVE:			Non-zero means this window is currently
44 *				active (in the foreground).
45 */
46
47#define ALREADY_DEAD		1
48#define IN_MODAL_LOOP		2
49#define ACTIVE			4
50
51/*
52 * Globals uses locally in this file.
53 */
54static ControlActionUPP scrollActionProc = NULL;	/* Pointer to func. */
55static ControlActionUPP thumbActionProc = NULL;		/* Pointer to func. */
56static Point mouseDownPoint;	/* Used to store the coordinates where the  */
57				/* mouse was first pressed to begin	    */
58				/* dragging the thumb, because		    */
59				/* ThumbActionProc can't take any args.	    */
60
61typedef struct ScrollbarMetrics {
62    SInt32 width, minHeight, minThumbHeight;
63    short topArrowHeight, bottomArrowHeight;
64    ControlSize size;
65} ScrollbarMetrics;
66
67static ScrollbarMetrics metrics[2] = {
68    {15, 54, 26, 14, 14, kControlSizeNormal}, /* kThemeScrollBarMedium */
69    {11, 40, 20, 10, 10, kControlSizeSmall},  /* kThemeScrollBarSmall  */
70};
71
72/*
73 * This variable holds the default width for a scrollbar in string form for
74 * use in a Tk_ConfigSpec.
75 */
76
77static char defWidth[TCL_INTEGER_SPACE];
78
79/*
80 * Forward declarations for procedures defined later in this file:
81 */
82
83static pascal void ScrollbarActionProc(ControlRef theControl,
84	ControlPartCode partCode);
85static pascal void ThumbActionProc(ControlRef theControl,
86	ControlPartCode partCode);
87static int ScrollbarBindProc(ClientData clientData, Tcl_Interp *interp,
88	XEvent *eventPtr, Tk_Window tkwin, KeySym keySym);
89static void ScrollbarEventProc(ClientData clientData, XEvent *eventPtr);
90static void UpdateControlValues(MacScrollbar *macScrollPtr);
91
92/*
93 * The class procedure table for the scrollbar widget. Leave the proc fields
94 * initialized to NULL, which should happen automatically because of the scope
95 * at which the variable is declared.
96 */
97
98Tk_ClassProcs tkpScrollbarProcs = {
99    sizeof(Tk_ClassProcs)	/* size */
100};
101
102/*
103 *----------------------------------------------------------------------
104 *
105 * TkMacOSXInitScrollbarMetrics --
106 *
107 *	This function initializes the current system metrics for a
108 *	scrollbar.
109 *
110 * Results:
111 *	None.
112 *
113 * Side effects:
114 *	Updates the geometry cache info for all scrollbars.
115 *
116 *----------------------------------------------------------------------
117 */
118
119void
120TkMacOSXInitScrollbarMetrics(void)
121{
122    const short height = 100, width = 50;
123    ThemeTrackDrawInfo info = {0, {0, 0, height, width}, 0, 1, 0, 0,
124	    kThemeTrackShowThumb, kThemeTrackActive, 0, {{1, 0}}};
125    Rect bounds;
126    Tk_ConfigSpec *specPtr;
127
128    ChkErr(GetThemeMetric, kThemeMetricScrollBarWidth, &metrics[0].width);
129    ChkErr(GetThemeMetric, kThemeMetricScrollBarMinThumbHeight,
130	    &metrics[0].minThumbHeight);
131    info.kind = kThemeScrollBarMedium;
132    ChkErr(GetThemeTrackDragRect, &info, &bounds);
133    metrics[0].topArrowHeight = bounds.top;
134    metrics[0].bottomArrowHeight = height - bounds.bottom;
135    metrics[0].minHeight = metrics[0].minThumbHeight +
136	    metrics[0].topArrowHeight + metrics[0].bottomArrowHeight;
137    ChkErr(GetThemeMetric, kThemeMetricSmallScrollBarWidth, &metrics[1].width);
138    ChkErr(GetThemeMetric, kThemeMetricSmallScrollBarMinThumbHeight,
139	    &metrics[1].minThumbHeight);
140    info.kind = kThemeScrollBarSmall;
141    ChkErr(GetThemeTrackDragRect, &info, &bounds);
142    metrics[1].topArrowHeight = bounds.top;
143    metrics[1].bottomArrowHeight = height - bounds.bottom;
144    metrics[1].minHeight = metrics[1].minThumbHeight +
145	    metrics[1].topArrowHeight + metrics[1].bottomArrowHeight;
146
147    sprintf(defWidth, "%ld", metrics[0].width);
148    for (specPtr = tkpScrollbarConfigSpecs; specPtr->type != TK_CONFIG_END;
149	    specPtr++) {
150	if (specPtr->offset == Tk_Offset(TkScrollbar, width)) {
151	    specPtr->defValue = defWidth;
152	}
153    }
154}
155
156/*
157 *----------------------------------------------------------------------
158 *
159 * TkpCreateScrollbar --
160 *
161 *	Allocate a new TkScrollbar structure.
162 *
163 * Results:
164 *	Returns a newly allocated TkScrollbar structure.
165 *
166 * Side effects:
167 *	None.
168 *
169 *----------------------------------------------------------------------
170 */
171
172TkScrollbar *
173TkpCreateScrollbar(
174    Tk_Window tkwin)	/* New Tk Window. */
175{
176    static int initialized = 0;
177    MacScrollbar * macScrollPtr;
178    TkWindow *winPtr = (TkWindow *)tkwin;
179
180    if (scrollActionProc == NULL) {
181	scrollActionProc = NewControlActionUPP(ScrollbarActionProc);
182	thumbActionProc =  NewControlActionUPP(ThumbActionProc);
183    }
184    if (!initialized) {
185	TkMacOSXInitScrollbarMetrics();
186	initialized = 1;
187    }
188    macScrollPtr = (MacScrollbar *) ckalloc(sizeof(MacScrollbar));
189    macScrollPtr->sbHandle = NULL;
190    macScrollPtr->macFlags = 0;
191    SetRect(&macScrollPtr->eraseRect, 0, 0, 0, 0);
192
193    Tk_CreateEventHandler(tkwin, ActivateMask|ExposureMask|
194	StructureNotifyMask|FocusChangeMask,
195	ScrollbarEventProc, (ClientData) macScrollPtr);
196
197    if (!Tcl_GetAssocData(winPtr->mainPtr->interp, "TkScrollbar", NULL)) {
198	Tcl_SetAssocData(winPtr->mainPtr->interp, "TkScrollbar", NULL,
199		(ClientData)1);
200	TkCreateBindingProcedure(winPtr->mainPtr->interp,
201	    winPtr->mainPtr->bindingTable,
202	    (ClientData)Tk_GetUid("Scrollbar"), "<ButtonPress>",
203	    ScrollbarBindProc, NULL, NULL);
204    }
205    return (TkScrollbar *) macScrollPtr;
206}
207
208/*
209 *--------------------------------------------------------------
210 *
211 * TkpDisplayScrollbar --
212 *
213 *	This procedure redraws the contents of a scrollbar window.
214 *	It is invoked as a do-when-idle handler, so it only runs
215 *	when there's nothing else for the application to do.
216 *
217 * Results:
218 *	None.
219 *
220 * Side effects:
221 *	Information appears on the screen.
222 *
223 *--------------------------------------------------------------
224 */
225
226void
227TkpDisplayScrollbar(
228    ClientData clientData)	/* Information about window. */
229{
230    TkScrollbar *scrollPtr = (TkScrollbar *) clientData;
231    MacScrollbar *macScrollPtr = (MacScrollbar *) clientData;
232    Tk_Window tkwin = scrollPtr->tkwin;
233    CGrafPtr destPort, savePort;
234    Boolean portChanged;
235    WindowRef windowRef;
236
237    if ((scrollPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
238	goto done;
239    }
240
241    /*
242     * Draw the focus or any 3D relief we may have.
243     */
244    if (scrollPtr->highlightWidth != 0) {
245	GC fgGC, bgGC;
246
247	bgGC = Tk_GCForColor(scrollPtr->highlightBgColorPtr,
248	    Tk_WindowId(tkwin));
249
250	if (scrollPtr->flags & GOT_FOCUS) {
251	    fgGC = Tk_GCForColor(scrollPtr->highlightColorPtr,
252		Tk_WindowId(tkwin));
253	    TkpDrawHighlightBorder(tkwin, fgGC, bgGC, scrollPtr->highlightWidth,
254		Tk_WindowId(tkwin));
255	} else {
256	    TkpDrawHighlightBorder(tkwin, bgGC, bgGC, scrollPtr->highlightWidth,
257		Tk_WindowId(tkwin));
258	}
259    }
260    Tk_Draw3DRectangle(tkwin, Tk_WindowId(tkwin), scrollPtr->bgBorder,
261	scrollPtr->highlightWidth, scrollPtr->highlightWidth,
262	Tk_Width(tkwin) - 2*scrollPtr->highlightWidth,
263	Tk_Height(tkwin) - 2*scrollPtr->highlightWidth,
264	scrollPtr->borderWidth, scrollPtr->relief);
265
266    if (macScrollPtr->sbHandle == NULL) {
267	Rect r = {0, 0, 1, 1};
268
269	windowRef = TkMacOSXDrawableWindow(Tk_WindowId(tkwin));
270	CreateScrollBarControl(windowRef, &r, 0, 0, 0, 0, true, NULL,
271		&(macScrollPtr->sbHandle));
272	SetControlReference(macScrollPtr->sbHandle, (SInt32) scrollPtr);
273
274	if (IsWindowActive(windowRef)) {
275	    macScrollPtr->macFlags |= ACTIVE;
276	}
277    }
278
279    /*
280     * Update the control values before we draw.
281     */
282
283    UpdateControlValues(macScrollPtr);
284
285    /*
286     * Set up port for drawing Macintosh control.
287     */
288    destPort = TkMacOSXGetDrawablePort(Tk_WindowId(tkwin));
289    portChanged = QDSwapPort(destPort, &savePort);
290    TkMacOSXSetUpClippingRgn(Tk_WindowId(tkwin));
291
292    /*
293     * Scrollbars do not erase the complete control bounds if they are wider
294     * than the standard width, so manually erase the extra space.
295     */
296
297    if (!EmptyRect(&macScrollPtr->eraseRect)) {
298	EraseRect(&macScrollPtr->eraseRect);
299    }
300
301    Draw1Control(macScrollPtr->sbHandle);
302
303    if (portChanged) {
304	QDSwapPort(savePort, NULL);
305    }
306
307    done:
308    scrollPtr->flags &= ~REDRAW_PENDING;
309}
310
311/*
312 *----------------------------------------------------------------------
313 *
314 * TkpConfigureScrollbar --
315 *
316 *	This procedure is called after the generic code has finished
317 *	processing configuration options, in order to configure
318 *	platform specific options.
319 *
320 * Results:
321 *	None.
322 *
323 * Side effects:
324 *	None.
325 *
326 *----------------------------------------------------------------------
327 */
328
329void
330TkpConfigureScrollbar(scrollPtr)
331    register TkScrollbar *scrollPtr;	/* Information about widget; may or
332					 * may not already have values for
333					 * some fields. */
334{
335}
336
337/*
338 *----------------------------------------------------------------------
339 *
340 * TkpComputeScrollbarGeometry --
341 *
342 *	After changes in a scrollbar's size or configuration, this
343 *	procedure recomputes various geometry information used in
344 *	displaying the scrollbar.
345 *
346 * Results:
347 *	None.
348 *
349 * Side effects:
350 *	The scrollbar will be displayed differently.
351 *
352 *----------------------------------------------------------------------
353 */
354
355void
356TkpComputeScrollbarGeometry(
357    register TkScrollbar *scrollPtr)	/* Scrollbar whose geometry may
358					 * have changed. */
359{
360    int variant, fieldLength;
361
362    if (scrollPtr->highlightWidth < 0) {
363	scrollPtr->highlightWidth = 0;
364    }
365    scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth;
366    variant = ((scrollPtr->vertical ? Tk_Width(scrollPtr->tkwin) :
367	    Tk_Height(scrollPtr->tkwin)) - 2 * scrollPtr->inset
368	    < metrics[0].width) ? 1 : 0;
369    scrollPtr->arrowLength = (metrics[variant].topArrowHeight +
370	    metrics[variant].bottomArrowHeight) / 2;
371    fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin)
372	    : Tk_Width(scrollPtr->tkwin))
373	    - 2 * (scrollPtr->arrowLength + scrollPtr->inset);
374    if (fieldLength < 0) {
375	fieldLength = 0;
376    }
377    scrollPtr->sliderFirst = fieldLength * scrollPtr->firstFraction;
378    scrollPtr->sliderLast = fieldLength * scrollPtr->lastFraction;
379
380    /*
381     * Adjust the slider so that some piece of it is always
382     * displayed in the scrollbar and so that it has at least
383     * a minimal width (so it can be grabbed with the mouse).
384     */
385
386    if (scrollPtr->sliderFirst > (fieldLength - 2*scrollPtr->borderWidth)) {
387	scrollPtr->sliderFirst = fieldLength - 2*scrollPtr->borderWidth;
388    }
389    if (scrollPtr->sliderFirst < 0) {
390	scrollPtr->sliderFirst = 0;
391    }
392    if (scrollPtr->sliderLast < (scrollPtr->sliderFirst +
393	    metrics[variant].minThumbHeight)) {
394	scrollPtr->sliderLast = scrollPtr->sliderFirst +
395		metrics[variant].minThumbHeight;
396    }
397    if (scrollPtr->sliderLast > fieldLength) {
398	scrollPtr->sliderLast = fieldLength;
399    }
400    scrollPtr->sliderFirst += scrollPtr->inset +
401	    metrics[variant].topArrowHeight;
402    scrollPtr->sliderLast += scrollPtr->inset +
403	    metrics[variant].bottomArrowHeight;
404
405    /*
406     * Register the desired geometry for the window (leave enough space
407     * for the two arrows plus a minimum-size slider, plus border around
408     * the whole window, if any). Then arrange for the window to be
409     * redisplayed.
410     */
411
412    if (scrollPtr->vertical) {
413	Tk_GeometryRequest(scrollPtr->tkwin, scrollPtr->width +
414		2 * scrollPtr->inset, 2 * (scrollPtr->arrowLength +
415		scrollPtr->borderWidth + scrollPtr->inset) +
416		metrics[variant].minThumbHeight);
417    } else {
418	Tk_GeometryRequest(scrollPtr->tkwin, 2 * (scrollPtr->arrowLength +
419		scrollPtr->borderWidth + scrollPtr->inset) +
420		metrics[variant].minThumbHeight, scrollPtr->width +
421		2 * scrollPtr->inset);
422    }
423    Tk_SetInternalBorder(scrollPtr->tkwin, scrollPtr->inset);
424}
425
426/*
427 *----------------------------------------------------------------------
428 *
429 * TkpDestroyScrollbar --
430 *
431 *	Free data structures associated with the scrollbar control.
432 *
433 * Results:
434 *	None.
435 *
436 * Side effects:
437 *	None.
438 *
439 *----------------------------------------------------------------------
440 */
441
442void
443TkpDestroyScrollbar(
444    TkScrollbar *scrollPtr)	/* Scrollbar to destroy. */
445{
446    MacScrollbar *macScrollPtr = (MacScrollbar *)scrollPtr;
447
448    if (macScrollPtr->sbHandle != NULL) {
449	if (!(macScrollPtr->macFlags & IN_MODAL_LOOP)) {
450	    DisposeControl(macScrollPtr->sbHandle);
451	    macScrollPtr->sbHandle = NULL;
452	}
453    }
454    macScrollPtr->macFlags |= ALREADY_DEAD;
455}
456
457/*
458 *--------------------------------------------------------------
459 *
460 * TkpScrollbarPosition --
461 *
462 *	Determine the scrollbar element corresponding to a
463 *	given position.
464 *
465 * Results:
466 *	One of TOP_ARROW, TOP_GAP, etc., indicating which element
467 *	of the scrollbar covers the position given by (x, y). If
468 *	(x,y) is outside the scrollbar entirely, then OUTSIDE is
469 *	returned.
470 *
471 * Side effects:
472 *	None.
473 *
474 *--------------------------------------------------------------
475 */
476
477int
478TkpScrollbarPosition(
479    TkScrollbar *scrollPtr,	/* Scrollbar widget record. */
480    int x, int y)		/* Coordinates within scrollPtr's
481				 * window. */
482{
483    MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr;
484    CGrafPtr destPort, savePort;
485    Boolean portChanged;
486    int inactive = 0;
487    ControlPartCode part;
488    Point where = {y, x};
489    Rect bounds;
490
491    if ((x < scrollPtr->inset) || (x >= (Tk_Width(scrollPtr->tkwin) -
492	    scrollPtr->inset)) || (y < scrollPtr->inset) ||
493	    (y >= (Tk_Height(scrollPtr->tkwin) - scrollPtr->inset))) {
494	return OUTSIDE;
495    }
496
497    /*
498     * All of the calculations in this procedure mirror those in
499     * DisplayScrollbar. Be sure to keep the two consistent. On the
500     * Macintosh we use the OS call TestControl to do this mapping.
501     * For TestControl to work, the scrollbar must be active and must
502     * be in the current port.
503     */
504
505    destPort = TkMacOSXGetDrawablePort(Tk_WindowId(scrollPtr->tkwin));
506    portChanged = QDSwapPort(destPort, &savePort);
507    UpdateControlValues(macScrollPtr);
508    if (!IsControlActive(macScrollPtr->sbHandle)) {
509	inactive = true;
510	ActivateControl(macScrollPtr->sbHandle);
511    }
512    TkMacOSXWinBounds((TkWindow *) scrollPtr->tkwin, &bounds);
513    where.h += bounds.left;
514    where.v += bounds.top;
515    part = TestControl(((MacScrollbar *) scrollPtr)->sbHandle, where);
516    if (inactive) {
517	DeactivateControl(macScrollPtr->sbHandle);
518    }
519    if (portChanged) {
520	QDSwapPort(savePort, NULL);
521    }
522    switch (part) {
523	case kAppearancePartUpButton:
524	    return TOP_ARROW;
525	case kAppearancePartPageUpArea:
526	    return TOP_GAP;
527	case kAppearancePartIndicator:
528	    return SLIDER;
529	case kAppearancePartPageDownArea:
530	    return BOTTOM_GAP;
531	case kAppearancePartDownButton:
532	    return BOTTOM_ARROW;
533	default:
534	    return OUTSIDE;
535    }
536}
537
538/*
539 *--------------------------------------------------------------
540 *
541 * ThumbActionProc --
542 *
543 *	Callback procedure used by the Macintosh toolbox call
544 *	HandleControlClick. This call is used to track the
545 *	thumb of the scrollbar. Unlike the
546 *	ScrollbarActionProc function this function is called
547 *	once and basically takes over tracking the scrollbar
548 *	from the control. This is done to avoid conflicts with
549 *	what the control plans to draw.
550 *
551 * Results:
552 *	None.
553 *
554 * Side effects:
555 *	May change the display.
556 *
557 *--------------------------------------------------------------
558 */
559
560static pascal void
561ThumbActionProc(ControlRef theControl, ControlPartCode partCode)
562{
563    TkScrollbar *scrollPtr = (TkScrollbar *)(intptr_t)GetControlReference(
564	    theControl);
565    MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr;
566    Tcl_DString cmdString;
567    int origValue, variant;
568    short trackBarSize;
569    double oldFirstFraction, newFirstFraction;
570    char valueString[40];
571    Point currentPoint = { 0, 0 };
572    Rect trackRect;
573    Tcl_Interp *interp;
574    MouseTrackingResult trackingResult;
575    OSStatus err;
576
577    if (scrollPtr == NULL) {
578	return;
579    }
580
581    Tcl_DStringInit(&cmdString);
582    origValue = GetControl32BitValue(macScrollPtr->sbHandle);
583    GetControlBounds(macScrollPtr->sbHandle, &trackRect);
584
585    if (scrollPtr->vertical) {
586	variant = (trackRect.right - trackRect.left) < metrics[0].width ? 1 : 0;
587	trackBarSize = trackRect.bottom - trackRect.top -
588		metrics[variant].topArrowHeight -
589		metrics[variant].bottomArrowHeight;
590	InsetRect(&trackRect, -25, -113);
591    } else {
592	variant = (trackRect.bottom - trackRect.top) < metrics[0].width ? 1 : 0;
593	trackBarSize = trackRect.right - trackRect.left -
594		metrics[variant].topArrowHeight -
595		metrics[variant].bottomArrowHeight;
596	InsetRect(&trackRect, -113, -25);
597    }
598
599    /*
600     * Track the mouse while the button is held down. If the mouse is moved,
601     * we calculate the value that should be passed to the "command" part of
602     * the scrollbar. Since the mouse may move a distance too small to
603     * cause a change to the first fraction, each calculation must be done
604     * versus what the first fraction was when the mouse button was
605     * initially pressed. Otherwise, moving the mouse too slowly will
606     * cause the calculated fraction delta to be zero and the scrollbar
607     * won't respond.
608     */
609
610    oldFirstFraction = scrollPtr->firstFraction;
611
612    TkMacOSXTrackingLoop(1);
613    do {
614	err = ChkErr(TrackMouseLocationWithOptions, NULL,
615		kTrackMouseLocationOptionDontConsumeMouseUp,
616		kEventDurationForever, &currentPoint, NULL, &trackingResult);
617	if ((err == noErr) && ((trackingResult == kMouseTrackingMouseDragged)
618		|| (trackingResult == kMouseTrackingMouseMoved))) {
619
620	   /*
621	    * Calculate where the scrollbar should move to, based on
622	    * where the mouse button was pressed and where the scrollbar
623	    * initially was at that time. Note that PtInRect() will
624	    * return false if currentPoint or trackRect are not in
625	    * is not in current graphics port, which may happen if any
626	    * of the waiting idle events change the port (e.g. with
627	    * SetPort()) but fail to restore it before returning and the
628	    * scrollbar will lock in place.
629	    */
630	    newFirstFraction = oldFirstFraction;
631	    if (PtInRect(currentPoint, &trackRect)) {
632		short pixDiff;
633
634		if (scrollPtr->vertical) {
635		    pixDiff = currentPoint.v - mouseDownPoint.v;
636		} else {
637		    pixDiff = currentPoint.h - mouseDownPoint.h;
638		}
639		newFirstFraction += (double)pixDiff / trackBarSize;
640		if (newFirstFraction > 1.0) {
641		    newFirstFraction = 1.0;
642		} else if (newFirstFraction < 0.0) {
643		    newFirstFraction = 0.0;
644		}
645	    }
646
647	    /*
648	     * Move the scrollbar thumb to the new first fraction given
649	     * its position when initially pressed and how far the mouse
650	     * has moved. Process waiting idle tasks afterward to allow
651	     * for the display to update.
652	     */
653
654	    sprintf(valueString, "%g", newFirstFraction);
655	    Tcl_DStringSetLength(&cmdString, 0);
656	    Tcl_DStringAppend(&cmdString, scrollPtr->command,
657		scrollPtr->commandSize);
658	    Tcl_DStringAppendElement(&cmdString, "moveto");
659	    Tcl_DStringAppendElement(&cmdString, valueString);
660	    interp = scrollPtr->interp;
661	    Tcl_Preserve((ClientData) interp);
662	    Tcl_EvalEx(interp, Tcl_DStringValue(&cmdString),
663		    Tcl_DStringLength(&cmdString), TCL_EVAL_GLOBAL);
664	    Tcl_Release((ClientData) interp);
665	    TkMacOSXRunTclEventLoop();
666	}
667    } while ((err == noErr) && trackingResult != kMouseTrackingMouseReleased);
668    TkMacOSXTrackingLoop(0);
669    Tcl_DStringFree(&cmdString);
670    return;
671}
672
673/*
674 *--------------------------------------------------------------
675 *
676 * ScrollbarActionProc --
677 *
678 *	Callback procedure used by the Macintosh toolbox call
679 *	HandleControlClick. This call will update the display
680 *	while the scrollbar is being manipulated by the user.
681 *
682 * Results:
683 *	None.
684 *
685 * Side effects:
686 *	May change the display.
687 *
688 *--------------------------------------------------------------
689 */
690
691static pascal void
692ScrollbarActionProc(
693    ControlRef theControl,	/* Handle to scrollbat control */
694    ControlPartCode partCode)	/* Part of scrollbar that was "hit" */
695{
696    TkScrollbar *scrollPtr = (TkScrollbar *)(intptr_t)GetControlReference(
697	    theControl);
698    MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr;
699    Tcl_DString cmdString;
700
701    Tcl_DStringInit(&cmdString);
702    Tcl_DStringAppend(&cmdString, scrollPtr->command,
703	    scrollPtr->commandSize);
704
705    if ( partCode == kAppearancePartUpButton ||
706	    partCode == kAppearancePartDownButton ) {
707	Tcl_DStringAppendElement(&cmdString, "scroll");
708	Tcl_DStringAppendElement(&cmdString,
709		(partCode == kAppearancePartUpButton) ? "-1" : "1");
710	Tcl_DStringAppendElement(&cmdString, "unit");
711    } else if (partCode == kAppearancePartPageUpArea ||
712	    partCode == kAppearancePartPageDownArea ) {
713	Tcl_DStringAppendElement(&cmdString, "scroll");
714	Tcl_DStringAppendElement(&cmdString,
715		(partCode == kAppearancePartPageUpArea) ? "-1" : "1");
716	Tcl_DStringAppendElement(&cmdString, "page");
717    } else if (partCode == kAppearancePartIndicator) {
718	char valueString[TCL_DOUBLE_SPACE];
719
720	sprintf(valueString, "%g",
721		(GetControl32BitValue(macScrollPtr->sbHandle) -
722		MIN_SCROLLBAR_VALUE) / SCROLLBAR_SCALING_VALUE);
723	Tcl_DStringAppendElement(&cmdString, "moveto");
724	Tcl_DStringAppendElement(&cmdString, valueString);
725    }
726    Tcl_Preserve((ClientData) scrollPtr->interp);
727    Tcl_EvalEx(scrollPtr->interp, Tcl_DStringValue(&cmdString),
728	    Tcl_DStringLength(&cmdString), TCL_EVAL_GLOBAL);
729    Tcl_Release((ClientData) scrollPtr->interp);
730    Tcl_DStringFree(&cmdString);
731    TkMacOSXRunTclEventLoop();
732}
733
734/*
735 *--------------------------------------------------------------
736 *
737 * ScrollbarBindProc --
738 *
739 *	This procedure is invoked when the default <ButtonPress>
740 *	binding on the Scrollbar bind tag fires.
741 *
742 * Results:
743 *	None.
744 *
745 * Side effects:
746 *	The event enters a modal loop.
747 *
748 *--------------------------------------------------------------
749 */
750
751static int
752ScrollbarBindProc(
753    ClientData clientData,	/* Not used. */
754    Tcl_Interp *interp,		/* Interp with binding. */
755    XEvent *eventPtr,		/* X event that triggered binding. */
756    Tk_Window tkwin,		/* Target window for event. */
757    KeySym keySym)		/* The KeySym if a key event. */
758{
759    TkWindow *winPtr = (TkWindow*)tkwin;
760    TkScrollbar *scrollPtr = (TkScrollbar *) winPtr->instanceData;
761    MacScrollbar *macScrollPtr = (MacScrollbar *) winPtr->instanceData;
762
763    Tcl_Preserve((ClientData)scrollPtr);
764    macScrollPtr->macFlags |= IN_MODAL_LOOP;
765
766    if (eventPtr->type == ButtonPress) {
767	Point where;
768	Rect bounds;
769	ControlPartCode part;
770	CGrafPtr destPort, savePort;
771	Boolean portChanged;
772	Window window;
773
774	/*
775	 * To call Macintosh control routines we must have the port
776	 * set to the window containing the control. We will then test
777	 * which part of the control was hit and act accordingly.
778	 */
779	destPort = TkMacOSXGetDrawablePort(Tk_WindowId(scrollPtr->tkwin));
780	portChanged = QDSwapPort(destPort, &savePort);
781	TkMacOSXSetUpClippingRgn(Tk_WindowId(scrollPtr->tkwin));
782
783	TkMacOSXWinBounds((TkWindow *) scrollPtr->tkwin, &bounds);
784	where.h = eventPtr->xbutton.x + bounds.left;
785	where.v = eventPtr->xbutton.y + bounds.top;
786	part = TestControl(macScrollPtr->sbHandle, where);
787	TkMacOSXTrackingLoop(1);
788	if (part == kAppearancePartIndicator && scrollPtr->jump == false) {
789	    /*
790	     * Case 1: In thumb, no jump scrolling. Call track control
791	     * with the thumb action proc which will do most of the work.
792	     */
793	    mouseDownPoint.h = where.h;
794	    mouseDownPoint.v = where.v;
795	    part = HandleControlClick(macScrollPtr->sbHandle, where,
796		    TkMacOSXModifierState(), thumbActionProc);
797	} else if (part == kAppearancePartIndicator) {
798	    /*
799	     * Case 2: in thumb with jump scrolling. Call HandleControlClick
800	     * with a NULL action proc. Use the new value of the control
801	     * to set update the control.
802	     */
803	    part = HandleControlClick(macScrollPtr->sbHandle, where,
804		    TkMacOSXModifierState(), NULL);
805	    if (part == kAppearancePartIndicator) {
806		Tcl_DString cmdString;
807		char valueString[TCL_DOUBLE_SPACE];
808
809		sprintf(valueString, "%g",
810			(GetControl32BitValue(macScrollPtr->sbHandle) -
811			MIN_SCROLLBAR_VALUE) / SCROLLBAR_SCALING_VALUE);
812		Tcl_DStringInit(&cmdString);
813		Tcl_DStringAppend(&cmdString, scrollPtr->command,
814			strlen(scrollPtr->command));
815		Tcl_DStringAppendElement(&cmdString, "moveto");
816		Tcl_DStringAppendElement(&cmdString, valueString);
817
818		interp = scrollPtr->interp;
819		Tcl_Preserve((ClientData) interp);
820		Tcl_EvalEx(interp, Tcl_DStringValue(&cmdString),
821			Tcl_DStringLength(&cmdString), TCL_EVAL_GLOBAL);
822		Tcl_Release((ClientData) interp);
823		Tcl_DStringFree(&cmdString);
824		TkMacOSXRunTclEventLoop();
825	    }
826	} else if (part != 0) {
827	    /*
828	     * Case 3: in any other part of the scrollbar. We call
829	     * HandleControlClick with the scrollActionProc which will do
830	     * most all the work.
831	     */
832	    HandleControlClick(macScrollPtr->sbHandle, where,
833		    TkMacOSXModifierState(), scrollActionProc);
834	    /*
835	     * Workaround for Carbon bug where the scrollbar down arrow
836	     * sometimes gets "stuck" after the mousebutton has been released.
837	     */
838	    if (scrollPtr->tkwin) {
839		TkMacOSXSetUpClippingRgn(Tk_WindowId(scrollPtr->tkwin));
840	    }
841	    Draw1Control(macScrollPtr->sbHandle);
842	}
843	TkMacOSXTrackingLoop(0);
844
845	/*
846	 * The HandleControlClick call will "eat" the ButtonUp event. We now
847	 * generate a ButtonUp event so Tk will unset implicit grabs etc.
848	 */
849
850	if (scrollPtr->tkwin) {
851	    window = Tk_WindowId(scrollPtr->tkwin);
852	    TkGenerateButtonEventForXPointer(window);
853	}
854
855	if (portChanged) {
856	    QDSwapPort(savePort, NULL);
857	}
858    }
859
860    if (macScrollPtr->sbHandle && (macScrollPtr->macFlags & ALREADY_DEAD)) {
861	DisposeControl(macScrollPtr->sbHandle);
862	macScrollPtr->sbHandle = NULL;
863    }
864    macScrollPtr->macFlags &= ~IN_MODAL_LOOP;
865    Tcl_Release((ClientData)scrollPtr);
866
867    return TCL_OK;
868}
869
870/*
871 *--------------------------------------------------------------
872 *
873 * ScrollbarEventProc --
874 *
875 *	This procedure is invoked by the Tk dispatcher for various
876 *	events on scrollbars.
877 *
878 * Results:
879 *	None.
880 *
881 * Side effects:
882 *	When the window gets deleted, internal structures get
883 *	cleaned up. When it gets exposed, it is redisplayed.
884 *
885 *--------------------------------------------------------------
886 */
887
888static void
889ScrollbarEventProc(
890    ClientData clientData,	/* Information about window. */
891    XEvent *eventPtr)		/* Information about event. */
892{
893    TkScrollbar *scrollPtr = (TkScrollbar *) clientData;
894    MacScrollbar *macScrollPtr = (MacScrollbar *) clientData;
895
896    if (eventPtr->type == UnmapNotify) {
897	TkMacOSXSetScrollbarGrow((TkWindow *) scrollPtr->tkwin, false);
898    } else if (eventPtr->type == ActivateNotify) {
899	macScrollPtr->macFlags |= ACTIVE;
900	TkScrollbarEventuallyRedraw((ClientData) scrollPtr);
901    } else if (eventPtr->type == DeactivateNotify) {
902	macScrollPtr->macFlags &= ~ACTIVE;
903	TkScrollbarEventuallyRedraw((ClientData) scrollPtr);
904    } else {
905	TkScrollbarEventProc(clientData, eventPtr);
906    }
907}
908
909/*
910 *--------------------------------------------------------------
911 *
912 * UpdateControlValues --
913 *
914 *	This procedure updates the Macintosh scrollbar control
915 *	to display the values defined by the Tk scrollbar.
916 *
917 * Results:
918 *	None.
919 *
920 * Side effects:
921 *	The Macintosh control is updated.
922 *
923 *--------------------------------------------------------------
924 */
925
926static void
927UpdateControlValues(
928    MacScrollbar *macScrollPtr)		/* Scrollbar data struct. */
929{
930    TkScrollbar *scrollPtr = (TkScrollbar *) macScrollPtr;
931    Tk_Window tkwin = scrollPtr->tkwin;
932    MacDrawable * macDraw = (MacDrawable *) Tk_WindowId(scrollPtr->tkwin);
933    double dViewSize;
934    Rect contrlRect, portRect;
935    int variant, active;
936    short width, height;
937
938    contrlRect.left   = macDraw->xOff + scrollPtr->inset;
939    contrlRect.top    = macDraw->yOff + scrollPtr->inset;
940    contrlRect.right  = macDraw->xOff + Tk_Width(tkwin) - scrollPtr->inset;
941    contrlRect.bottom = macDraw->yOff + Tk_Height(tkwin) - scrollPtr->inset;
942
943    GetPortBounds (GetWindowPort(GetControlOwner(macScrollPtr->sbHandle)),
944	    &portRect);
945
946    /*
947     * If the scrollbar is flush against the bottom right hand corner then
948     * we leave space to draw the grow region for the window.
949     */
950    if (portRect.bottom == contrlRect.bottom &&
951	    portRect.right == contrlRect.right) {
952	TkMacOSXSetScrollbarGrow((TkWindow *) tkwin, true);
953	if (macDraw->toplevel &&
954		TkMacOSXResizable(macDraw->toplevel->winPtr)) {
955	    int growSize;
956
957	    switch (TkMacOSXWindowClass(macDraw->toplevel->winPtr)) {
958		case kFloatingWindowClass:
959		case kUtilityWindowClass:
960		    growSize = metrics[1].width - 1;
961		    break;
962		case kDocumentWindowClass:
963		case kMovableAlertWindowClass:
964		case kMovableModalWindowClass:
965		default:
966		    growSize = metrics[0].width - 1;
967		    break;
968	    }
969	    if (scrollPtr->vertical) {
970		contrlRect.bottom -= growSize;
971	    } else {
972		contrlRect.right -= growSize;
973	    }
974	}
975    } else {
976	TkMacOSXSetScrollbarGrow((TkWindow *) tkwin, false);
977    }
978
979    if (IsControlVisible (macScrollPtr->sbHandle)) {
980	SetControlVisibility(macScrollPtr->sbHandle, false, false);
981    }
982
983    if (scrollPtr->vertical) {
984	width  = contrlRect.right - contrlRect.left;
985	height = contrlRect.bottom - contrlRect.top;
986    } else {
987	width  = contrlRect.bottom - contrlRect.top;
988	height = contrlRect.right - contrlRect.left;
989    }
990    variant = width < metrics[0].width ? 1 : 0;
991    SetControlData(macScrollPtr->sbHandle, kControlEntireControl,
992	    kControlSizeTag, sizeof(ControlSize),
993	    &(metrics[variant].size));
994
995    macScrollPtr->eraseRect = contrlRect;
996    if (scrollPtr->vertical) {
997	macScrollPtr->eraseRect.left += metrics[variant].width;
998    } else {
999	macScrollPtr->eraseRect.top  += metrics[variant].width;
1000    }
1001
1002    /*
1003     * Ensure we set scrollbar control bounds only once all size
1004     * adjustments have been computed.
1005     */
1006
1007    SetControlBounds(macScrollPtr->sbHandle, &contrlRect);
1008
1009    /*
1010     * Given the Tk parameters for the fractions of the start and
1011     * end of the thumb, the following calculation determines the
1012     * location for the Macintosh thumb.
1013     * The Aqua scroll control works as follows.
1014     * The scrollbar's value is the position of the left (or top) side of
1015     * the view area in the content area being scrolled.
1016     * The maximum value of the control is therefore the dimension of
1017     * the content area less the size of the view area.
1018     * Since these values are all integers, and Tk gives the thumb position
1019     * as fractions, we have introduced a scaling factor.
1020     */
1021
1022    dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction)
1023	    * SCROLLBAR_SCALING_VALUE;
1024    SetControl32BitMinimum(macScrollPtr->sbHandle, MIN_SCROLLBAR_VALUE);
1025    SetControl32BitMaximum(macScrollPtr->sbHandle, MIN_SCROLLBAR_VALUE +
1026	    SCROLLBAR_SCALING_VALUE - dViewSize);
1027    SetControlViewSize(macScrollPtr->sbHandle, dViewSize);
1028    SetControl32BitValue(macScrollPtr->sbHandle, MIN_SCROLLBAR_VALUE +
1029	    SCROLLBAR_SCALING_VALUE * scrollPtr->firstFraction);
1030
1031    if((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0)
1032	    || height <= metrics[variant].minHeight) {
1033	/* Disable scrollbar */
1034	SetControl32BitMaximum(macScrollPtr->sbHandle, MIN_SCROLLBAR_VALUE);
1035    }
1036    active = ((macScrollPtr->macFlags & ACTIVE) != 0);
1037    if (active != IsControlActive(macScrollPtr->sbHandle)) {
1038	if (active) {
1039	    ActivateControl(macScrollPtr->sbHandle);
1040	} else {
1041	    DeactivateControl(macScrollPtr->sbHandle);
1042	}
1043    }
1044    SetControlVisibility(macScrollPtr->sbHandle, true, false);
1045}
1046