1/*
2 * tkMacOSXMenu.c --
3 *
4 *	This module implements the Mac-platform specific features of menus.
5 *
6 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
7 * Copyright 2001-2009, Apple Inc.
8 * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
9 *
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id$
14 */
15
16#include "tkMacOSXPrivate.h"
17#include "tkMenubutton.h"
18#include "tkMenu.h"
19#include "tkColor.h"
20#include "tkFont.h"
21#include "tkMacOSXWm.h"
22#include "tkMacOSXDebug.h"
23
24/*
25#ifdef TK_MAC_DEBUG
26#define TK_MAC_DEBUG_MENUS
27#endif
28*/
29
30#define ENTRY_HELP_MENU		ENTRY_PLATFORM_FLAG1
31#define ENTRY_APPLE_MENU	ENTRY_PLATFORM_FLAG2
32#define ENTRY_WINDOWS_MENU	ENTRY_PLATFORM_FLAG3
33
34#define sl(s) ((int) (sizeof(s "") - 1))
35#define SPECIALMENU(n, f) {.name = "." #n, .len = sl(#n) + 1, \
36	.flag = ENTRY_##f##_MENU }
37static const struct {
38    const char *name; const size_t len; const int flag;
39} specialMenus[] = {
40    SPECIALMENU(help,	HELP),
41    SPECIALMENU(apple,	APPLE),
42    SPECIALMENU(window,	WINDOWS),
43    {NULL}
44};
45#undef SPECIALMENU
46#define MODIFIER(n, f) {.name = #n, .len = sl(#n), .mask = f }
47static const struct {
48    const char *name; const size_t len; const NSUInteger mask;
49} modifiers[] = {
50    MODIFIER(Control,	NSControlKeyMask),
51    MODIFIER(Ctrl,	NSControlKeyMask),
52    MODIFIER(Option,	NSAlternateKeyMask),
53    MODIFIER(Opt,	NSAlternateKeyMask),
54    MODIFIER(Alt,	NSAlternateKeyMask),
55    MODIFIER(Shift,	NSShiftKeyMask),
56    MODIFIER(Command,	NSCommandKeyMask),
57    MODIFIER(Cmd,	NSCommandKeyMask),
58    MODIFIER(Meta,	NSCommandKeyMask),
59    {NULL}
60};
61#undef MODIFIER
62#define ACCEL(n, c) {.name = #n, .len = sl(#n), .ch = c }
63static const struct {
64    const char *name; const size_t len; const UniChar ch;
65} specialAccelerators[] = {
66    ACCEL(PageUp,	NSPageUpFunctionKey),
67    ACCEL(PageDown,	NSPageDownFunctionKey),
68    ACCEL(Left,		NSLeftArrowFunctionKey),
69    ACCEL(Right,	NSRightArrowFunctionKey),
70    ACCEL(Up,		NSUpArrowFunctionKey),
71    ACCEL(Down,		NSDownArrowFunctionKey),
72    ACCEL(Escape,	0x001b),
73    ACCEL(Clear,	NSClearDisplayFunctionKey),
74    ACCEL(Enter,	NSEnterCharacter),
75    ACCEL(Backspace,	NSBackspaceCharacter),
76    ACCEL(Space,	' '),
77    ACCEL(Tab,		NSTabCharacter),
78    ACCEL(BackTab,	NSBackTabCharacter),
79    ACCEL(Delete,	NSDeleteCharacter),
80    ACCEL(Home,		NSHomeFunctionKey),
81    ACCEL(End,		NSEndFunctionKey),
82    ACCEL(Return,	NSCarriageReturnCharacter),
83    ACCEL(Help,		NSHelpFunctionKey),
84    ACCEL(Power,	0x233d),
85    ACCEL(Eject,	0xf804),
86    {NULL}
87};
88#undef ACCEL
89#undef sl
90
91static int gNoTkMenus = 0;	/* This is used by Tk_MacOSXTurnOffMenus as
92				 * the flag that Tk is not to draw any
93				 * menus. */
94static int inPostMenu = 0;
95static unsigned long defaultBg = 0, defaultFg = 0;
96static SInt32 menuMarkColumnWidth = 0, menuIconTrailingEdgeMargin = 0;
97static SInt32 menuTextLeadingEdgeMargin = 0, menuTextTrailingEdgeMargin = 0;
98static SInt16 menuItemExtraHeight = 0, menuItemExtraWidth = 0;
99static SInt16 menuSeparatorHeight = 0;
100
101static void	CheckForSpecialMenu(TkMenu *menuPtr);
102static NSString *ParseAccelerator(const char *accel, NSUInteger *maskPtr);
103static int	GenerateMenuSelectEvent(TKMenu *menu, NSMenuItem *menuItem);
104static void	MenuSelectEvent(TkMenu *menuPtr);
105static void	RecursivelyClearActiveMenu(TkMenu *menuPtr);
106static int	ModifierCharWidth(Tk_Font tkfont);
107
108#pragma mark TKMenu
109
110@interface TKMenu(TKMenuPrivate)
111- (id)initWithTkMenu:(TkMenu *)tkMenu;
112- (TkMenu *)tkMenu;
113- (int)tkIndexOfItem:(NSMenuItem *)menuItem;
114- (void)insertItem:(NSMenuItem *)newItem atTkIndex:(NSInteger)index;
115@end
116
117#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
118#define TKMenu_NSMenuDelegate <NSMenuDelegate>
119#else
120#define TKMenu_NSMenuDelegate
121#endif
122@interface TKMenu(TKMenuDelegate) TKMenu_NSMenuDelegate
123@end
124
125@implementation TKMenu
126- (void)setSpecial:(NSUInteger)special {
127    NSAssert(!_tkSpecial, @"Cannot change specialness of a special menu");
128    _tkSpecial = special;
129}
130- (BOOL)isSpecial:(NSUInteger)special {
131    return (_tkSpecial == special);
132}
133@end
134
135@implementation TKMenu(TKMenuPrivate)
136- (id)initWithTitle:(NSString *)aTitle {
137    self = [super initWithTitle:aTitle];
138    if (self) {
139	_tkMenu = NULL;
140	_tkOffset = 0;
141	_tkItemCount = 0;
142	_tkSpecial = 0;
143	[self setDelegate:self];
144    }
145    return self;
146}
147- (id)initWithTkMenu:(TkMenu *)tkMenu {
148    NSString *title = [[NSString alloc] initWithUTF8String:
149	    Tk_PathName(tkMenu->tkwin)];
150    self = [self initWithTitle:title];
151    [title release];
152    if (self) {
153	_tkMenu = tkMenu;
154    }
155    return self;
156}
157- (id)copyWithZone:(NSZone *)zone {
158    TKMenu *copy = [super copyWithZone:zone];
159    NSAssert(_tkMenu == nil, @"Cannot copy tkMenu");
160    copy->_tkMenu = _tkMenu;
161    copy->_tkOffset = _tkOffset;
162    copy->_tkItemCount = _tkItemCount;
163    copy->_tkSpecial = _tkSpecial;
164    return copy;
165}
166- (TkMenu *)tkMenu {
167    return _tkMenu;
168}
169- (int)tkIndexOfItem:(NSMenuItem *)menuItem {
170    return [self indexOfItem:menuItem] - _tkOffset;
171}
172- (void)insertItem:(NSMenuItem *)newItem atTkIndex:(NSInteger)index {
173    [super insertItem:newItem atIndex:index + _tkOffset];
174    _tkItemCount++;
175}
176- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index {
177    if (_tkMenu && index >= 0) {
178	if ((NSUInteger)index <= _tkOffset) {
179	    _tkOffset++;
180	} else {
181	    NSAssert((NSUInteger)index >= _tkItemCount + _tkOffset,
182		    @"Cannot insert in the middle of Tk menu");
183	}
184    }
185    [super insertItem:newItem atIndex:index];
186}
187- (void)removeItemAtIndex:(NSInteger)index {
188    if (_tkMenu && index >= 0) {
189	if ((NSUInteger)index < _tkOffset) {
190	    _tkOffset--;
191	} else if ((NSUInteger)index < _tkItemCount + _tkOffset) {
192	    _tkItemCount--;
193	}
194    }
195    [super removeItemAtIndex:index];
196}
197- (NSMenuItem *)newTkMenuItem:(TkMenuEntry *)mePtr {
198    NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@""
199	action:@selector(tkMenuItemInvoke:) keyEquivalent:@""];
200    [menuItem setTarget:self];
201    [menuItem setTag:(NSInteger)mePtr];
202    return menuItem;
203}
204@end
205
206@implementation TKMenu(TKMenuActions)
207// target methods
208- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
209    return [menuItem isEnabled];
210}
211- (void)tkMenuItemInvoke:(id)sender {
212    /*
213     * With the delegate matching key equivalents, when a menu action is sent
214     * in response to a key equivalent, sender is the whole menu and not the
215     * the specific menu item, use this to ignore key equivalents for our menus
216     * (as Tk handles them directly via bindings).
217     */
218    if ([sender isKindOfClass:[NSMenuItem class]]) {
219	NSMenuItem *menuItem = (NSMenuItem *)sender;
220	TkMenu *menuPtr = (TkMenu *)_tkMenu;
221	TkMenuEntry *mePtr = (TkMenuEntry *)[menuItem tag];
222	if (menuPtr && mePtr) {
223	    Tcl_Interp *interp = menuPtr->interp;
224	    Tcl_Preserve(interp);
225	    Tcl_Preserve(menuPtr);
226	    int result = TkInvokeMenu(interp, menuPtr, mePtr->index);
227	    if (result != TCL_OK && result != TCL_CONTINUE &&
228		    result != TCL_BREAK) {
229		Tcl_AddErrorInfo(interp, "\n    (menu invoke)");
230		Tcl_BackgroundError(interp);
231	    }
232	    Tcl_Release(menuPtr);
233	    Tcl_Release(interp);
234	}
235    }
236}
237@end
238
239@implementation TKMenu(TKMenuDelegate)
240#define keyEquivModifiersMatch(km, m) (( \
241    ((km) & NSCommandKeyMask) != ((m) & NSCommandKeyMask) || \
242    ((km) & NSAlternateKeyMask) != ((m) & NSAlternateKeyMask) || \
243    ((km) & NSControlKeyMask) != ((m) & NSControlKeyMask) || \
244    (((km) & NSShiftKeyMask) != ((m) & NSShiftKeyMask) && \
245    ((m) & NSFunctionKeyMask))) ? NO : YES)
246- (BOOL)menuHasKeyEquivalent:(NSMenu*)menu forEvent:(NSEvent*)event
247	target:(id*)target action:(SEL*)action {
248    NSString *key = [event charactersIgnoringModifiers];
249    NSUInteger modifiers = [event modifierFlags] &
250	    NSDeviceIndependentModifierFlagsMask;
251    if (modifiers == (NSCommandKeyMask | NSShiftKeyMask) &&
252	    [key compare:@"?"] == NSOrderedSame) {
253	return NO;
254    }
255    NSArray *itemArray = [self itemArray];
256    for (NSMenuItem *item in itemArray) {
257	if ([item isEnabled] && [[item keyEquivalent] compare:key] ==
258		NSOrderedSame) {
259	    NSUInteger keyEquivModifiers = [item keyEquivalentModifierMask];
260	    if (keyEquivModifiersMatch(keyEquivModifiers, modifiers)) {
261		*target = [item target];
262		*action = [item action];
263		return YES;
264	    }
265	}
266    }
267    return NO;
268}
269- (void)menuWillOpen:(NSMenu *)menu {
270    if (_tkMenu) {
271	//RecursivelyClearActiveMenu(_tkMenu);
272	GenerateMenuSelectEvent((TKMenu *)[self supermenu],
273		[self itemInSupermenu]);
274    }
275}
276- (void)menuDidClose:(NSMenu *)menu {
277    if (_tkMenu) {
278	RecursivelyClearActiveMenu(_tkMenu);
279    }
280}
281- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
282    if (_tkMenu) {
283	GenerateMenuSelectEvent(self, item);
284    }
285}
286- (void)menuNeedsUpdate:(NSMenu*)menu {
287    TkMenu *menuPtr = (TkMenu *)_tkMenu;
288    if (menuPtr) {
289	Tcl_Interp *interp = menuPtr->interp;
290	Tcl_Preserve(interp);
291	Tcl_Preserve(menuPtr);
292	int result = TkPostCommand(_tkMenu);
293	if (result != TCL_OK && result != TCL_CONTINUE && result != TCL_BREAK) {
294	    Tcl_AddErrorInfo(interp, "\n    (menu preprocess)");
295	    Tcl_BackgroundError(interp);
296	}
297	Tcl_Release(menuPtr);
298	Tcl_Release(interp);
299    }
300}
301@end
302
303#pragma mark TKApplication(TKMenu)
304
305@interface NSApplication(TKMenu)
306- (void)setAppleMenu:(NSMenu *)menu;
307@end
308
309@implementation TKApplication(TKMenu)
310- (void)menuBeginTracking:(NSNotification *)notification {
311#ifdef TK_MAC_DEBUG_NOTIFICATIONS
312    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
313#endif
314    //TkMacOSXClearMenubarActive();
315    //TkMacOSXPreprocessMenu();
316}
317- (void)menuEndTracking:(NSNotification *)notification {
318#ifdef TK_MAC_DEBUG_NOTIFICATIONS
319    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
320#endif
321    if (!inPostMenu) {
322	TkMacOSXClearMenubarActive();
323    }
324}
325- (void)tkSetMainMenu:(TKMenu *)menu {
326    if (gNoTkMenus) {
327	return;
328    }
329    TKMenu *applicationMenu = nil;
330    if (menu) {
331	NSMenuItem *applicationMenuItem = [menu numberOfItems] ?
332		[menu itemAtIndex:0] : nil;
333	if (![menu isSpecial:tkMainMenu]) {
334	    TkMenuEntry *mePtr = (TkMenuEntry *)[applicationMenuItem tag];
335	    if (!mePtr || !(mePtr->entryFlags & ENTRY_APPLE_MENU)) {
336		applicationMenuItem = [NSMenuItem itemWithSubmenu:
337			[[_defaultApplicationMenu copy] autorelease]];
338		[menu insertItem:applicationMenuItem atIndex:0];
339	    }
340	    [menu setSpecial:tkMainMenu];
341	}
342	applicationMenu = (TKMenu *)[applicationMenuItem submenu];
343	if (![applicationMenu isSpecial:tkApplicationMenu]) {
344	    for (NSMenuItem *item in _defaultApplicationMenuItems) {
345		[applicationMenu addItem:[[item copy] autorelease]];
346	    }
347	    [applicationMenu setSpecial:tkApplicationMenu];
348	}
349	NSArray *itemArray = [menu itemArray];
350	for (NSMenuItem *item in itemArray) {
351	    TkMenuEntry *mePtr = (TkMenuEntry *)[item tag];
352	    TKMenu *submenu = (TKMenu *)[item submenu];
353	    if (mePtr && submenu) {
354		if ((mePtr->entryFlags & ENTRY_WINDOWS_MENU) &&
355			![submenu isSpecial:tkWindowsMenu]) {
356		    NSInteger index = 0;
357		    for (NSMenuItem *i in _defaultWindowsMenuItems) {
358			[submenu insertItem:[[i copy] autorelease] atIndex:
359				index++];
360		    }
361		    [self setWindowsMenu:submenu];
362		    [submenu setSpecial:tkWindowsMenu];
363		} else if ((mePtr->entryFlags & ENTRY_HELP_MENU) &&
364			![submenu isSpecial:tkHelpMenu]) {
365		    NSInteger index = 0;
366		    for (NSMenuItem *i in _defaultHelpMenuItems) {
367			[submenu insertItem:[[i copy] autorelease] atIndex:
368				index++];
369		    }
370		    [submenu setSpecial:tkHelpMenu];
371		}
372	    }
373	}
374    } else {
375	menu = _defaultMainMenu;
376	applicationMenu = _defaultApplicationMenu;
377    }
378    NSMenuItem *servicesMenuItem = [applicationMenu itemWithTitle:@"Services"];
379    if (servicesMenuItem && [servicesMenuItem submenu] != _servicesMenu) {
380	[[_servicesMenu itemInSupermenu] setSubmenu:nil];
381	[servicesMenuItem setSubmenu:_servicesMenu];
382    }
383    [self setAppleMenu:applicationMenu];
384    [self setMainMenu:menu];
385}
386@end
387
388#pragma mark -
389
390/*
391 *----------------------------------------------------------------------
392 *
393 * TkpNewMenu --
394 *
395 *	Gets a new blank menu. Only the platform specific options are filled
396 *	in.
397 *
398 * Results:
399 *	Returns a standard Tcl error.
400 *
401 * Side effects:
402 *	Allocates a NSMenu and puts it into the platformData field
403 *	of the menuPtr.
404 *
405 *----------------------------------------------------------------------
406 */
407
408int
409TkpNewMenu(
410    TkMenu *menuPtr)		/* The common structure we are making the
411				 * platform structure for. */
412{
413    TKMenu *menu = [[TKMenu alloc] initWithTkMenu:menuPtr];
414    menuPtr->platformData = (TkMenuPlatformData)
415	    TkMacOSXMakeUncollectable(menu);
416    CheckForSpecialMenu(menuPtr);
417    return TCL_OK;
418}
419
420/*
421 *----------------------------------------------------------------------
422 *
423 * TkpDestroyMenu --
424 *
425 *	Destroys platform-specific menu structures.
426 *
427 * Results:
428 *	None.
429 *
430 * Side effects:
431 *	All platform-specific allocations are freed up.
432 *
433 *----------------------------------------------------------------------
434 */
435
436void
437TkpDestroyMenu(
438    TkMenu *menuPtr)		/* The common menu structure */
439{
440    TkMacOSXMakeCollectableAndRelease(menuPtr->platformData);
441}
442
443/*
444 *----------------------------------------------------------------------
445 *
446 * TkpMenuNewEntry --
447 *
448 *	Adds a pointer to a new menu entry structure with the platform-
449 *	specific fields filled in. The Macintosh uses the platformEntryData
450 *	field of the TkMenuEntry record.
451 *
452 * Results:
453 *	Standard TCL error.
454 *
455 * Side effects:
456 *	Storage gets allocated. New menu entry data is put into the
457 *	platformEntryData field of the mePtr.
458 *
459 *----------------------------------------------------------------------
460 */
461
462int
463TkpMenuNewEntry(
464    TkMenuEntry *mePtr)		/* The menu we are adding an entry to */
465{
466    TKMenu *menu = (TKMenu *) mePtr->menuPtr->platformData;
467    NSMenuItem *menuItem;
468    if (mePtr->type == SEPARATOR_ENTRY || mePtr->type == TEAROFF_ENTRY) {
469	menuItem = [[NSMenuItem separatorItem] retain];
470    } else {
471	menuItem = [menu newTkMenuItem:mePtr];
472    }
473    mePtr->platformEntryData = (TkMenuPlatformEntryData)
474	    TkMacOSXMakeUncollectable(menuItem);
475    /* Caller TkMenuEntry() already did this same insertion into the generic
476     * TkMenu so we just match it for the platform menu. */
477    [menu insertItem:menuItem atTkIndex:mePtr->index];
478    return TCL_OK;
479}
480
481/*
482 *----------------------------------------------------------------------
483 *
484 * TkpConfigureMenuEntry --
485 *
486 *	Processes configurations for menu entries.
487 *
488 * Results:
489 *	Returns standard TCL result. If TCL_ERROR is returned, then the
490 *	interp's result contains an error message.
491 *
492 * Side effects:
493 *	Configuration information get set for mePtr; old resources get freed,
494 *	if any need it.
495 *
496 *----------------------------------------------------------------------
497 */
498
499int
500TkpConfigureMenuEntry(
501    TkMenuEntry *mePtr) 	/* Information about menu entry; may or may
502				 * not already have values for some fields. */
503{
504    NSMenuItem *menuItem = (NSMenuItem *) mePtr->platformEntryData;
505    NSString *title = @"";
506    NSAttributedString *attributedTitle = nil;
507    NSImage *image = nil;
508    NSString *keyEquivalent = @"";
509    NSUInteger modifierMask = NSCommandKeyMask;
510    NSMenu *submenu = nil;
511    NSDictionary *attributes;
512    int imageWidth, imageHeight;
513    GC gc = (mePtr->textGC ? mePtr->textGC : mePtr->menuPtr->textGC);
514    Tcl_Obj *fontPtr = (mePtr->fontPtr ? mePtr->fontPtr :
515	    mePtr->menuPtr->fontPtr);
516
517    if (mePtr->image) {
518    	Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
519	image = TkMacOSXGetNSImageWithTkImage(mePtr->menuPtr->display,
520		mePtr->image, imageWidth, imageHeight);
521    } else if (mePtr->bitmapPtr != None) {
522	Pixmap bitmap = Tk_GetBitmapFromObj(mePtr->menuPtr->tkwin,
523		mePtr->bitmapPtr);
524	Tk_SizeOfBitmap(mePtr->menuPtr->display, bitmap, &imageWidth,
525		&imageHeight);
526	image = TkMacOSXGetNSImageWithBitmap(mePtr->menuPtr->display, bitmap,
527		gc, imageWidth, imageHeight);
528    }
529    [menuItem setImage:image];
530    if ((!image || mePtr->compound != COMPOUND_NONE) && mePtr->labelPtr &&
531	    mePtr->labelLength) {
532	title = [[[NSString alloc] initWithBytes:Tcl_GetString(mePtr->labelPtr)
533		length:mePtr->labelLength encoding:NSUTF8StringEncoding]
534		autorelease];
535	if ([title hasSuffix:@"..."]) {
536	    title = [NSString stringWithFormat:@"%@%C",
537		    [title substringToIndex:[title length] - 3], 0x2026];
538	}
539    }
540    [menuItem setTitle:title];
541    if (strcmp(Tcl_GetString(fontPtr), "menu") || gc->foreground != defaultFg
542	    || gc->background != defaultBg) {
543	attributes = TkMacOSXNSFontAttributesForFont(Tk_GetFontFromObj(
544		mePtr->menuPtr->tkwin, fontPtr));
545	if (gc->foreground != defaultFg || gc->background != defaultBg) {
546	    NSColor *color = TkMacOSXGetNSColor(gc, gc->foreground != defaultFg ?
547		    gc->foreground : gc->background);
548	    attributes = [[attributes mutableCopy] autorelease];
549	    [(NSMutableDictionary *)attributes setObject:color
550		    forKey:NSForegroundColorAttributeName];
551	}
552	if (attributes) {
553	    attributedTitle = [[[NSAttributedString alloc]
554		    initWithString:title attributes:attributes] autorelease];
555	}
556    }
557    [menuItem setAttributedTitle:attributedTitle];
558    [menuItem setEnabled:!(mePtr->state == ENTRY_DISABLED)];
559    [menuItem setState:((mePtr->type == CHECK_BUTTON_ENTRY ||
560	    mePtr->type == RADIO_BUTTON_ENTRY) && mePtr->indicatorOn &&
561	    (mePtr->entryFlags & ENTRY_SELECTED) ? NSOnState : NSOffState)];
562    if (mePtr->type != CASCADE_ENTRY && mePtr->accelPtr && mePtr->accelLength) {
563	keyEquivalent = ParseAccelerator(Tcl_GetString(mePtr->accelPtr),
564		&modifierMask);
565    }
566    [menuItem setKeyEquivalent:keyEquivalent];
567    [menuItem setKeyEquivalentModifierMask:modifierMask];
568    if (mePtr->type == CASCADE_ENTRY && mePtr->namePtr) {
569	TkMenuReferences *menuRefPtr;
570
571	menuRefPtr = TkFindMenuReferencesObj(mePtr->menuPtr->interp,
572		mePtr->namePtr);
573	if (menuRefPtr && menuRefPtr->menuPtr) {
574	    CheckForSpecialMenu(menuRefPtr->menuPtr);
575	    submenu = (TKMenu *) menuRefPtr->menuPtr->platformData;
576	    if ([submenu supermenu] && [menuItem submenu] != submenu) {
577		/*
578		* This happens during a clone, where the parent menu is cloned
579		* before its children, so just ignore this temprary setting,
580		* it will be changed shortly (c.f. tkMenu.c CloneMenu())
581		*/
582		submenu = nil;
583	    } else {
584		[submenu setTitle:title];
585	    }
586	}
587    }
588    [menuItem setSubmenu:submenu];
589    return TCL_OK;
590}
591
592/*
593 *----------------------------------------------------------------------
594 *
595 * TkpDestroyMenuEntry --
596 *
597 *	Cleans up platform-specific menu entry items.
598 *
599 * Results:
600 *	None
601 *
602 * Side effects:
603 *	All platform-specific allocations are freed up.
604 *
605 *----------------------------------------------------------------------
606 */
607
608void
609TkpDestroyMenuEntry(
610    TkMenuEntry *mePtr)
611{
612    if (mePtr->platformEntryData && mePtr->menuPtr->platformData) {
613	TKMenu *menu = (TKMenu *) mePtr->menuPtr->platformData;
614	NSMenuItem *menuItem = (NSMenuItem *)mePtr->platformEntryData;
615	NSInteger index = [menu indexOfItem:menuItem];
616	if (index > -1) {
617	    [menu removeItemAtIndex:index];
618	}
619    }
620    TkMacOSXMakeCollectableAndRelease(mePtr->platformEntryData);
621}
622
623/*
624 *----------------------------------------------------------------------
625 *
626 * TkpPostMenu --
627 *
628 *	Posts a menu on the screen
629 *
630 * Results:
631 *	None.
632 *
633 * Side effects:
634 *	The menu is posted and handled.
635 *
636 *----------------------------------------------------------------------
637 */
638
639int
640TkpPostMenu(
641    Tcl_Interp *interp,		/* The interpreter this menu lives in */
642    TkMenu *menuPtr,		/* The menu we are posting */
643    int x,			/* The global x-coordinate of the top, left-
644				 * hand corner of where the menu is supposed
645				 * to be posted. */
646    int y)			/* The global y-coordinate */
647{
648    NSWindow *win = [NSApp keyWindow];
649    if (win) {
650	inPostMenu = 1;
651	int oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE);
652	NSView *view = [win contentView];
653	NSRect frame = NSMakeRect(x + 9, tkMacOSXZeroScreenHeight - y - 9, 1, 1);
654	frame.origin = [view convertPoint:
655		[win convertScreenToBase:frame.origin] fromView:nil];
656	NSMenu *menu = (NSMenu *) menuPtr->platformData;
657	NSPopUpButtonCell *popUpButtonCell = [[NSPopUpButtonCell alloc]
658		initTextCell:@"" pullsDown:NO];
659	[popUpButtonCell setMenu:menu];
660	[popUpButtonCell selectItem:nil];
661	[popUpButtonCell performClickWithFrame:frame inView:view];
662	[popUpButtonCell release];
663	Tcl_SetServiceMode(oldMode);
664	inPostMenu = 0;
665	return TCL_OK;
666    } else {
667	return TCL_ERROR;
668    }
669}
670
671/*
672 *----------------------------------------------------------------------
673 *
674 * TkpSetWindowMenuBar --
675 *
676 *	Associates a given menu with a window.
677 *
678 * Results:
679 *	None.
680 *
681 * Side effects:
682 *	On Windows and UNIX, associates the platform menu with the platform
683 *	window.
684 *
685 *----------------------------------------------------------------------
686 */
687
688void
689TkpSetWindowMenuBar(
690    Tk_Window tkwin,		/* The window we are setting the menu in */
691    TkMenu *menuPtr)		/* The menu we are setting */
692{
693    TkWindow *winPtr = (TkWindow *) tkwin;
694
695    if (winPtr->wmInfoPtr) {
696	winPtr->wmInfoPtr->menuPtr = menuPtr;
697    }
698}
699
700/*
701 *----------------------------------------------------------------------
702 *
703 * TkpSetMainMenubar --
704 *
705 *	Puts the menu associated with a window into the menubar. Should only
706 *	be called when the window is in front.
707 *
708 * Results:
709 *	None.
710 *
711 * Side effects:
712 *	The menubar is changed.
713 *
714 *----------------------------------------------------------------------
715 */
716
717void
718TkpSetMainMenubar(
719    Tcl_Interp *interp,		/* The interpreter of the application */
720    Tk_Window tkwin,		/* The frame we are setting up */
721    char *menuName)		/* The name of the menu to put in front. If
722				 * NULL, use the default menu bar. */
723{
724    static Tcl_Interp *currentInterp = NULL;
725    TKMenu *menu = nil;
726    if (menuName) {
727	TkWindow *winPtr = (TkWindow *) tkwin;
728	if (winPtr->wmInfoPtr && winPtr->wmInfoPtr->menuPtr &&
729		winPtr->wmInfoPtr->menuPtr->masterMenuPtr &&
730		winPtr->wmInfoPtr->menuPtr->masterMenuPtr->tkwin &&
731		!strcmp(menuName, Tk_PathName(
732		winPtr->wmInfoPtr->menuPtr->masterMenuPtr->tkwin))) {
733	    menu = (TKMenu *) winPtr->wmInfoPtr->menuPtr->platformData;
734	} else {
735	    TkMenuReferences *menuRefPtr = TkFindMenuReferences(interp,
736		    menuName);
737	    if (menuRefPtr && menuRefPtr->menuPtr &&
738		    menuRefPtr->menuPtr->platformData) {
739		menu = (TKMenu *) menuRefPtr->menuPtr->platformData;
740	    }
741	}
742    }
743    if (menu || interp != currentInterp) {
744	[NSApp tkSetMainMenu:menu];
745    }
746    currentInterp = interp;
747}
748
749/*
750 *----------------------------------------------------------------------
751 *
752 * CheckForSpecialMenu --
753 *
754 *	Given a menu, check to see whether or not it is a cascade in
755 *	a menubar with one of the special names .apple, .help or .window
756 *	If it is, the entry that points to this menu will be marked.
757 *
758 * Results:
759 *	None.
760 *
761 * Side effects:
762 *	Will set entryFlags appropriately.
763 *
764 *----------------------------------------------------------------------
765 */
766
767static void
768CheckForSpecialMenu(
769    TkMenu *menuPtr)		/* The menu we are checking */
770{
771    if (!menuPtr->masterMenuPtr->tkwin) {
772	return;
773    }
774    for (TkMenuEntry *cascadeEntryPtr = menuPtr->menuRefPtr->parentEntryPtr;
775	    cascadeEntryPtr; cascadeEntryPtr = cascadeEntryPtr->nextCascadePtr) {
776	if (cascadeEntryPtr->menuPtr->menuType == MENUBAR
777		&& cascadeEntryPtr->menuPtr->masterMenuPtr->tkwin) {
778	    TkMenu *masterMenuPtr = cascadeEntryPtr->menuPtr->masterMenuPtr;
779	    int i = 0;
780	    Tcl_DString ds;
781
782	    Tcl_DStringInit(&ds);
783	    Tcl_DStringAppend(&ds, Tk_PathName(masterMenuPtr->tkwin), -1);
784	    while (specialMenus[i].name) {
785		Tcl_DStringAppend(&ds, specialMenus[i].name,
786			specialMenus[i].len);
787		if (strcmp(Tcl_DStringValue(&ds),
788			Tk_PathName(menuPtr->masterMenuPtr->tkwin)) == 0) {
789		    cascadeEntryPtr->entryFlags |= specialMenus[i].flag;
790		} else {
791		    cascadeEntryPtr->entryFlags &= ~specialMenus[i].flag;
792		}
793		Tcl_DStringSetLength(&ds, Tcl_DStringLength(&ds) -
794			specialMenus[i].len);
795		i++;
796	    }
797	    Tcl_DStringFree(&ds);
798	}
799    }
800}
801
802/*
803 *----------------------------------------------------------------------
804 *
805 * ParseAccelerator --
806 *
807 *	Parse accelerator string.
808 *
809 * Results:
810 *	Accelerator string & flags.
811 *
812 * Side effects:
813 *	None.
814 *
815 *----------------------------------------------------------------------
816 */
817
818static NSString*
819ParseAccelerator(
820    const char *accel,
821    NSUInteger *maskPtr)
822{
823    unichar ch = 0;
824    size_t len;
825    int i;
826
827    *maskPtr = 0;
828    while (1) {
829	i = 0;
830	while (modifiers[i].name) {
831	    int l = modifiers[i].len;
832
833	    if (!strncasecmp(accel, modifiers[i].name, l) &&
834		    (accel[l] == '-' || accel[l] == '+')) {
835		*maskPtr |= modifiers[i].mask;
836		accel += l+1;
837		break;
838	    }
839	    i++;
840	}
841	if (!modifiers[i].name || !*accel) {
842	    break;
843	}
844    }
845    len = strlen(accel);
846    if (len > 1) {
847	i = 0;
848	if (accel[0] == 'F' && len < 4 && accel[1] > '0' && accel[1] <= '9') {
849	    int fkey = accel[1] - '0';
850
851	    if (len == 3) {
852		if (accel[2] >= '0' && accel[2] <= '9') {
853		    fkey = 10 * fkey + (accel[2] - '0');
854		} else {
855		    fkey = 0;
856		}
857	    }
858	    if (fkey >= 1 && fkey <= 15) {
859		ch = NSF1FunctionKey + fkey - 1;
860	    }
861	} else while (specialAccelerators[i].name) {
862	    if (accel[0] == specialAccelerators[i].name[0] &&
863		    len == specialAccelerators[i].len && !strncasecmp(accel,
864		    specialAccelerators[i].name, specialAccelerators[i].len)) {
865		ch = specialAccelerators[i].ch;
866		break;
867	    }
868	    i++;
869	}
870    }
871    if (ch) {
872	return [[[NSString alloc] initWithCharacters:&ch length:1] autorelease];
873    } else {
874	return [[[[NSString alloc] initWithUTF8String:accel] autorelease]
875		lowercaseString];
876    }
877}
878
879/*
880 *--------------------------------------------------------------
881 *
882 * ModifierCharWidth --
883 *
884 *	Helper mesuring width of command char in given font.
885 *
886 * Results:
887 *	Width of command char.
888 *
889 * Side effects:
890 *	None.
891 *
892 *--------------------------------------------------------------
893 */
894
895static int
896ModifierCharWidth(Tk_Font tkfont)
897{
898    static NSString *cmdChar = nil;
899
900    if (!cmdChar) {
901	unichar cmd = kCommandUnicode;
902	cmdChar = [[NSString alloc] initWithCharacters:&cmd length:1];
903    }
904    return [cmdChar sizeWithAttributes:
905	    TkMacOSXNSFontAttributesForFont(tkfont)].width;
906}
907
908/*
909 *--------------------------------------------------------------
910 *
911 * TkpComputeStandardMenuGeometry --
912 *
913 *	This procedure is invoked to recompute the size and layout of a menu
914 *	that is not a menubar clone.
915 *
916 * Results:
917 *	None.
918 *
919 * Side effects:
920 *	Fields of menu entries are changed to reflect their current positions,
921 *	and the size of the menu window itself may be changed.
922 *
923 *--------------------------------------------------------------
924 */
925
926void
927TkpComputeStandardMenuGeometry(
928    TkMenu *menuPtr)		/* Structure describing menu. */
929{
930    Tk_Font tkfont, menuFont;
931    Tk_FontMetrics menuMetrics, entryMetrics, *fmPtr;
932    int modifierCharWidth, menuModifierCharWidth;
933    int x, y, modifierWidth, labelWidth, indicatorSpace;
934    int windowWidth, windowHeight, accelWidth;
935    int i, j, lastColumnBreak, maxWidth;
936    int entryWidth, maxIndicatorSpace, borderWidth, activeBorderWidth;
937    TkMenuEntry *mePtr, *columnEntryPtr;
938    int haveAccel = 0;
939
940    if (menuPtr->tkwin == NULL) {
941	return;
942    }
943
944    Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
945	    &borderWidth);
946    Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->activeBorderWidthPtr,
947	    &activeBorderWidth);
948    x = y = borderWidth;
949    windowHeight = maxWidth = lastColumnBreak = 0;
950    maxIndicatorSpace = 0;
951
952    /*
953     * On the Mac especially, getting font metrics can be quite slow, so we
954     * want to do it intelligently. We are going to precalculate them and pass
955     * them down to all of the measuring and drawing routines. We will measure
956     * the font metrics of the menu once. If an entry does not have its own
957     * font set, then we give the geometry/drawing routines the menu's font
958     * and metrics. If an entry has its own font, we will measure that font
959     * and give all of the geometry/drawing the entry's font and metrics.
960     */
961
962    menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
963    Tk_GetFontMetrics(menuFont, &menuMetrics);
964    menuModifierCharWidth = ModifierCharWidth(menuFont);
965
966    for (i = 0; i < menuPtr->numEntries; i++) {
967	mePtr = menuPtr->entries[i];
968	if (mePtr->type == CASCADE_ENTRY || mePtr->accelLength > 0) {
969	    haveAccel = 1;
970	    break;
971	}
972    }
973
974    for (i = 0; i < menuPtr->numEntries; i++) {
975	mePtr = menuPtr->entries[i];
976	if (mePtr->fontPtr == NULL) {
977	    tkfont = menuFont;
978	    fmPtr = &menuMetrics;
979	    modifierCharWidth = menuModifierCharWidth;
980	} else {
981	    tkfont = Tk_GetFontFromObj(menuPtr->tkwin, mePtr->fontPtr);
982	    Tk_GetFontMetrics(tkfont, &entryMetrics);
983	    fmPtr = &entryMetrics;
984	    modifierCharWidth = ModifierCharWidth(tkfont);
985	}
986
987	if ((i > 0) && mePtr->columnBreak) {
988	    if (maxIndicatorSpace != 0) {
989		maxIndicatorSpace += 2;
990	    }
991	    for (j = lastColumnBreak; j < i; j++) {
992		columnEntryPtr = menuPtr->entries[j];
993		columnEntryPtr->indicatorSpace = maxIndicatorSpace;
994		columnEntryPtr->width = maxIndicatorSpace + maxWidth
995			+ 2 * activeBorderWidth;
996		columnEntryPtr->x = x;
997		columnEntryPtr->entryFlags &= ~ENTRY_LAST_COLUMN;
998	    }
999	    x += maxIndicatorSpace + maxWidth + 2 * borderWidth;
1000	    maxWidth = maxIndicatorSpace = 0;
1001	    lastColumnBreak = i;
1002	    y = borderWidth;
1003	}
1004	accelWidth = modifierWidth = indicatorSpace = 0;
1005	if (mePtr->type == SEPARATOR_ENTRY || mePtr->type == TEAROFF_ENTRY) {
1006	    mePtr->height = menuSeparatorHeight;
1007	} else {
1008	    /*
1009	     * For each entry, compute the height required by that particular
1010	     * entry, plus three widths: the width of the label, the width to
1011	     * allow for an indicator to be displayed to the left of the label
1012	     * (if any), and the width of the accelerator to be displayed to
1013	     * the right of the label (if any). These sizes depend, of course,
1014	     * on the type of the entry.
1015	     */
1016
1017	    NSMenuItem *menuItem = (NSMenuItem *) mePtr->platformEntryData;
1018	    int haveImage = 0, width = 0, height = 0;
1019
1020	    if (mePtr->image) {
1021		Tk_SizeOfImage(mePtr->image, &width, &height);
1022		haveImage = 1;
1023	    } else if (mePtr->bitmapPtr) {
1024		Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin,
1025			mePtr->bitmapPtr);
1026		Tk_SizeOfBitmap(menuPtr->display, bitmap, &width, &height);
1027		haveImage = 1;
1028	    }
1029	    if (!haveImage || (mePtr->compound != COMPOUND_NONE)) {
1030		NSAttributedString *attrTitle = [menuItem attributedTitle];
1031		NSSize size;
1032
1033		if (attrTitle) {
1034		    size = [attrTitle size];
1035		} else {
1036		    size = [[menuItem title] sizeWithAttributes:
1037			TkMacOSXNSFontAttributesForFont(tkfont)];
1038		}
1039		size.width += menuTextLeadingEdgeMargin +
1040			menuTextTrailingEdgeMargin;
1041		if (size.height < fmPtr->linespace) {
1042		    size.height = fmPtr->linespace;
1043		}
1044		if (haveImage && (mePtr->compound != COMPOUND_NONE)) {
1045		    int margin = width + menuIconTrailingEdgeMargin;
1046
1047		    if (margin > menuTextLeadingEdgeMargin) {
1048			margin = menuTextLeadingEdgeMargin;
1049		    }
1050		    width += size.width + menuIconTrailingEdgeMargin - margin;
1051		    if (size.height > height) {
1052			height = size.height;
1053		    }
1054		} else {
1055		    width = size.width;
1056		    height = size.height;
1057		}
1058	    }
1059	    labelWidth = width + menuItemExtraWidth;
1060	    mePtr->height = height + menuItemExtraHeight;
1061
1062	    if (mePtr->type == CASCADE_ENTRY) {
1063		modifierWidth = modifierCharWidth;
1064	    } else if (mePtr->accelLength == 0) {
1065		if (haveAccel && !mePtr->hideMargin) {
1066		    modifierWidth = modifierCharWidth;
1067		}
1068	    } else {
1069		NSUInteger modifMask = [menuItem keyEquivalentModifierMask];
1070		int i = 0;
1071
1072		while (modifiers[i].name) {
1073		    if (modifMask & modifiers[i].mask) {
1074			modifMask &= ~modifiers[i].mask;
1075			modifierWidth += modifierCharWidth;
1076		    }
1077		    i++;
1078		}
1079		accelWidth = [[menuItem keyEquivalent] sizeWithAttributes:
1080			TkMacOSXNSFontAttributesForFont(tkfont)].width;
1081	    }
1082	    if (!mePtr->hideMargin) {
1083		indicatorSpace = menuMarkColumnWidth;
1084	    }
1085	    if (indicatorSpace > maxIndicatorSpace) {
1086		maxIndicatorSpace = indicatorSpace;
1087	    }
1088	    entryWidth = labelWidth + modifierWidth + accelWidth;
1089	    if (entryWidth > maxWidth) {
1090		maxWidth = entryWidth;
1091	    }
1092	    mePtr->height += 2 * activeBorderWidth;
1093	}
1094	mePtr->y = y;
1095	y += menuPtr->entries[i]->height + borderWidth;
1096	if (y > windowHeight) {
1097	    windowHeight = y;
1098	}
1099    }
1100
1101    for (j = lastColumnBreak; j < menuPtr->numEntries; j++) {
1102	columnEntryPtr = menuPtr->entries[j];
1103	columnEntryPtr->indicatorSpace = maxIndicatorSpace;
1104	columnEntryPtr->width = maxIndicatorSpace + maxWidth
1105		+ 2 * activeBorderWidth;
1106	columnEntryPtr->x = x;
1107	columnEntryPtr->entryFlags |= ENTRY_LAST_COLUMN;
1108    }
1109    windowWidth = x + maxIndicatorSpace + maxWidth
1110	    + 2 * activeBorderWidth + borderWidth;
1111    windowHeight += borderWidth;
1112
1113    if (windowWidth <= 0) {
1114	windowWidth = 1;
1115    }
1116    if (windowHeight <= 0) {
1117	windowHeight = 1;
1118    }
1119    menuPtr->totalWidth = windowWidth;
1120    menuPtr->totalHeight = windowHeight;
1121}
1122
1123/*
1124 *----------------------------------------------------------------------
1125 *
1126 * GenerateMenuSelectEvent --
1127 *
1128 *	Respond to a menu item being selected.
1129 *
1130 * Results:
1131 *	True if event(s) are generated - false otherwise.
1132 *
1133 * Side effects:
1134 *	Places a virtual event on the event queue.
1135 *
1136 *----------------------------------------------------------------------
1137 */
1138
1139int
1140GenerateMenuSelectEvent(
1141    TKMenu *menu,
1142    NSMenuItem *menuItem)
1143{
1144    TkMenu *menuPtr = [menu tkMenu];
1145
1146    if (menuPtr) {
1147	int index = [menu tkIndexOfItem:menuItem];
1148	if (index < 0 || index >= menuPtr->numEntries ||
1149		(menuPtr->entries[index])->state == ENTRY_DISABLED) {
1150	    TkActivateMenuEntry(menuPtr, -1);
1151	} else {
1152	    TkActivateMenuEntry(menuPtr, index);
1153	    MenuSelectEvent(menuPtr);
1154	    return true;
1155	}
1156    }
1157    return false;
1158}
1159
1160/*
1161 *----------------------------------------------------------------------
1162 *
1163 * MenuSelectEvent --
1164 *
1165 *	Generates a "MenuSelect" virtual event. This can be used to do
1166 *	context-sensitive menu help.
1167 *
1168 * Results:
1169 *	None.
1170 *
1171 * Side effects:
1172 *	Places a virtual event on the event queue.
1173 *
1174 *----------------------------------------------------------------------
1175 */
1176
1177void
1178MenuSelectEvent(
1179    TkMenu *menuPtr)		/* the menu we have selected. */
1180{
1181    XVirtualEvent event;
1182
1183    bzero(&event, sizeof(XVirtualEvent));
1184    event.type = VirtualEvent;
1185    event.serial = LastKnownRequestProcessed(menuPtr->display);
1186    event.send_event = false;
1187    event.display = menuPtr->display;
1188    event.event = Tk_WindowId(menuPtr->tkwin);
1189    event.root = XRootWindow(menuPtr->display, 0);
1190    event.subwindow = None;
1191    event.time = TkpGetMS();
1192    XQueryPointer(NULL, None, NULL, NULL, &event.x_root, &event.y_root, NULL,
1193	    NULL, &event.state);
1194    event.same_screen = true;
1195    event.name = Tk_GetUid("MenuSelect");
1196    Tk_MakeWindowExist(menuPtr->tkwin);
1197    if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) {
1198	Tk_HandleEvent((XEvent *) &event);
1199    } else {
1200	Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL);
1201    }
1202}
1203
1204/*
1205 *----------------------------------------------------------------------
1206 *
1207 * RecursivelyClearActiveMenu --
1208 *
1209 *	Recursively clears the active entry in the menu's cascade hierarchy.
1210 *
1211 * Results:
1212 *	None.
1213 *
1214 * Side effects:
1215 *	Generates <<MenuSelect>> virtual events.
1216 *
1217 *----------------------------------------------------------------------
1218 */
1219
1220void
1221RecursivelyClearActiveMenu(
1222    TkMenu *menuPtr)		/* The menu to reset. */
1223{
1224    int i;
1225    TkMenuEntry *mePtr;
1226
1227    TkActivateMenuEntry(menuPtr, -1);
1228    for (i = 0; i < menuPtr->numEntries; i++) {
1229	mePtr = menuPtr->entries[i];
1230	if (mePtr->type == CASCADE_ENTRY) {
1231	    if ((mePtr->childMenuRefPtr != NULL)
1232		    && (mePtr->childMenuRefPtr->menuPtr != NULL)) {
1233		RecursivelyClearActiveMenu(mePtr->childMenuRefPtr->menuPtr);
1234	    }
1235	}
1236    }
1237}
1238
1239/*
1240 *----------------------------------------------------------------------
1241 *
1242 * TkMacOSXClearMenubarActive --
1243 *
1244 *	Recursively clears the active entry in the current menubar hierarchy.
1245 *
1246 * Results:
1247 *	None.
1248 *
1249 * Side effects:
1250 *	Generates <<MenuSelect>> virtual events.
1251 *
1252 *----------------------------------------------------------------------
1253 */
1254
1255void
1256TkMacOSXClearMenubarActive(void)
1257{
1258    NSMenu *mainMenu = [NSApp mainMenu];
1259    if (mainMenu && [mainMenu isKindOfClass:[TKMenu class]]) {
1260	TkMenu *menuPtr = [(TKMenu *)mainMenu tkMenu];
1261	if (menuPtr && menuPtr->numEntries && menuPtr->entries) {
1262	    RecursivelyClearActiveMenu(menuPtr);
1263	}
1264    }
1265}
1266
1267/*
1268 *----------------------------------------------------------------------
1269 *
1270 * Tk_MacOSXTurnOffMenus --
1271 *
1272 *	Turns off all the menu drawing code. This is more than just disabling
1273 *	the "menu" command, this means that Tk will NEVER touch the menubar.
1274 *	It is needed in the Plugin, where Tk does not own the menubar.
1275 *
1276 * Results:
1277 *	None.
1278 *
1279 * Side effects:
1280 *	A flag is set which will disable all menu drawing.
1281 *
1282 *----------------------------------------------------------------------
1283 */
1284
1285void
1286Tk_MacOSXTurnOffMenus(void)
1287{
1288    gNoTkMenus = 1;
1289}
1290
1291/*
1292 *----------------------------------------------------------------------
1293 *
1294 * TkpMenuInit --
1295 *
1296 *	Initializes Mac-specific menu data.
1297 *
1298 * Results:
1299 *	None.
1300 *
1301 * Side effects:
1302 *	Allocates a hash table.
1303 *
1304 *----------------------------------------------------------------------
1305 */
1306
1307void
1308TkpMenuInit(void)
1309{
1310    TkColor *tkColPtr;
1311
1312    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
1313#define observe(n, s) [nc addObserver:NSApp selector:@selector(s) name:(n) object:nil]
1314    observe(NSMenuDidBeginTrackingNotification, menuBeginTracking:);
1315    observe(NSMenuDidEndTrackingNotification, menuEndTracking:);
1316#undef observe
1317
1318    [NSMenuItem setUsesUserKeyEquivalents:NO];
1319    tkColPtr = TkpGetColor(None, DEF_MENU_BG_COLOR);
1320    defaultBg = tkColPtr->color.pixel;
1321    ckfree((char *) tkColPtr);
1322    tkColPtr = TkpGetColor(None, DEF_MENU_FG);
1323    defaultFg = tkColPtr->color.pixel;
1324    ckfree((char *) tkColPtr);
1325
1326    ChkErr(GetThemeMetric, kThemeMetricMenuMarkColumnWidth,
1327	    &menuMarkColumnWidth);
1328    ChkErr(GetThemeMetric, kThemeMetricMenuTextLeadingEdgeMargin,
1329	    &menuTextLeadingEdgeMargin);
1330    ChkErr(GetThemeMetric, kThemeMetricMenuTextTrailingEdgeMargin,
1331	    &menuTextTrailingEdgeMargin);
1332    ChkErr(GetThemeMetric, kThemeMetricMenuIconTrailingEdgeMargin,
1333	    &menuIconTrailingEdgeMargin);
1334    ChkErr(GetThemeMenuItemExtra, kThemeMenuItemPlain, &menuItemExtraHeight,
1335	    &menuItemExtraWidth);
1336    ChkErr(GetThemeMenuSeparatorHeight, &menuSeparatorHeight);
1337}
1338
1339#pragma mark -
1340#pragma mark NOPs
1341
1342/*
1343 *----------------------------------------------------------------------
1344 *
1345 * TkpMenuThreadInit --
1346 *
1347 *	Does platform-specific initialization of thread-specific menu state.
1348 *
1349 * Results:
1350 *	None.
1351 *
1352 * Side effects:
1353 *	None.
1354 *
1355 *----------------------------------------------------------------------
1356 */
1357
1358void
1359TkpMenuThreadInit(void)
1360{
1361    /*
1362     * Nothing to do.
1363     */
1364}
1365
1366/*
1367 *----------------------------------------------------------------------
1368 *
1369 * TkpMenuNotifyToplevelCreate --
1370 *
1371 *	This routine reconfigures the menu and the clones indicated by
1372 *	menuName becuase a toplevel has been created and any system menus need
1373 *	to be created. Only applicable to Windows.
1374 *
1375 * Results:
1376 *	None.
1377 *
1378 * Side effects:
1379 *	An idle handler is set up to do the reconfiguration.
1380 *
1381 *----------------------------------------------------------------------
1382 */
1383
1384void
1385TkpMenuNotifyToplevelCreate(
1386    Tcl_Interp *interp,		/* The interp the menu lives in. */
1387    char *menuName)		/* The name of the menu to reconfigure. */
1388{
1389    /*
1390     * Nothing to do.
1391     */
1392}
1393
1394/*
1395 *--------------------------------------------------------------
1396 *
1397 * TkpInitializeMenuBindings --
1398 *
1399 *	For every interp, initializes the bindings for Windows menus. Does
1400 *	nothing on Mac or XWindows.
1401 *
1402 * Results:
1403 *	None.
1404 *
1405 * Side effects:
1406 *	C-level bindings are setup for the interp which will handle Alt-key
1407 *	sequences for menus without beeping or interfering with user-defined
1408 *	Alt-key bindings.
1409 *
1410 *--------------------------------------------------------------
1411 */
1412
1413void
1414TkpInitializeMenuBindings(
1415    Tcl_Interp *interp,		/* The interpreter to set. */
1416    Tk_BindingTable bindingTable)
1417				/* The table to add to. */
1418{
1419    /*
1420     * Nothing to do.
1421     */
1422}
1423
1424/*
1425 *--------------------------------------------------------------
1426 *
1427 * TkpComputeMenubarGeometry --
1428 *
1429 *	This procedure is invoked to recompute the size and layout of a menu
1430 *	that is a menubar clone.
1431 *
1432 * Results:
1433 *	None.
1434 *
1435 * Side effects:
1436 *	Fields of menu entries are changed to reflect their current positions,
1437 *	and the size of the menu window itself may be changed.
1438 *
1439 *--------------------------------------------------------------
1440 */
1441
1442void
1443TkpComputeMenubarGeometry(
1444    TkMenu *menuPtr)		/* Structure describing menu. */
1445{
1446    TkpComputeStandardMenuGeometry(menuPtr);
1447}
1448
1449
1450/*
1451 *----------------------------------------------------------------------
1452 *
1453 * TkpDrawMenuEntry --
1454 *
1455 *	Draws the given menu entry at the given coordinates with the given
1456 *	attributes.
1457 *
1458 * Results:
1459 *	None.
1460 *
1461 * Side effects:
1462 *	X Server commands are executed to display the menu entry.
1463 *
1464 *----------------------------------------------------------------------
1465 */
1466
1467void
1468TkpDrawMenuEntry(
1469    TkMenuEntry *mePtr,		/* The entry to draw */
1470    Drawable d,			/* What to draw into */
1471    Tk_Font tkfont,		/* Precalculated font for menu */
1472    const Tk_FontMetrics *menuMetricsPtr,
1473				/* Precalculated metrics for menu */
1474    int x,			/* X-coordinate of topleft of entry */
1475    int y,			/* Y-coordinate of topleft of entry */
1476    int width,			/* Width of the entry rectangle */
1477    int height,			/* Height of the current rectangle */
1478    int strictMotif,		/* Boolean flag */
1479    int drawArrow)		/* Whether or not to draw the cascade
1480				 * arrow for cascade items. Only applies
1481				 * to Windows. */
1482{
1483}
1484
1485#pragma mark Obsolete
1486
1487/*
1488 *----------------------------------------------------------------------
1489 *
1490 * TkMacOSXPreprocessMenu --
1491 *
1492 *    Handle preprocessing of menubar if it exists.
1493 *
1494 * Results:
1495 *    None.
1496 *
1497 * Side effects:
1498 *    All post commands for the current menubar get executed.
1499 *
1500 *----------------------------------------------------------------------
1501 */
1502
1503void
1504TkMacOSXPreprocessMenu(void)
1505{
1506}
1507
1508/*
1509 *----------------------------------------------------------------------
1510 *
1511 * TkMacOSXUseID --
1512 *
1513 *	Take the ID out of the available list for new menus. Used by the
1514 *	default menu bar's menus so that they do not get created at the tk
1515 *	level. See TkMacOSXGetNewMenuID for more information.
1516 *
1517 * Results:
1518 *	Returns TCL_OK if the id was not in use. Returns TCL_ERROR if the id
1519 *	was in use.
1520 *
1521 * Side effects:
1522 *	A hash table entry in the command table is created with a NULL value.
1523 *
1524 *----------------------------------------------------------------------
1525 */
1526
1527int
1528TkMacOSXUseMenuID(
1529    short macID)		/* The id to take out of the table */
1530{
1531    return TCL_OK;
1532}
1533
1534/*
1535 *----------------------------------------------------------------------
1536 *
1537 * TkMacOSXDispatchMenuEvent --
1538 *
1539 *	Given a menu id and an item, dispatches the command associated with
1540 *	it.
1541 *
1542 * Results:
1543 *	None.
1544 *
1545 * Side effects:
1546 *	Commands for the event are scheduled for execution at idle time.
1547 *
1548 *----------------------------------------------------------------------
1549 */
1550
1551int
1552TkMacOSXDispatchMenuEvent(
1553    int menuID,			/* The menu id of the menu we are invoking */
1554    int index)			/* The one-based index of the item that was
1555				 * selected. */
1556{
1557    return TCL_ERROR;
1558}
1559
1560/*
1561 *----------------------------------------------------------------------
1562 *
1563 * TkMacOSXHandleTearoffMenu() --
1564 *
1565 *	This routine sees if the MDEF has set a menu and a mouse position for
1566 *	tearing off and makes a tearoff menu if it has.
1567 *
1568 * Results:
1569 *	menuPtr->interp will have the result of the tearoff command.
1570 *
1571 * Side effects:
1572 *	A new tearoff menu is created if it is supposed to be.
1573 *
1574 *----------------------------------------------------------------------
1575 */
1576
1577void
1578TkMacOSXHandleTearoffMenu(void)
1579{
1580    /*
1581     * Obsolete: Nothing to do.
1582     */
1583}
1584
1585/*
1586 *----------------------------------------------------------------------
1587 *
1588 * TkMacOSXSetHelpMenuItemCount --
1589 *
1590 *	Has to be called after the first call to InsertMenu. Sets up the
1591 *	global variable for the number of items in the unmodified help menu.
1592 *	NB. Nobody uses this any more, since you can get the number of system
1593 *	help items from HMGetHelpMenu trivially. But it is in the stubs
1594 *	table...
1595 *
1596 * Results:
1597 *	None.
1598 *
1599 * Side effects:
1600 *	Nothing.
1601 *
1602 *----------------------------------------------------------------------
1603 */
1604
1605void
1606TkMacOSXSetHelpMenuItemCount(void)
1607{
1608}
1609
1610/*
1611 *----------------------------------------------------------------------
1612 *
1613 * TkMacOSXMenuClick --
1614 *
1615 *	Prepares a menubar for MenuSelect or MenuKey.
1616 *
1617 * Results:
1618 *	None.
1619 *
1620 * Side effects:
1621 *	Any pending configurations of the menubar are completed.
1622 *
1623 *----------------------------------------------------------------------
1624 */
1625
1626void
1627TkMacOSXMenuClick(void)
1628{
1629}
1630
1631/*
1632 * Local Variables:
1633 * mode: c
1634 * c-basic-offset: 4
1635 * fill-column: 79
1636 * coding: utf-8
1637 * End:
1638 */
1639