1/* 2 * tkMacOSXMenubutton.c -- 3 * 4 * This file implements the Macintosh specific portion of the 5 * menubutton 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 "tkMenubutton.h" 19#include "tkMacOSXFont.h" 20#include "tkMacOSXDebug.h" 21 22/* 23#ifdef TK_MAC_DEBUG 24#define TK_MAC_DEBUG_MENUBUTTON 25#endif 26*/ 27 28typedef struct MacMenuButton { 29 TkMenuButton info; 30 NSPopUpButton *button; 31#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 32 int fix; 33#endif 34} MacMenuButton; 35 36#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 37 38/* 39 * Use the following heuristic conversion constants to make NSButton-based 40 * widget metrics match up with the old Carbon control buttons (for the 41 * default Lucida Grande 13 font). 42 * TODO: provide a scriptable way to turn this off and use the raw NSButton 43 * metrics (will also need dynamic adjustment of the default padding, 44 * c.f. tkMacOSXDefault.h). 45 */ 46 47typedef struct { 48 int trimW, trimH, inset, shrinkW, offsetX, offsetY; 49} BoundsFix; 50 51#define fixForStyle(style) ( \ 52 style == NSRoundedBezelStyle ? 1 : \ 53 style == NSRegularSquareBezelStyle ? 2 : \ 54 style == NSShadowlessSquareBezelStyle ? 3 : \ 55 INT_MIN) 56 57static const BoundsFix boundsFixes[] = { 58 [fixForStyle(NSRoundedBezelStyle)] = { 14, 10, -2, -1}, 59 [fixForStyle(NSRegularSquareBezelStyle)] = { 6, 13, -2, 1, 1}, 60 [fixForStyle(NSShadowlessSquareBezelStyle)] = { 15, 0, 2 }, 61}; 62 63#endif 64 65/* 66 * Forward declarations for procedures defined later in this file: 67 */ 68 69static void MenuButtonEventProc(ClientData clientData, XEvent *eventPtr); 70 71/* 72 * The structure below defines menubutton class behavior by means of functions 73 * that can be invoked from generic window code. 74 */ 75 76Tk_ClassProcs tkpMenubuttonClass = { 77 sizeof(Tk_ClassProcs), /* size */ 78 TkMenuButtonWorldChanged, /* worldChangedProc */ 79}; 80 81/* 82 *---------------------------------------------------------------------- 83 * 84 * TkpCreateMenuButton -- 85 * 86 * Allocate a new TkMenuButton structure. 87 * 88 * Results: 89 * Returns a newly allocated TkMenuButton structure. 90 * 91 * Side effects: 92 * Registers an event handler for the widget. 93 * 94 *---------------------------------------------------------------------- 95 */ 96 97TkMenuButton * 98TkpCreateMenuButton( 99 Tk_Window tkwin) 100{ 101 MacMenuButton *macButtonPtr = 102 (MacMenuButton *) ckalloc(sizeof(MacMenuButton)); 103 104 macButtonPtr->button = nil; 105 106 Tk_CreateEventHandler(tkwin, ActivateMask, 107 MenuButtonEventProc, (ClientData) macButtonPtr); 108 return (TkMenuButton *) macButtonPtr; 109} 110 111/* 112 *---------------------------------------------------------------------- 113 * 114 * TkpDestroyMenuButton -- 115 * 116 * Free data structures associated with the menubutton control. 117 * 118 * Results: 119 * None. 120 * 121 * Side effects: 122 * Restores the default control state. 123 * 124 *---------------------------------------------------------------------- 125 */ 126 127void 128TkpDestroyMenuButton( 129 TkMenuButton *mbPtr) 130{ 131 MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; 132 133 TkMacOSXMakeCollectableAndRelease(macButtonPtr->button); 134} 135 136/* 137 *---------------------------------------------------------------------- 138 * 139 * TkpDisplayMenuButton -- 140 * 141 * This function is invoked to display a menubutton widget. 142 * 143 * Results: 144 * None. 145 * 146 * Side effects: 147 * Commands are output to X to display the menubutton in its current 148 * mode. 149 * 150 *---------------------------------------------------------------------- 151 */ 152 153void 154TkpDisplayMenuButton( 155 ClientData clientData) /* Information about widget. */ 156{ 157 TkMenuButton *mbPtr = (TkMenuButton *) clientData; 158 MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; 159 NSPopUpButton *button = macButtonPtr->button; 160 Tk_Window tkwin = mbPtr->tkwin; 161 TkWindow *winPtr = (TkWindow *) tkwin; 162 MacDrawable *macWin = (MacDrawable *) winPtr->window; 163 TkMacOSXDrawingContext dc; 164 NSView *view = TkMacOSXDrawableView(macWin); 165 CGFloat viewHeight = [view bounds].size.height; 166 CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, 167 .ty = viewHeight}; 168 NSRect frame; 169 int enabled; 170 171 mbPtr->flags &= ~REDRAW_PENDING; 172 if (!tkwin || !Tk_IsMapped(tkwin) || !view || 173 !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { 174 return; 175 } 176 CGContextConcatCTM(dc.context, t); 177 Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, mbPtr->normalBorder, 0, 0, 178 Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); 179 if ([button superview] != view) { 180 [view addSubview:button]; 181 } 182 enabled = !(mbPtr->state == STATE_DISABLED); 183 [button setEnabled:enabled]; 184 if (enabled) { 185 [[button cell] setHighlighted:(mbPtr->state == STATE_ACTIVE)]; 186 } 187 frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin), 188 Tk_Height(tkwin)); 189 frame = NSInsetRect(frame, mbPtr->inset, mbPtr->inset); 190#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 191 if (tkMacOSXUseCompatibilityMetrics) { 192 BoundsFix boundsFix = boundsFixes[macButtonPtr->fix]; 193 frame = NSOffsetRect(frame, boundsFix.offsetX, boundsFix.offsetY); 194 frame.size.width -= boundsFix.shrinkW; 195 frame = NSInsetRect(frame, boundsFix.inset, boundsFix.inset); 196 } 197#endif 198 frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); 199 if (!NSEqualRects(frame, [button frame])) { 200 [button setFrame:frame]; 201 } 202 [button displayRectIgnoringOpacity:[button bounds]]; 203 TkMacOSXRestoreDrawingContext(&dc); 204#ifdef TK_MAC_DEBUG_MENUBUTTON 205 TKLog(@"menubutton %s frame %@ width %d height %d", 206 ((TkWindow *)mbPtr->tkwin)->pathName, NSStringFromRect(frame), 207 Tk_Width(tkwin), Tk_Height(tkwin)); 208#endif 209} 210 211/* 212 *---------------------------------------------------------------------- 213 * 214 * TkpComputeMenuButtonGeometry -- 215 * 216 * After changes in a menu button's text or bitmap, this function 217 * recomputes the menu button's geometry and passes this information 218 * along to the geometry manager for the window. 219 * 220 * Results: 221 * None. 222 * 223 * Side effects: 224 * The menu button's window may change size. 225 * 226 *---------------------------------------------------------------------- 227 */ 228 229void 230TkpComputeMenuButtonGeometry( 231 TkMenuButton *mbPtr) /* Widget record for menu button. */ 232{ 233 MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; 234 NSPopUpButton *button = macButtonPtr->button; 235 NSPopUpButtonCell *cell; 236 NSMenuItem *menuItem; 237 NSBezelStyle style = NSRoundedBezelStyle; 238 NSFont *font; 239 NSRect bounds = NSZeroRect, titleRect = NSZeroRect; 240 int haveImage = (mbPtr->image || mbPtr->bitmap != None), haveText = 0; 241 int haveCompound = (mbPtr->compound != COMPOUND_NONE); 242 int width, height; 243 244 if (!button) { 245 button = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:YES]; 246 macButtonPtr->button = TkMacOSXMakeUncollectable(button); 247 cell = [button cell]; 248 [cell setUsesItemFromMenu:NO]; 249 menuItem = [[[NSMenuItem alloc] initWithTitle:@"" 250 action:NULL keyEquivalent:@""] autorelease]; 251 [cell setMenuItem:menuItem]; 252 } else { 253 cell = [button cell]; 254 menuItem = [cell menuItem]; 255 } 256 if (haveImage) { 257 style = NSShadowlessSquareBezelStyle; 258 } else if (!mbPtr->indicatorOn) { 259 style = NSRegularSquareBezelStyle; 260 } 261 [button setBezelStyle:style]; 262 [cell setArrowPosition:(mbPtr->indicatorOn ? NSPopUpArrowAtBottom : 263 NSPopUpNoArrow)]; 264#if 0 265 NSControlSize controlSize = NSRegularControlSize; 266 267 if (mbPtr->borderWidth <= 2) { 268 controlSize = NSMiniControlSize; 269 } else if (mbPtr->borderWidth == 3) { 270 controlSize = NSSmallControlSize; 271 } 272 [cell setControlSize:controlSize]; 273#endif 274 275 if (mbPtr->text && *(mbPtr->text) && (!haveImage || haveCompound)) { 276 NSString *title = [[NSString alloc] initWithUTF8String:mbPtr->text]; 277 [button setTitle:title]; 278 [title release]; 279 haveText = 1; 280 } 281 haveCompound = (haveCompound && haveImage && haveText); 282 if (haveText) { 283 NSTextAlignment alignment = NSNaturalTextAlignment; 284 285 switch (mbPtr->justify) { 286 case TK_JUSTIFY_LEFT: 287 alignment = NSLeftTextAlignment; 288 break; 289 case TK_JUSTIFY_RIGHT: 290 alignment = NSRightTextAlignment; 291 break; 292 case TK_JUSTIFY_CENTER: 293 alignment = NSCenterTextAlignment; 294 break; 295 } 296 [button setAlignment:alignment]; 297 } else { 298 [button setTitle:@""]; 299 } 300 font = TkMacOSXNSFontForFont(mbPtr->tkfont); 301 if (font) { 302 [button setFont:font]; 303 } 304 if (haveImage) { 305 int width, height; 306 NSImage *image; 307 NSCellImagePosition pos = NSImageOnly; 308 309 if (mbPtr->image) { 310 Tk_SizeOfImage(mbPtr->image, &width, &height); 311 image = TkMacOSXGetNSImageWithTkImage(mbPtr->display, 312 mbPtr->image, width, height); 313 } else { 314 Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height); 315 image = TkMacOSXGetNSImageWithBitmap(mbPtr->display, 316 mbPtr->bitmap, mbPtr->normalTextGC, width, height); 317 } 318 if (haveCompound) { 319 switch ((enum compound) mbPtr->compound) { 320 case COMPOUND_TOP: 321 pos = NSImageAbove; 322 break; 323 case COMPOUND_BOTTOM: 324 pos = NSImageBelow; 325 break; 326 case COMPOUND_LEFT: 327 pos = NSImageLeft; 328 break; 329 case COMPOUND_RIGHT: 330 pos = NSImageRight; 331 break; 332 case COMPOUND_CENTER: 333 pos = NSImageOverlaps; 334 break; 335 case COMPOUND_NONE: 336 pos = NSImageOnly; 337 break; 338 } 339 } 340 [button setImagePosition:pos]; 341 [menuItem setImage:image]; 342 bounds.size = cell ? [cell cellSize] : NSZeroSize; 343 if (bounds.size.height < height + 8) { /* workaround AppKit sizing bug */ 344 bounds.size.height = height + 8; 345 } 346#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 347 if (!mbPtr->indicatorOn && tkMacOSXUseCompatibilityMetrics) { 348 bounds.size.width -= 16; 349 } 350#endif 351 } else { 352 bounds.size = cell ? [cell cellSize] : NSZeroSize; 353 } 354 if (haveText) { 355 titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; 356 if (mbPtr->wrapLength > 0 && 357 titleRect.size.width > mbPtr->wrapLength) { 358 if (style == NSRoundedBezelStyle) { 359 [button setBezelStyle:(style = NSRegularSquareBezelStyle)]; 360 bounds.size = cell ? [cell cellSize] : NSZeroSize; 361 titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; 362 } 363 bounds.size.width -= titleRect.size.width - mbPtr->wrapLength; 364 bounds.size.height = 40000.0; 365 [cell setWraps:YES]; 366 bounds.size = cell ? [cell cellSizeForBounds:bounds] : NSZeroSize; 367#ifdef TK_MAC_DEBUG_MENUBUTTON 368 titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; 369#endif 370#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 371 if (tkMacOSXUseCompatibilityMetrics) { 372 bounds.size.height += 3; 373 } 374#endif 375 } 376 } 377 width = lround(bounds.size.width); 378 height = lround(bounds.size.height); 379#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 380 if (tkMacOSXUseCompatibilityMetrics) { 381 macButtonPtr->fix = fixForStyle(style); 382 width -= boundsFixes[macButtonPtr->fix].trimW; 383 height -= boundsFixes[macButtonPtr->fix].trimH; 384 } 385#endif 386 387 if (haveImage || haveCompound) { 388 if (mbPtr->width > 0) { 389 width = mbPtr->width; 390 } 391 if (mbPtr->height > 0) { 392 height = mbPtr->height; 393 } 394 } else { 395 if (mbPtr->width > 0) { 396 int avgWidth = Tk_TextWidth(mbPtr->tkfont, "0", 1); 397 width = mbPtr->width * avgWidth; 398 } 399 if (mbPtr->height > 0) { 400 Tk_FontMetrics fm; 401 402 Tk_GetFontMetrics(mbPtr->tkfont, &fm); 403 height = mbPtr->height * fm.linespace; 404 } 405 } 406 if (!haveImage || haveCompound) { 407 width += 2*mbPtr->padX; 408 height += 2*mbPtr->padY; 409 } 410 if (mbPtr->highlightWidth < 0) { 411 mbPtr->highlightWidth = 0; 412 } 413 if (haveImage) { 414 mbPtr->inset = mbPtr->highlightWidth; 415 width += 2*mbPtr->borderWidth; 416 height += 2*mbPtr->borderWidth; 417 } else { 418 mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth; 419 } 420 Tk_GeometryRequest(mbPtr->tkwin, width + 2 * mbPtr->inset, 421 height + 2 * mbPtr->inset); 422 Tk_SetInternalBorder(mbPtr->tkwin, mbPtr->inset); 423#ifdef TK_MAC_DEBUG_MENUBUTTON 424 TKLog(@"menubutton %s bounds %@ titleRect %@ width %d height %d inset %d borderWidth %d", 425 ((TkWindow *)mbPtr->tkwin)->pathName, NSStringFromRect(bounds), 426 NSStringFromRect(titleRect), width, height, mbPtr->inset, 427 mbPtr->borderWidth); 428#endif 429} 430 431/* 432 *-------------------------------------------------------------- 433 * 434 * MenuButtonEventProc -- 435 * 436 * This procedure is invoked by the Tk dispatcher for various 437 * events on buttons. 438 * 439 * Results: 440 * None. 441 * 442 * Side effects: 443 * When activation state changes, it is redisplayed. 444 * 445 *-------------------------------------------------------------- 446 */ 447 448static void 449MenuButtonEventProc( 450 ClientData clientData, /* Information about window. */ 451 XEvent *eventPtr) /* Information about event. */ 452{ 453 TkMenuButton *mbPtr = (TkMenuButton *) clientData; 454 455 if (!mbPtr->tkwin || !Tk_IsMapped(mbPtr->tkwin)) { 456 return; 457 } 458 switch (eventPtr->type) { 459 case ActivateNotify: 460 case DeactivateNotify: 461 if (!(mbPtr->flags & REDRAW_PENDING)) { 462 Tcl_DoWhenIdle(TkpDisplayMenuButton, (ClientData) mbPtr); 463 mbPtr->flags |= REDRAW_PENDING; 464 } 465 break; 466 } 467} 468 469/* 470 * Local Variables: 471 * mode: c 472 * c-basic-offset: 4 473 * fill-column: 79 474 * coding: utf-8 475 * End: 476 */ 477