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 * 11 * See the file "license.terms" for information on usage and redistribution 12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id$ 15 */ 16 17#include "tkMacOSXPrivate.h" 18#include "tkScrollbar.h" 19 20/* 21#ifdef TK_MAC_DEBUG 22#define TK_MAC_DEBUG_SCROLLBAR 23#endif 24*/ 25 26/* 27 * Declaration of Mac specific scrollbar structure. 28 */ 29 30typedef struct MacScrollbar { 31 TkScrollbar info; 32 NSScroller *scroller; 33 int variant; 34} MacScrollbar; 35 36typedef struct ScrollbarMetrics { 37 SInt32 width, minThumbHeight; 38 int minHeight, topArrowHeight, bottomArrowHeight; 39 NSControlSize controlSize; 40} ScrollbarMetrics; 41 42static ScrollbarMetrics metrics[2] = { 43 {15, 54, 26, 14, 14, NSRegularControlSize}, /* kThemeScrollBarMedium */ 44 {11, 40, 20, 10, 10, NSSmallControlSize}, /* kThemeScrollBarSmall */ 45}; 46 47/* 48 * This variable holds the default width for a scrollbar in string form for 49 * use in a Tk_ConfigSpec. 50 */ 51 52static char defWidth[TCL_INTEGER_SPACE]; 53 54/* 55 * Declarations for functions defined in this file. 56 */ 57 58static void UpdateScrollbarMetrics(void); 59static void ScrollbarEventProc(ClientData clientData, 60 XEvent *eventPtr); 61 62/* 63 * The class procedure table for the scrollbar widget. 64 */ 65 66Tk_ClassProcs tkpScrollbarProcs = { 67 sizeof(Tk_ClassProcs) /* size */ 68}; 69 70#pragma mark TKApplication(TKScrlbr) 71 72#define NSAppleAquaScrollBarVariantChanged @"AppleAquaScrollBarVariantChanged" 73 74@implementation TKApplication(TKScrlbr) 75- (void)tkScroller:(NSScroller *)scroller { 76 NSScrollerPart hitPart = [scroller hitPart]; 77 TkScrollbar *scrollPtr = (TkScrollbar *)[scroller tag]; 78 Tcl_DString cmdString; 79 Tcl_Interp *interp; 80 int result; 81 82 if (!scrollPtr || !scrollPtr->command || !scrollPtr->commandSize || 83 hitPart == NSScrollerNoPart) { 84 return; 85 } 86 87 Tcl_DStringInit(&cmdString); 88 Tcl_DStringAppend(&cmdString, scrollPtr->command, 89 scrollPtr->commandSize); 90 switch (hitPart) { 91 case NSScrollerKnob: 92 case NSScrollerKnobSlot: { 93 char valueString[TCL_DOUBLE_SPACE]; 94 95 Tcl_PrintDouble(NULL, [scroller doubleValue] * 96 (1.0 - [scroller knobProportion]), valueString); 97 Tcl_DStringAppendElement(&cmdString, "moveto"); 98 Tcl_DStringAppendElement(&cmdString, valueString); 99 break; 100 } 101 case NSScrollerDecrementLine: 102 case NSScrollerIncrementLine: 103 Tcl_DStringAppendElement(&cmdString, "scroll"); 104 Tcl_DStringAppendElement(&cmdString, 105 (hitPart == NSScrollerDecrementLine) ? "-1" : "1"); 106 Tcl_DStringAppendElement(&cmdString, "unit"); 107 break; 108 case NSScrollerDecrementPage: 109 case NSScrollerIncrementPage: 110 Tcl_DStringAppendElement(&cmdString, "scroll"); 111 Tcl_DStringAppendElement(&cmdString, 112 (hitPart == NSScrollerDecrementPage) ? "-1" : "1"); 113 Tcl_DStringAppendElement(&cmdString, "page"); 114 break; 115 } 116 interp = scrollPtr->interp; 117 Tcl_Preserve(interp); 118 Tcl_Preserve(scrollPtr); 119 result = Tcl_EvalEx(interp, Tcl_DStringValue(&cmdString), 120 Tcl_DStringLength(&cmdString), TCL_EVAL_GLOBAL); 121 if (result != TCL_OK && result != TCL_CONTINUE && result != TCL_BREAK) { 122 Tcl_AddErrorInfo(interp, "\n (scrollbar command)"); 123 Tcl_BackgroundError(interp); 124 } 125 Tcl_Release(scrollPtr); 126 Tcl_Release(interp); 127 Tcl_DStringFree(&cmdString); 128#ifdef TK_MAC_DEBUG_SCROLLBAR 129 TKLog(@"scroller %s value %f knobProportion %f", 130 ((TkWindow *)scrollPtr->tkwin)->pathName, [scroller doubleValue], 131 [scroller knobProportion]); 132#endif 133} 134- (void)scrollBarVariantChanged:(NSNotification *)notification { 135#ifdef TK_MAC_DEBUG_NOTIFICATIONS 136 TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); 137#endif 138 UpdateScrollbarMetrics(); 139} 140- (void)_setupScrollBarNotifications { 141 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 142#define observe(n, s) [nc addObserver:self selector:@selector(s) name:(n) object:nil] 143 observe(NSAppleAquaScrollBarVariantChanged, scrollBarVariantChanged:); 144#undef observe 145 146 UpdateScrollbarMetrics(); 147} 148@end 149 150#pragma mark - 151 152/* 153 *---------------------------------------------------------------------- 154 * 155 * UpdateScrollbarMetrics -- 156 * 157 * This function retrieves the current system metrics for a scrollbar. 158 * 159 * Results: 160 * None. 161 * 162 * Side effects: 163 * Updates the geometry cache info for all scrollbars. 164 * 165 *---------------------------------------------------------------------- 166 */ 167 168static void 169UpdateScrollbarMetrics(void) 170{ 171 const short height = 100, width = 50; 172 HIThemeTrackDrawInfo info = { 173 .version = 0, 174 .bounds = {{0, 0}, {width, height}}, 175 .min = 0, 176 .max = 1, 177 .value = 0, 178 .attributes = kThemeTrackShowThumb, 179 .enableState = kThemeTrackActive, 180 .trackInfo.scrollbar = {.viewsize = 1, .pressState = 0}, 181 }; 182 CGRect bounds; 183 Tk_ConfigSpec *specPtr; 184 185 ChkErr(GetThemeMetric, kThemeMetricScrollBarWidth, &metrics[0].width); 186 ChkErr(GetThemeMetric, kThemeMetricScrollBarMinThumbHeight, 187 &metrics[0].minThumbHeight); 188 info.kind = kThemeScrollBarMedium; 189 ChkErr(HIThemeGetTrackDragRect, &info, &bounds); 190 metrics[0].topArrowHeight = bounds.origin.y; 191 metrics[0].bottomArrowHeight = height - (bounds.origin.y + 192 bounds.size.height); 193 metrics[0].minHeight = metrics[0].minThumbHeight + 194 metrics[0].topArrowHeight + metrics[0].bottomArrowHeight; 195 ChkErr(GetThemeMetric, kThemeMetricSmallScrollBarWidth, &metrics[1].width); 196 ChkErr(GetThemeMetric, kThemeMetricSmallScrollBarMinThumbHeight, 197 &metrics[1].minThumbHeight); 198 info.kind = kThemeScrollBarSmall; 199 ChkErr(HIThemeGetTrackDragRect, &info, &bounds); 200 metrics[1].topArrowHeight = bounds.origin.y; 201 metrics[1].bottomArrowHeight = height - (bounds.origin.y + 202 bounds.size.height); 203 metrics[1].minHeight = metrics[1].minThumbHeight + 204 metrics[1].topArrowHeight + metrics[1].bottomArrowHeight; 205 206 sprintf(defWidth, "%d", (int)(metrics[0].width)); 207 for (specPtr = tkpScrollbarConfigSpecs; specPtr->type != TK_CONFIG_END; 208 specPtr++) { 209 if (specPtr->offset == Tk_Offset(TkScrollbar, width)) { 210 specPtr->defValue = defWidth; 211 } 212 } 213} 214 215/* 216 *---------------------------------------------------------------------- 217 * 218 * TkpCreateScrollbar -- 219 * 220 * Allocate a new TkScrollbar structure. 221 * 222 * Results: 223 * Returns a newly allocated TkScrollbar structure. 224 * 225 * Side effects: 226 * Registers an event handler for the widget. 227 * 228 *---------------------------------------------------------------------- 229 */ 230 231TkScrollbar * 232TkpCreateScrollbar( 233 Tk_Window tkwin) 234{ 235 MacScrollbar *scrollPtr = (MacScrollbar *) ckalloc(sizeof(MacScrollbar)); 236 237 scrollPtr->scroller = nil; 238 239 Tk_CreateEventHandler(tkwin, ActivateMask|ExposureMask| 240 StructureNotifyMask|FocusChangeMask, 241 ScrollbarEventProc, (ClientData) scrollPtr); 242 243 return (TkScrollbar *) scrollPtr; 244} 245 246/* 247 *---------------------------------------------------------------------- 248 * 249 * TkpDestroyScrollbar -- 250 * 251 * Free data structures associated with the scrollbar control. 252 * 253 * Results: 254 * None. 255 * 256 * Side effects: 257 * None. 258 * 259 *---------------------------------------------------------------------- 260 */ 261 262void 263TkpDestroyScrollbar( 264 TkScrollbar *scrollPtr) 265{ 266 MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr; 267 268 TkMacOSXMakeCollectableAndRelease(macScrollPtr->scroller); 269} 270 271/* 272 *-------------------------------------------------------------- 273 * 274 * TkpDisplayScrollbar -- 275 * 276 * This procedure redraws the contents of a scrollbar window. It is 277 * invoked as a do-when-idle handler, so it only runs when there's 278 * nothing else for the application to do. 279 * 280 * Results: 281 * None. 282 * 283 * Side effects: 284 * Information appears on the screen. 285 * 286 *-------------------------------------------------------------- 287 */ 288 289void 290TkpDisplayScrollbar( 291 ClientData clientData) /* Information about window. */ 292{ 293 TkScrollbar *scrollPtr = (TkScrollbar *) clientData; 294 MacScrollbar *macScrollPtr = (MacScrollbar *) clientData; 295 NSScroller *scroller = macScrollPtr->scroller; 296 Tk_Window tkwin = scrollPtr->tkwin; 297 TkWindow *winPtr = (TkWindow *) tkwin; 298 MacDrawable *macWin = (MacDrawable *) winPtr->window; 299 TkMacOSXDrawingContext dc; 300 NSView *view = TkMacOSXDrawableView(macWin); 301 CGFloat viewHeight = [view bounds].size.height; 302 CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, 303 .ty = viewHeight}; 304 NSRect frame; 305 double knobProportion = scrollPtr->lastFraction - scrollPtr->firstFraction; 306 307 scrollPtr->flags &= ~REDRAW_PENDING; 308 if (!scrollPtr->tkwin || !Tk_IsMapped(tkwin) || !view || 309 !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { 310 return; 311 } 312 CGContextConcatCTM(dc.context, t); 313 if (scrollPtr->highlightWidth != 0) { 314 GC fgGC, bgGC; 315 316 bgGC = Tk_GCForColor(scrollPtr->highlightBgColorPtr, (Pixmap) macWin); 317 if (scrollPtr->flags & GOT_FOCUS) { 318 fgGC = Tk_GCForColor(scrollPtr->highlightColorPtr, (Pixmap) macWin); 319 } else { 320 fgGC = bgGC; 321 } 322 TkpDrawHighlightBorder(tkwin, fgGC, bgGC, scrollPtr->highlightWidth, 323 (Pixmap) macWin); 324 } 325 Tk_Draw3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder, 326 scrollPtr->highlightWidth, scrollPtr->highlightWidth, 327 Tk_Width(tkwin) - 2*scrollPtr->highlightWidth, 328 Tk_Height(tkwin) - 2*scrollPtr->highlightWidth, 329 scrollPtr->borderWidth, scrollPtr->relief); 330 Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder, 331 scrollPtr->inset, scrollPtr->inset, 332 Tk_Width(tkwin) - 2*scrollPtr->inset, 333 Tk_Height(tkwin) - 2*scrollPtr->inset, 0, TK_RELIEF_FLAT); 334 if ([scroller superview] != view) { 335 [view addSubview:scroller]; 336 } 337 frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin), 338 Tk_Height(tkwin)); 339 frame = NSInsetRect(frame, scrollPtr->inset, scrollPtr->inset); 340 frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); 341 NSWindow *w = [view window]; 342 if ([w showsResizeIndicator]) { 343 NSRect growBox = [view convertRect:[w _growBoxRect] fromView:nil]; 344 if (NSIntersectsRect(growBox, frame)) { 345 if (scrollPtr->vertical) { 346 CGFloat y = frame.origin.y; 347 frame.origin.y = growBox.origin.y + growBox.size.height; 348 frame.size.height -= frame.origin.y - y; 349 } else { 350 frame.size.width = growBox.origin.x - frame.origin.x; 351 } 352 TkMacOSXSetScrollbarGrow(winPtr, true); 353 } 354 } 355 if (!NSEqualRects(frame, [scroller frame])) { 356 [scroller setFrame:frame]; 357 } 358 [scroller setEnabled:(knobProportion < 1.0 && 359 (scrollPtr->vertical ? frame.size.height : frame.size.width) > 360 metrics[macScrollPtr->variant].minHeight)]; 361 [scroller setDoubleValue:scrollPtr->firstFraction / (1.0 - knobProportion)]; 362 [scroller setKnobProportion:knobProportion]; 363 [scroller displayRectIgnoringOpacity:[scroller bounds]]; 364 TkMacOSXRestoreDrawingContext(&dc); 365#ifdef TK_MAC_DEBUG_SCROLLBAR 366 TKLog(@"scroller %s frame %@ width %d height %d", 367 ((TkWindow *)scrollPtr->tkwin)->pathName, NSStringFromRect(frame), 368 Tk_Width(tkwin), Tk_Height(tkwin)); 369#endif 370} 371 372/* 373 *---------------------------------------------------------------------- 374 * 375 * TkpComputeScrollbarGeometry -- 376 * 377 * After changes in a scrollbar's size or configuration, this procedure 378 * recomputes various geometry information used in displaying the 379 * scrollbar. 380 * 381 * Results: 382 * None. 383 * 384 * Side effects: 385 * The scrollbar will be displayed differently. 386 * 387 *---------------------------------------------------------------------- 388 */ 389 390void 391TkpComputeScrollbarGeometry( 392 register TkScrollbar *scrollPtr) 393 /* Scrollbar whose geometry may have 394 * changed. */ 395{ 396 MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr; 397 NSScroller *scroller = macScrollPtr->scroller; 398 int width, height, variant, fieldLength; 399 400 if (scrollPtr->highlightWidth < 0) { 401 scrollPtr->highlightWidth = 0; 402 } 403 scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth; 404 width = Tk_Width(scrollPtr->tkwin) - 2 * scrollPtr->inset; 405 height = Tk_Height(scrollPtr->tkwin) - 2 * scrollPtr->inset; 406 variant = ((scrollPtr->vertical ? width : height) < metrics[0].width) ? 407 1 : 0; 408 macScrollPtr->variant = variant; 409 if (scroller) { 410 NSSize size = [scroller frame].size; 411 if ((size.width > size.height) ^ !scrollPtr->vertical) { 412 /* Orientation changed, need new scroller */ 413 if ([scroller superview]) { 414 [scroller removeFromSuperviewWithoutNeedingDisplay]; 415 } 416 TkMacOSXMakeCollectableAndRelease(scroller); 417 } 418 } 419 if (!scroller) { 420 if ((width > height) ^ !scrollPtr->vertical) { 421 /* -[NSScroller initWithFrame:] determines horizonalness for the 422 * lifetime of the scroller via isHoriz = (width > height) */ 423 if (scrollPtr->vertical) { 424 width = height; 425 } else if (width > 1) { 426 height = width - 1; 427 } else { 428 height = 1; 429 width = 2; 430 } 431 } 432 scroller = [[NSScroller alloc] initWithFrame: 433 NSMakeRect(0, 0, width, height)]; 434 macScrollPtr->scroller = TkMacOSXMakeUncollectable(scroller); 435 [scroller setAction:@selector(tkScroller:)]; 436 [scroller setTarget:NSApp]; 437 [scroller setTag:(NSInteger)scrollPtr]; 438 } 439 [[scroller cell] setControlSize:metrics[variant].controlSize]; 440 441 scrollPtr->arrowLength = (metrics[variant].topArrowHeight + 442 metrics[variant].bottomArrowHeight) / 2; 443 fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin) 444 : Tk_Width(scrollPtr->tkwin)) 445 - 2 * (scrollPtr->arrowLength + scrollPtr->inset); 446 if (fieldLength < 0) { 447 fieldLength = 0; 448 } 449 scrollPtr->sliderFirst = fieldLength * scrollPtr->firstFraction; 450 scrollPtr->sliderLast = fieldLength * scrollPtr->lastFraction; 451 452 /* 453 * Adjust the slider so that some piece of it is always displayed in the 454 * scrollbar and so that it has at least a minimal width (so it can be 455 * grabbed with the mouse). 456 */ 457 458 if (scrollPtr->sliderFirst > (fieldLength - 2*scrollPtr->borderWidth)) { 459 scrollPtr->sliderFirst = fieldLength - 2*scrollPtr->borderWidth; 460 } 461 if (scrollPtr->sliderFirst < 0) { 462 scrollPtr->sliderFirst = 0; 463 } 464 if (scrollPtr->sliderLast < (scrollPtr->sliderFirst + 465 metrics[variant].minThumbHeight)) { 466 scrollPtr->sliderLast = scrollPtr->sliderFirst + 467 metrics[variant].minThumbHeight; 468 } 469 if (scrollPtr->sliderLast > fieldLength) { 470 scrollPtr->sliderLast = fieldLength; 471 } 472 scrollPtr->sliderFirst += scrollPtr->inset + 473 metrics[variant].topArrowHeight; 474 scrollPtr->sliderLast += scrollPtr->inset + 475 metrics[variant].bottomArrowHeight; 476 477 /* 478 * Register the desired geometry for the window (leave enough space for 479 * the two arrows plus a minimum-size slider, plus border around the whole 480 * window, if any). Then arrange for the window to be redisplayed. 481 */ 482 483 if (scrollPtr->vertical) { 484 Tk_GeometryRequest(scrollPtr->tkwin, scrollPtr->width + 485 2 * scrollPtr->inset, 2 * (scrollPtr->arrowLength + 486 scrollPtr->borderWidth + scrollPtr->inset) + 487 metrics[variant].minThumbHeight); 488 } else { 489 Tk_GeometryRequest(scrollPtr->tkwin, 2 * (scrollPtr->arrowLength + 490 scrollPtr->borderWidth + scrollPtr->inset) + 491 metrics[variant].minThumbHeight, scrollPtr->width + 492 2 * scrollPtr->inset); 493 } 494 Tk_SetInternalBorder(scrollPtr->tkwin, scrollPtr->inset); 495#ifdef TK_MAC_DEBUG_SCROLLBAR 496 TKLog(@"scroller %s bounds %@ width %d height %d inset %d borderWidth %d", 497 ((TkWindow *)scrollPtr->tkwin)->pathName, 498 NSStringFromRect([scroller bounds]), 499 width, height, scrollPtr->inset, scrollPtr->borderWidth); 500#endif 501} 502 503/* 504 *---------------------------------------------------------------------- 505 * 506 * TkpConfigureScrollbar -- 507 * 508 * This procedure is called after the generic code has finished 509 * processing configuration options, in order to configure platform 510 * specific options. 511 * 512 * Results: 513 * None. 514 * 515 * Side effects: 516 * None. 517 * 518 *---------------------------------------------------------------------- 519 */ 520 521void 522TkpConfigureScrollbar( 523 register TkScrollbar *scrollPtr) 524 /* Information about widget; may or may not 525 * already have values for some fields. */ 526{ 527} 528 529/* 530 *-------------------------------------------------------------- 531 * 532 * TkpScrollbarPosition -- 533 * 534 * Determine the scrollbar element corresponding to a given position. 535 * 536 * Results: 537 * One of TOP_ARROW, TOP_GAP, etc., indicating which element of the 538 * scrollbar covers the position given by (x, y). If (x,y) is outside the 539 * scrollbar entirely, then OUTSIDE is returned. 540 * 541 * Side effects: 542 * None. 543 * 544 *-------------------------------------------------------------- 545 */ 546 547int 548TkpScrollbarPosition( 549 register TkScrollbar *scrollPtr, 550 /* Scrollbar widget record. */ 551 int x, int y) /* Coordinates within scrollPtr's window. */ 552{ 553 NSScroller *scroller = ((MacScrollbar *) scrollPtr)->scroller; 554 MacDrawable *macWin = (MacDrawable *) 555 ((TkWindow *) scrollPtr->tkwin)->window; 556 NSView *view = TkMacOSXDrawableView(macWin); 557 558 switch ([scroller testPart:NSMakePoint(macWin->xOff + x, 559 [view bounds].size.height - (macWin->yOff + y))]) { 560 case NSScrollerDecrementLine: 561 return TOP_ARROW; 562 case NSScrollerDecrementPage: 563 return TOP_GAP; 564 case NSScrollerKnob: 565 return SLIDER; 566 case NSScrollerIncrementPage: 567 return BOTTOM_GAP; 568 case NSScrollerIncrementLine: 569 return BOTTOM_ARROW; 570 case NSScrollerKnobSlot: 571 case NSScrollerNoPart: 572 default: 573 return OUTSIDE; 574 } 575} 576 577/* 578 *-------------------------------------------------------------- 579 * 580 * ScrollbarEventProc -- 581 * 582 * This procedure is invoked by the Tk dispatcher for various events on 583 * scrollbars. 584 * 585 * Results: 586 * None. 587 * 588 * Side effects: 589 * When the window gets deleted, internal structures get cleaned up. When 590 * it gets exposed, it is redisplayed. 591 * 592 *-------------------------------------------------------------- 593 */ 594 595static void 596ScrollbarEventProc( 597 ClientData clientData, /* Information about window. */ 598 XEvent *eventPtr) /* Information about event. */ 599{ 600 TkScrollbar *scrollPtr = (TkScrollbar *) clientData; 601 602 switch (eventPtr->type) { 603 case UnmapNotify: 604 TkMacOSXSetScrollbarGrow((TkWindow *) scrollPtr->tkwin, false); 605 break; 606 case ActivateNotify: 607 case DeactivateNotify: 608 TkScrollbarEventuallyRedraw((ClientData) scrollPtr); 609 break; 610 default: 611 TkScrollbarEventProc(clientData, eventPtr); 612 } 613} 614 615/* 616 * Local Variables: 617 * mode: c 618 * c-basic-offset: 4 619 * fill-column: 79 620 * coding: utf-8 621 * End: 622 */ 623