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, ¤tPoint, 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