/* * tkMacOSXMenubutton.c -- * * This file implements the Macintosh specific portion of the * menubutton widget. * * Copyright (c) 1996 by Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkMacOSXPrivate.h" #include "tkMenubutton.h" #include "tkMacOSXFont.h" #include "tkMacOSXDebug.h" /* #ifdef TK_MAC_DEBUG #define TK_MAC_DEBUG_MENUBUTTON #endif */ typedef struct MacMenuButton { TkMenuButton info; NSPopUpButton *button; #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS int fix; #endif } MacMenuButton; #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS /* * Use the following heuristic conversion constants to make NSButton-based * widget metrics match up with the old Carbon control buttons (for the * default Lucida Grande 13 font). * TODO: provide a scriptable way to turn this off and use the raw NSButton * metrics (will also need dynamic adjustment of the default padding, * c.f. tkMacOSXDefault.h). */ typedef struct { int trimW, trimH, inset, shrinkW, offsetX, offsetY; } BoundsFix; #define fixForStyle(style) ( \ style == NSRoundedBezelStyle ? 1 : \ style == NSRegularSquareBezelStyle ? 2 : \ style == NSShadowlessSquareBezelStyle ? 3 : \ INT_MIN) static const BoundsFix boundsFixes[] = { [fixForStyle(NSRoundedBezelStyle)] = { 14, 10, -2, -1}, [fixForStyle(NSRegularSquareBezelStyle)] = { 6, 13, -2, 1, 1}, [fixForStyle(NSShadowlessSquareBezelStyle)] = { 15, 0, 2 }, }; #endif /* * Forward declarations for procedures defined later in this file: */ static void MenuButtonEventProc(ClientData clientData, XEvent *eventPtr); /* *---------------------------------------------------------------------- * * TkpCreateMenuButton -- * * Allocate a new TkMenuButton structure. * * Results: * Returns a newly allocated TkMenuButton structure. * * Side effects: * Registers an event handler for the widget. * *---------------------------------------------------------------------- */ TkMenuButton * TkpCreateMenuButton( Tk_Window tkwin) { MacMenuButton *macButtonPtr = ckalloc(sizeof(MacMenuButton)); macButtonPtr->button = nil; Tk_CreateEventHandler(tkwin, ActivateMask, MenuButtonEventProc, (ClientData) macButtonPtr); return (TkMenuButton *) macButtonPtr; } /* *---------------------------------------------------------------------- * * TkpDestroyMenuButton -- * * Free data structures associated with the menubutton control. * * Results: * None. * * Side effects: * Restores the default control state. * *---------------------------------------------------------------------- */ void TkpDestroyMenuButton( TkMenuButton *mbPtr) { MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; [macButtonPtr->button setTag:(NSInteger)-1]; TkMacOSXMakeCollectableAndRelease(macButtonPtr->button); } /* *---------------------------------------------------------------------- * * TkpDisplayMenuButton -- * * This function is invoked to display a menubutton widget. * * Results: * None. * * Side effects: * Commands are output to X to display the menubutton in its current * mode. * *---------------------------------------------------------------------- */ void TkpDisplayMenuButton( ClientData clientData) /* Information about widget. */ { TkMenuButton *mbPtr = (TkMenuButton *) clientData; MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; NSPopUpButton *button = macButtonPtr->button; Tk_Window tkwin = mbPtr->tkwin; TkWindow *winPtr = (TkWindow *) tkwin; MacDrawable *macWin = (MacDrawable *) winPtr->window; TkMacOSXDrawingContext dc; NSView *view = TkMacOSXDrawableView(macWin); CGFloat viewHeight = [view bounds].size.height; CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, .ty = viewHeight}; NSRect frame; int enabled; mbPtr->flags &= ~REDRAW_PENDING; if (!tkwin || !Tk_IsMapped(tkwin) || !view || !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { return; } CGContextConcatCTM(dc.context, t); Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, mbPtr->normalBorder, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); if ([button superview] != view) { [view addSubview:button]; } enabled = !(mbPtr->state == STATE_DISABLED); [button setEnabled:enabled]; if (enabled) { [[button cell] setHighlighted:(mbPtr->state == STATE_ACTIVE)]; } frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin), Tk_Height(tkwin)); frame = NSInsetRect(frame, mbPtr->inset, mbPtr->inset); #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS if (tkMacOSXUseCompatibilityMetrics) { BoundsFix boundsFix = boundsFixes[macButtonPtr->fix]; frame = NSOffsetRect(frame, boundsFix.offsetX, boundsFix.offsetY); frame.size.width -= boundsFix.shrinkW; frame = NSInsetRect(frame, boundsFix.inset, boundsFix.inset); } #endif frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); if (!NSEqualRects(frame, [button frame])) { [button setFrame:frame]; } [button displayRectIgnoringOpacity:[button bounds]]; TkMacOSXRestoreDrawingContext(&dc); #ifdef TK_MAC_DEBUG_MENUBUTTON TKLog(@"menubutton %s frame %@ width %d height %d", ((TkWindow *)mbPtr->tkwin)->pathName, NSStringFromRect(frame), Tk_Width(tkwin), Tk_Height(tkwin)); #endif } /* *---------------------------------------------------------------------- * * TkpComputeMenuButtonGeometry -- * * After changes in a menu button's text or bitmap, this function * recomputes the menu button's geometry and passes this information * along to the geometry manager for the window. * * Results: * None. * * Side effects: * The menu button's window may change size. * *---------------------------------------------------------------------- */ void TkpComputeMenuButtonGeometry( TkMenuButton *mbPtr) /* Widget record for menu button. */ { MacMenuButton *macButtonPtr = (MacMenuButton *) mbPtr; NSPopUpButton *button = macButtonPtr->button; NSPopUpButtonCell *cell; NSMenuItem *menuItem; NSBezelStyle style = NSRoundedBezelStyle; NSFont *font; NSRect bounds = NSZeroRect, titleRect = NSZeroRect; int haveImage = (mbPtr->image || mbPtr->bitmap != None), haveText = 0; int haveCompound = (mbPtr->compound != COMPOUND_NONE); int width, height; if (!button) { button = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:YES]; macButtonPtr->button = TkMacOSXMakeUncollectable(button); cell = [button cell]; [cell setUsesItemFromMenu:NO]; menuItem = [[[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""] autorelease]; [cell setMenuItem:menuItem]; } else { cell = [button cell]; menuItem = [cell menuItem]; } if (haveImage) { style = NSShadowlessSquareBezelStyle; } else if (!mbPtr->indicatorOn) { style = NSRegularSquareBezelStyle; } [button setBezelStyle:style]; [cell setArrowPosition:(mbPtr->indicatorOn ? NSPopUpArrowAtBottom : NSPopUpNoArrow)]; #if 0 NSControlSize controlSize = NSRegularControlSize; if (mbPtr->borderWidth <= 2) { controlSize = NSMiniControlSize; } else if (mbPtr->borderWidth == 3) { controlSize = NSSmallControlSize; } [cell setControlSize:controlSize]; #endif if (mbPtr->text && *(mbPtr->text) && (!haveImage || haveCompound)) { NSString *title = [[NSString alloc] initWithUTF8String:mbPtr->text]; [button setTitle:title]; [title release]; haveText = 1; } haveCompound = (haveCompound && haveImage && haveText); if (haveText) { NSTextAlignment alignment = NSNaturalTextAlignment; switch (mbPtr->justify) { case TK_JUSTIFY_LEFT: alignment = NSLeftTextAlignment; break; case TK_JUSTIFY_RIGHT: alignment = NSRightTextAlignment; break; case TK_JUSTIFY_CENTER: alignment = NSCenterTextAlignment; break; } [button setAlignment:alignment]; } else { [button setTitle:@""]; } font = TkMacOSXNSFontForFont(mbPtr->tkfont); if (font) { [button setFont:font]; } if (haveImage) { int width, height; NSImage *image; NSCellImagePosition pos = NSImageOnly; if (mbPtr->image) { Tk_SizeOfImage(mbPtr->image, &width, &height); image = TkMacOSXGetNSImageWithTkImage(mbPtr->display, mbPtr->image, width, height); } else { Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height); image = TkMacOSXGetNSImageWithBitmap(mbPtr->display, mbPtr->bitmap, mbPtr->normalTextGC, width, height); } if (haveCompound) { switch ((enum compound) mbPtr->compound) { case COMPOUND_TOP: pos = NSImageAbove; break; case COMPOUND_BOTTOM: pos = NSImageBelow; break; case COMPOUND_LEFT: pos = NSImageLeft; break; case COMPOUND_RIGHT: pos = NSImageRight; break; case COMPOUND_CENTER: pos = NSImageOverlaps; break; case COMPOUND_NONE: pos = NSImageOnly; break; } } [button setImagePosition:pos]; [menuItem setImage:image]; bounds.size = cell ? [cell cellSize] : NSZeroSize; if (bounds.size.height < height + 8) { /* workaround AppKit sizing bug */ bounds.size.height = height + 8; } #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS if (!mbPtr->indicatorOn && tkMacOSXUseCompatibilityMetrics) { bounds.size.width -= 16; } #endif } else { bounds.size = cell ? [cell cellSize] : NSZeroSize; } if (haveText) { titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; if (mbPtr->wrapLength > 0 && titleRect.size.width > mbPtr->wrapLength) { if (style == NSRoundedBezelStyle) { [button setBezelStyle:(style = NSRegularSquareBezelStyle)]; bounds.size = cell ? [cell cellSize] : NSZeroSize; titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; } bounds.size.width -= titleRect.size.width - mbPtr->wrapLength; bounds.size.height = 40000.0; [cell setWraps:YES]; bounds.size = cell ? [cell cellSizeForBounds:bounds] : NSZeroSize; #ifdef TK_MAC_DEBUG_MENUBUTTON titleRect = cell ? [cell titleRectForBounds:bounds] : NSZeroRect; #endif #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS if (tkMacOSXUseCompatibilityMetrics) { bounds.size.height += 3; } #endif } } width = lround(bounds.size.width); height = lround(bounds.size.height); #if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS if (tkMacOSXUseCompatibilityMetrics) { macButtonPtr->fix = fixForStyle(style); width -= boundsFixes[macButtonPtr->fix].trimW; height -= boundsFixes[macButtonPtr->fix].trimH; } #endif if (haveImage || haveCompound) { if (mbPtr->width > 0) { width = mbPtr->width; } if (mbPtr->height > 0) { height = mbPtr->height; } } else { if (mbPtr->width > 0) { int avgWidth = Tk_TextWidth(mbPtr->tkfont, "0", 1); width = mbPtr->width * avgWidth; } if (mbPtr->height > 0) { Tk_FontMetrics fm; Tk_GetFontMetrics(mbPtr->tkfont, &fm); height = mbPtr->height * fm.linespace; } } if (!haveImage || haveCompound) { width += 2*mbPtr->padX; height += 2*mbPtr->padY; } if (mbPtr->highlightWidth < 0) { mbPtr->highlightWidth = 0; } if (haveImage) { mbPtr->inset = mbPtr->highlightWidth; width += 2*mbPtr->borderWidth; height += 2*mbPtr->borderWidth; } else { mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth; } Tk_GeometryRequest(mbPtr->tkwin, width + 2 * mbPtr->inset, height + 2 * mbPtr->inset); Tk_SetInternalBorder(mbPtr->tkwin, mbPtr->inset); #ifdef TK_MAC_DEBUG_MENUBUTTON TKLog(@"menubutton %s bounds %@ titleRect %@ width %d height %d inset %d borderWidth %d", ((TkWindow *)mbPtr->tkwin)->pathName, NSStringFromRect(bounds), NSStringFromRect(titleRect), width, height, mbPtr->inset, mbPtr->borderWidth); #endif } /* *-------------------------------------------------------------- * * MenuButtonEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on buttons. * * Results: * None. * * Side effects: * When activation state changes, it is redisplayed. * *-------------------------------------------------------------- */ static void MenuButtonEventProc( ClientData clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { TkMenuButton *mbPtr = (TkMenuButton *) clientData; if (!mbPtr->tkwin || !Tk_IsMapped(mbPtr->tkwin)) { return; } switch (eventPtr->type) { case ActivateNotify: case DeactivateNotify: if (!(mbPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(TkpDisplayMenuButton, (ClientData) mbPtr); mbPtr->flags |= REDRAW_PENDING; } break; } } /* * Local Variables: * mode: objc * c-basic-offset: 4 * fill-column: 79 * coding: utf-8 * End: */