diff options
Diffstat (limited to 'src/nsmenu.m')
-rw-r--r-- | src/nsmenu.m | 641 |
1 files changed, 283 insertions, 358 deletions
diff --git a/src/nsmenu.m b/src/nsmenu.m index efad978316e..9b56958100a 100644 --- a/src/nsmenu.m +++ b/src/nsmenu.m @@ -1,5 +1,5 @@ /* NeXT/Open/GNUstep and macOS Cocoa menu and toolbar module. - Copyright (C) 2007-2020 Free Software Foundation, Inc. + Copyright (C) 2007-2021 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -47,21 +47,11 @@ Carbon version by Yamamoto Mitsuharu. */ #endif -#if 0 -/* Include lisp -> C common menu parsing code. */ -#define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str) -#include "nsmenu_common.c" -#endif - extern long context_menu_value; EmacsMenu *svcsMenu; /* Nonzero means a menu is currently active. */ static int popup_activated_flag; -/* Nonzero means we are tracking and updating menus. */ -static int trackingMenu; - - /* NOTE: toolbar implementation is at end, following complete menu implementation. */ @@ -75,11 +65,22 @@ static int trackingMenu; /* Supposed to discard menubar and free storage. Since we share the menubar among frames and update its context for the focused window, - there is nothing to do here. */ + we do not discard the menu. We do, however, want to remove any + existing menu items. */ void free_frame_menubar (struct frame *f) { - return; + id menu = [NSApp mainMenu]; + for (int i = [menu numberOfItems] - 1 ; i >= 0; i--) + { + NSMenuItem *item = [menu itemAtIndex:i]; + NSString *title = [item title]; + + if ([ns_app_name isEqualToString:title]) + continue; + + [menu removeItemAtIndex:i]; + } } @@ -98,16 +99,19 @@ popup_activated (void) 3) deep_p, submenu = non-nil: Update contents of a single submenu. -------------------------------------------------------------------------- */ static void -ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) +ns_update_menubar (struct frame *f, bool deep_p) { NSAutoreleasePool *pool; - id menu = [NSApp mainMenu]; - static EmacsMenu *last_submenu = nil; BOOL needsSet = NO; + id menu = [NSApp mainMenu]; bool owfi; + Lisp_Object items; widget_value *wv, *first_wv, *prev_wv = 0; int i; + int *submenu_start, *submenu_end; + bool *submenu_top_level_items; + int *submenu_n_panes; #if NSMENUPROFILE struct timeb tb; @@ -116,7 +120,7 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) NSTRACE ("ns_update_menubar"); - if (f != SELECTED_FRAME ()) + if (f != SELECTED_FRAME () || FRAME_EXTERNAL_MENU_BAR (f) == 0) return; XSETFRAME (Vmenu_updating_frame, f); /*fprintf (stderr, "ns_update_menubar: frame: %p\tdeep: %d\tsub: %p\n", f, deep_p, submenu); */ @@ -141,115 +145,102 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) t = -(1000*tb.time+tb.millitm); #endif -#ifdef NS_IMPL_GNUSTEP - deep_p = 1; /* until GNUstep NSMenu implements the Panther delegation model */ -#endif - if (deep_p) { - /* Fully parse one or more of the submenus. */ - int n = 0; - int *submenu_start, *submenu_end; - bool *submenu_top_level_items; - int *submenu_n_panes; + /* Make a widget-value tree representing the entire menu trees. */ + struct buffer *prev = current_buffer; Lisp_Object buffer; ptrdiff_t specpdl_count = SPECPDL_INDEX (); int previous_menu_items_used = f->menu_bar_items_used; Lisp_Object *previous_items = alloca (previous_menu_items_used * sizeof *previous_items); + int subitems; - /* lisp preliminaries */ buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents; specbind (Qinhibit_quit, Qt); + /* Don't let the debugger step into this code + because it is not reentrant. */ specbind (Qdebug_on_next_call, Qnil); + record_unwind_save_match_data (); if (NILP (Voverriding_local_map_menu_flag)) { specbind (Qoverriding_terminal_local_map, Qnil); specbind (Qoverriding_local_map, Qnil); } + set_buffer_internal_1 (XBUFFER (buffer)); - /* TODO: for some reason this is not needed in other terms, - but some menu updates call Info-extract-pointer which causes - abort-on-error if waiting-for-input. Needs further investigation. */ + /* TODO: for some reason this is not needed in other terms, but + some menu updates call Info-extract-pointer which causes + abort-on-error if waiting-for-input. Needs further + investigation. */ owfi = waiting_for_input; waiting_for_input = 0; - /* lucid hook and possible reset */ + /* Run the Lucid hook. */ safe_run_hooks (Qactivate_menubar_hook); + + /* If it has changed current-menubar from previous value, + really recompute the menubar from the value. */ if (! NILP (Vlucid_menu_bar_dirty_flag)) call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); - /* Now ready to go */ items = FRAME_MENU_BAR_ITEMS (f); - /* Save the frame's previous menu bar contents data */ + /* Save the frame's previous menu bar contents data. */ if (previous_menu_items_used) - memcpy (previous_items, aref_addr (f->menu_bar_vector, 0), - previous_menu_items_used * sizeof (Lisp_Object)); + memcpy (previous_items, xvector_contents (f->menu_bar_vector), + previous_menu_items_used * word_size); - /* parse stage 1: extract from lisp */ + /* Fill in menu_items with the current menu bar contents. + This can evaluate Lisp code. */ save_menu_items (); menu_items = f->menu_bar_vector; menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0; - submenu_start = alloca (ASIZE (items) * sizeof *submenu_start); - submenu_end = alloca (ASIZE (items) * sizeof *submenu_end); - submenu_n_panes = alloca (ASIZE (items) * sizeof *submenu_n_panes); - submenu_top_level_items = alloca (ASIZE (items) + subitems = ASIZE (items) / 4; + submenu_start = alloca ((subitems + 1) * sizeof *submenu_start); + submenu_end = alloca (subitems * sizeof *submenu_end); + submenu_n_panes = alloca (subitems * sizeof *submenu_n_panes); + submenu_top_level_items = alloca (subitems * sizeof *submenu_top_level_items); init_menu_items (); - for (i = 0; i < ASIZE (items); i += 4) + for (i = 0; i < subitems; i++) { Lisp_Object key, string, maps; - key = AREF (items, i); - string = AREF (items, i + 1); - maps = AREF (items, i + 2); + key = AREF (items, 4 * i); + string = AREF (items, 4 * i + 1); + maps = AREF (items, 4 * i + 2); if (NILP (string)) break; - /* FIXME: we'd like to only parse the needed submenu, but this - was causing crashes in the _common parsing code: need to make - sure proper initialization done. */ - /* if (submenu && strcmp ([[submenu title] UTF8String], SSDATA (string))) - continue; */ - submenu_start[i] = menu_items_used; menu_items_n_panes = 0; - submenu_top_level_items[i] = parse_single_submenu (key, string, maps); + submenu_top_level_items[i] + = parse_single_submenu (key, string, maps); submenu_n_panes[i] = menu_items_n_panes; + submenu_end[i] = menu_items_used; - n++; } + submenu_start[i] = -1; finish_menu_items (); waiting_for_input = owfi; + /* Convert menu_items into widget_value trees + to display the menu. This cannot evaluate Lisp code. */ - if (submenu && n == 0) - { - /* should have found a menu for this one but didn't */ - fprintf (stderr, "ERROR: did not find lisp menu for submenu '%s'.\n", - [[submenu title] UTF8String]); - discard_menu_items (); - unbind_to (specpdl_count, Qnil); - unblock_input (); - return; - } - - /* parse stage 2: insert into lucid 'widget_value' structures - [comments in other terms say not to evaluate lisp code here] */ wv = make_widget_value ("menubar", NULL, true, Qnil); wv->button_type = BUTTON_TYPE_NONE; first_wv = wv; - for (i = 0; i < 4*n; i += 4) + for (i = 0; submenu_start[i] >= 0; i++) { menu_items_n_panes = submenu_n_panes[i]; wv = digest_single_submenu (submenu_start[i], submenu_end[i], @@ -259,169 +250,79 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) else first_wv->contents = wv; /* Don't set wv->name here; GC during the loop might relocate it. */ - wv->enabled = 1; + wv->enabled = true; wv->button_type = BUTTON_TYPE_NONE; prev_wv = wv; } set_buffer_internal_1 (prev); - /* Compare the new menu items with previous, and leave off if no change. */ - /* FIXME: following other terms here, but seems like this should be - done before parse stage 2 above, since its results aren't used. */ - if (previous_menu_items_used - && (!submenu || (submenu && submenu == last_submenu)) - && menu_items_used == previous_menu_items_used) - { - for (i = 0; i < previous_menu_items_used; i++) - /* FIXME: this ALWAYS fails on Buffers menu items.. something - about their strings causes them to change every time, so we - double-check failures. */ - if (!EQ (previous_items[i], AREF (menu_items, i))) - if (!(STRINGP (previous_items[i]) - && STRINGP (AREF (menu_items, i)) - && !strcmp (SSDATA (previous_items[i]), - SSDATA (AREF (menu_items, i))))) - break; - if (i == previous_menu_items_used) - { - /* No change. */ + /* If there has been no change in the Lisp-level contents + of the menu bar, skip redisplaying it. Just exit. */ -#if NSMENUPROFILE - ftime (&tb); - t += 1000*tb.time+tb.millitm; - fprintf (stderr, "NO CHANGE! CUTTING OUT after %ld msec.\n", t); -#endif + /* Compare the new menu items with the ones computed last time. */ + for (i = 0; i < previous_menu_items_used; i++) + if (menu_items_used == i + || (!EQ (previous_items[i], AREF (menu_items, i)))) + break; + if (i == menu_items_used && i == previous_menu_items_used && i != 0) + { + /* The menu items have not changed. Don't bother updating + the menus in any form, since it would be a no-op. */ + free_menubar_widget_value_tree (first_wv); + discard_menu_items (); + unbind_to (specpdl_count, Qnil); + return; + } - free_menubar_widget_value_tree (first_wv); - discard_menu_items (); - unbind_to (specpdl_count, Qnil); - unblock_input (); - return; - } - } /* The menu items are different, so store them in the frame. */ - /* FIXME: this is not correct for single-submenu case. */ fset_menu_bar_vector (f, menu_items); f->menu_bar_items_used = menu_items_used; - /* Calls restore_menu_items, etc., as they were outside. */ + /* This undoes save_menu_items. */ unbind_to (specpdl_count, Qnil); - /* Parse stage 2a: now GC cannot happen during the lifetime of the - widget_value, so it's safe to store data from a Lisp_String. */ + /* Now GC cannot happen during the lifetime of the widget_value, + so it's safe to store data from a Lisp_String. */ wv = first_wv->contents; for (i = 0; i < ASIZE (items); i += 4) { Lisp_Object string; string = AREF (items, i + 1); if (NILP (string)) - break; - - wv->name = SSDATA (string); + break; + wv->name = SSDATA (string); update_submenu_strings (wv->contents); - wv = wv->next; + wv = wv->next; } - /* Now, update the NS menu; if we have a submenu, use that, otherwise - create a new menu for each sub and fill it. */ - if (submenu) - { - const char *submenuTitle = [[submenu title] UTF8String]; - for (wv = first_wv->contents; wv; wv = wv->next) - { - if (!strcmp (submenuTitle, wv->name)) - { - [submenu fillWithWidgetValue: wv->contents]; - last_submenu = submenu; - break; - } - } - } - else - { - [menu fillWithWidgetValue: first_wv->contents frame: f]; - } - } else { - static int n_previous_strings = 0; - static char previous_strings[100][10]; - static struct frame *last_f = NULL; - int n; - Lisp_Object string; + /* Make a widget-value tree containing + just the top level menu bar strings. */ wv = make_widget_value ("menubar", NULL, true, Qnil); wv->button_type = BUTTON_TYPE_NONE; first_wv = wv; - /* Make widget-value tree with just the top level menu bar strings. */ items = FRAME_MENU_BAR_ITEMS (f); - if (NILP (items)) - { - free_menubar_widget_value_tree (first_wv); - unblock_input (); - return; - } - - - /* Check if no change: this mechanism is a bit rough, but ready. */ - n = ASIZE (items) / 4; - if (f == last_f && n_previous_strings == n) - { - for (i = 0; i<n; i++) - { - string = AREF (items, 4*i+1); - - if (EQ (string, make_fixnum (0))) // FIXME: Why??? --Stef - continue; - if (NILP (string)) - { - if (previous_strings[i][0]) - break; - else - continue; - } - else if (memcmp (previous_strings[i], SDATA (string), - min (10, SBYTES (string) + 1))) - break; - } - - if (i == n) - { - free_menubar_widget_value_tree (first_wv); - unblock_input (); - return; - } - } - - [menu clear]; for (i = 0; i < ASIZE (items); i += 4) { + Lisp_Object string; + string = AREF (items, i + 1); if (NILP (string)) break; - if (n < 100) - memcpy (previous_strings[i/4], SDATA (string), - min (10, SBYTES (string) + 1)); - wv = make_widget_value (SSDATA (string), NULL, true, Qnil); wv->button_type = BUTTON_TYPE_NONE; + /* This prevents lwlib from assuming this + menu item is really supposed to be empty. */ + /* The intptr_t cast avoids a warning. + This value just has to be different from small integers. */ wv->call_data = (void *) (intptr_t) (-1); -#ifdef NS_IMPL_COCOA - /* We'll update the real copy under app menu when time comes. */ - if (!strcmp ("Services", wv->name)) - { - /* But we need to make sure it will update on demand. */ - [svcsMenu setFrame: f]; - } - else -#endif - [menu addSubmenuWithTitle: wv->name forFrame: f]; - if (prev_wv) prev_wv->next = wv; else @@ -429,15 +330,58 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) prev_wv = wv; } - last_f = f; - if (n < 100) - n_previous_strings = n; + /* Forget what we thought we knew about what is in the + detailed contents of the menu bar menus. + Changing the top level always destroys the contents. */ + f->menu_bar_items_used = 0; + } + + /* Now, update the NS menu. */ + i = 0; + + /* Make sure we skip the "application" menu, which is always the + first entry in our top-level menu. */ + if (i < [menu numberOfItems]) + { + NSString *title = [[menu itemAtIndex:i] title]; + if ([ns_app_name isEqualToString:title]) + i += 1; + } + + for (wv = first_wv->contents; wv; wv = wv->next) + { + EmacsMenu *submenu; + + if (i < [menu numberOfItems]) + { + NSString *titleStr = [NSString stringWithUTF8String: wv->name]; + NSMenuItem *item = [menu itemAtIndex:i]; + submenu = (EmacsMenu*)[item submenu]; + + [item setTitle:titleStr]; + [submenu setTitle:titleStr]; + [submenu removeAllItems]; + } else - n_previous_strings = 0; + submenu = [menu addSubmenuWithTitle: wv->name]; + + if ([[submenu title] isEqualToString:@"Help"]) + [NSApp setHelpMenu:submenu]; + + if (deep_p) + [submenu fillWithWidgetValue: wv->contents]; + i += 1; } - free_menubar_widget_value_tree (first_wv); + while (i < [menu numberOfItems]) + { + /* Remove any extra items. */ + [menu removeItemAtIndex:i]; + } + + + free_menubar_widget_value_tree (first_wv); #if NSMENUPROFILE ftime (&tb); @@ -460,21 +404,10 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) void set_frame_menubar (struct frame *f, bool first_time, bool deep_p) { - ns_update_menubar (f, deep_p, nil); -} - -void -ns_activate_menubar (struct frame *f) -{ -#ifdef NS_IMPL_COCOA - ns_update_menubar (f, true, nil); - ns_check_pending_open_menu (); -#endif + ns_update_menubar (f, deep_p); } - - /* ========================================================================== Menu: class implementation @@ -490,97 +423,31 @@ ns_activate_menubar (struct frame *f) /* override designated initializer */ - (instancetype)initWithTitle: (NSString *)title { - frame = 0; if ((self = [super initWithTitle: title])) [self setAutoenablesItems: NO]; - return self; -} - - -/* used for top-level */ -- (instancetype)initWithTitle: (NSString *)title frame: (struct frame *)f -{ - [self initWithTitle: title]; - frame = f; -#ifdef NS_IMPL_COCOA [self setDelegate: self]; -#endif - return self; -} - - -- (void)setFrame: (struct frame *)f -{ - frame = f; -} - -#ifdef NS_IMPL_COCOA --(void)trackingNotification:(NSNotification *)notification -{ - /* Update menu in menuNeedsUpdate only while tracking menus. */ - trackingMenu = ([notification name] == NSMenuDidBeginTrackingNotification - ? 1 : 0); - if (! trackingMenu) ns_check_menu_open (nil); -} - -- (void)menuWillOpen:(NSMenu *)menu -{ - ++trackingMenu; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 - // On 10.6 we get repeated calls, only the one for NSSystemDefined is "real". - if ( -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - NSAppKitVersionNumber < NSAppKitVersionNumber10_7 && -#endif - [[NSApp currentEvent] type] != NSEventTypeSystemDefined) - return; -#endif + needsUpdate = YES; - /* When dragging from one menu to another, we get willOpen followed by didClose, - i.e. trackingMenu == 3 in willOpen and then 2 after didClose. - We have updated all menus, so avoid doing it when trackingMenu == 3. */ - if (trackingMenu == 2) - ns_check_menu_open (menu); -} - -- (void)menuDidClose:(NSMenu *)menu -{ - --trackingMenu; + return self; } -#endif /* NS_IMPL_COCOA */ /* Delegate method called when a submenu is being opened: run a 'deep' call to set_frame_menubar. */ + +/* TODO: GNUstep calls this method when the menu is still being built + which throws it into an infinite loop. One possible solution is to + use menuWillOpen instead, but the Apple docs explicitly warn + against changing the contents of the menu in it. I don't know what + the right thing to do for GNUstep is. */ - (void)menuNeedsUpdate: (NSMenu *)menu { - if (!FRAME_LIVE_P (frame)) + if (!FRAME_LIVE_P (SELECTED_FRAME ())) return; - /* Cocoa/Carbon will request update on every keystroke - via IsMenuKeyEvent -> CheckMenusForKeyEvent. These are not needed - since key equivalents are handled through emacs. - On Leopard, even keystroke events generate SystemDefined event. - Third-party applications that enhance mouse / trackpad - interaction, or also VNC/Remote Desktop will send events - of type AppDefined rather than SysDefined. - Menus will fail to show up if they haven't been initialized. - AppDefined events may lack timing data. - - Thus, we rely on the didBeginTrackingNotification notification - as above to indicate the need for updates. - From 10.6 on, we could also use -[NSMenu propertiesToUpdate]: In the - key press case, NSMenuPropertyItemImage (e.g.) won't be set. - */ - if (trackingMenu == 0) - return; -/*fprintf (stderr, "Updating menu '%s'\n", [[self title] UTF8String]); NSLog (@"%@\n", event); */ -#ifdef NS_IMPL_GNUSTEP - /* Don't know how to do this for anything other than Mac OS X 10.5 and later. - This is wrong, as it might run Lisp code in the event loop. */ - ns_update_menubar (frame, true, self); -#endif + if (needsUpdate) + ns_update_menubar (SELECTED_FRAME (), true); } @@ -593,33 +460,8 @@ ns_activate_menubar (struct frame *f) } -/* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>') - into an accelerator string. We are only able to display a single character - for an accelerator, together with an optional modifier combination. (Under - Carbon more control was possible, but in Cocoa multi-char strings passed to - NSMenuItem get ignored. For now we try to display a super-single letter - combo, and return the others as strings to be appended to the item title. - (This is signaled by setting keyEquivModMask to 0 for now.) */ --(NSString *)parseKeyEquiv: (const char *)key -{ - const char *tpos = key; - keyEquivModMask = NSEventModifierFlagCommand; - - if (!key || !*key) - return @""; - - while (*tpos == ' ' || *tpos == '(') - tpos++; - if ((*tpos == 's') && (*(tpos+1) == '-')) - { - return [NSString stringWithFormat: @"%c", tpos[2]]; - } - keyEquivModMask = 0; /* signal */ - return [NSString stringWithUTF8String: tpos]; -} - - - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr + attributes: (NSDictionary *)attributes { NSMenuItem *item; widget_value *wv = (widget_value *)wvptr; @@ -627,36 +469,33 @@ ns_activate_menubar (struct frame *f) if (menu_separator_name_p (wv->name)) { item = [NSMenuItem separatorItem]; - [self addItem: item]; } else { - NSString *title, *keyEq; - title = [NSString stringWithUTF8String: wv->name]; + NSString *title = [NSString stringWithUTF8String: wv->name]; if (title == nil) title = @"< ? >"; /* (get out in the open so we know about it) */ - keyEq = [self parseKeyEquiv: wv->key]; -#ifdef NS_IMPL_COCOA - /* macOS mangles modifier strings longer than one character. */ - if (keyEquivModMask == 0) - { - title = [title stringByAppendingFormat: @" (%@)", keyEq]; - item = [self addItemWithTitle: (NSString *)title - action: @selector (menuDown:) - keyEquivalent: @""]; - } - else + item = [[[NSMenuItem alloc] init] autorelease]; + if (wv->key) { -#endif - item = [self addItemWithTitle: (NSString *)title - action: @selector (menuDown:) - keyEquivalent: keyEq]; + NSString *key = [NSString stringWithUTF8String: wv->key]; #ifdef NS_IMPL_COCOA - } + /* Cocoa only permits a single key (with modifiers) as + keyEquivalent, so we put them in the title string + in a tab-separated column. */ + title = [title stringByAppendingFormat: @"\t%@", key]; +#else + [item setKeyEquivalent: key]; #endif - [item setKeyEquivalentModifierMask: keyEquivModMask]; + } + NSAttributedString *atitle = [[[NSAttributedString alloc] + initWithString: title + attributes: attributes] + autorelease]; + [item setAction: @selector (menuDown:)]; + [item setAttributedTitle: atitle]; [item setEnabled: wv->enabled]; /* Draw radio buttons and tickboxes. */ @@ -669,52 +508,145 @@ ns_activate_menubar (struct frame *f) [item setTag: (NSInteger)wv->call_data]; } + [self addItem: item]; return item; } /* convenience */ --(void)clear +-(void)removeAllItems { +#ifdef NS_IMPL_COCOA + [super removeAllItems]; +#else + /* GNUstep doesn't have removeAllItems yet, so do it + manually. */ int n; for (n = [self numberOfItems]-1; n >= 0; n--) - { - NSMenuItem *item = [self itemAtIndex: n]; - NSString *title = [item title]; - if ([ns_app_name isEqualToString: title] - && ![item isSeparatorItem]) - continue; - [self removeItemAtIndex: n]; - } + [self removeItemAtIndex: n]; +#endif + + needsUpdate = YES; } -- (void)fillWithWidgetValue: (void *)wvptr +typedef struct { + const char *from, *to; +} subst_t; + +/* Standard keyboard symbols used in menus. */ +static const subst_t key_symbols[] = { + {"<backspace>", "⌫"}, + {"DEL", "⌫"}, + {"<deletechar>", "⌦"}, + {"<return>", "↩"}, + {"RET", "↩"}, + {"<left>", "←"}, + {"<right>", "→"}, + {"<up>", "↑"}, + {"<down>", "↓"}, + {"<prior>", "⇞"}, + {"<next>", "⇟"}, + {"<home>", "↖"}, + {"<end>", "↘"}, + {"<tab>", "⇥"}, + {"TAB", "⇥"}, + {"<backtab>", "⇤"}, +}; + +/* Transform the key sequence KEY into something prettier by + substituting keyboard symbols. */ +static char * +prettify_key (const char *key) { - [self fillWithWidgetValue: wvptr frame: (struct frame *)nil]; + while (*key == ' ') key++; + + int len = strlen (key); + char *buf = xmalloc (len + 1); + memcpy (buf, key, len + 1); + for (int i = 0; i < ARRAYELTS (key_symbols); i++) + { + ptrdiff_t fromlen = strlen (key_symbols[i].from); + char *p = buf; + while (p < buf + len) + { + char *match = memmem (buf, len, key_symbols[i].from, fromlen); + if (!match) + break; + ptrdiff_t tolen = strlen (key_symbols[i].to); + eassert (tolen <= fromlen); + memcpy (match, key_symbols[i].to, tolen); + memmove (match + tolen, match + fromlen, + len - (match + fromlen - buf) + 1); + len -= fromlen - tolen; + p = match + tolen; + } + } + Lisp_Object result = build_string (buf); + xfree (buf); + return SSDATA (result); } -- (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f +- (void)fillWithWidgetValue: (void *)wvptr { - widget_value *wv = (widget_value *)wvptr; + widget_value *first_wv = (widget_value *)wvptr; + NSFont *menuFont = [NSFont menuFontOfSize:0]; + NSDictionary *attributes = nil; + +#ifdef NS_IMPL_COCOA + /* Cocoa doesn't allow multi-key sequences in its menu display, so + work around it by using tabs to split the title into two + columns. */ + NSDictionary *font_attribs = @{NSFontAttributeName: menuFont}; + CGFloat maxNameWidth = 0; + CGFloat maxKeyWidth = 0; + + /* Determine the maximum width of all menu items. */ + for (widget_value *wv = first_wv; wv != NULL; wv = wv->next) + if (!menu_separator_name_p (wv->name)) + { + NSString *name = [NSString stringWithUTF8String: wv->name]; + NSSize nameSize = [name sizeWithAttributes: font_attribs]; + maxNameWidth = MAX(maxNameWidth, nameSize.width); + if (wv->key) + { + wv->key = prettify_key (wv->key); + NSString *key = [NSString stringWithUTF8String: wv->key]; + NSSize keySize = [key sizeWithAttributes: font_attribs]; + maxKeyWidth = MAX(maxKeyWidth, keySize.width); + } + } + + /* Put some space between the names and keys. */ + CGFloat maxWidth = maxNameWidth + maxKeyWidth + 40; + + /* Set a right-aligned tab stop at the maximum width, so that the + key will appear immediately to the left of it. */ + NSTextTab *tab = + [[[NSTextTab alloc] initWithTextAlignment: NSTextAlignmentRight + location: maxWidth + options: @{}] autorelease]; + NSMutableParagraphStyle *pstyle = [[[NSMutableParagraphStyle alloc] init] + autorelease]; + [pstyle setTabStops: @[tab]]; + attributes = @{NSParagraphStyleAttributeName: pstyle}; +#endif /* clear existing contents */ - [self clear]; + [self removeAllItems]; /* add new contents */ - for (; wv != NULL; wv = wv->next) + for (widget_value *wv = first_wv; wv != NULL; wv = wv->next) { - NSMenuItem *item = [self addItemWithWidgetValue: wv]; + NSMenuItem *item = [self addItemWithWidgetValue: wv + attributes: attributes]; if (wv->contents) { EmacsMenu *submenu; - if (f) - submenu = [[EmacsMenu alloc] initWithTitle: [item title] frame:f]; - else - submenu = [[EmacsMenu alloc] initWithTitle: [item title]]; + submenu = [[EmacsMenu alloc] initWithTitle: [item title]]; [self setSubmenu: submenu forItem: item]; [submenu fillWithWidgetValue: wv->contents]; @@ -723,6 +655,8 @@ ns_activate_menubar (struct frame *f) } } + needsUpdate = NO; + #ifdef NS_IMPL_GNUSTEP if ([[self window] isVisible]) [self sizeToFit]; @@ -731,13 +665,13 @@ ns_activate_menubar (struct frame *f) /* Adds an empty submenu and returns it. */ -- (EmacsMenu *)addSubmenuWithTitle: (const char *)title forFrame: (struct frame *)f +- (EmacsMenu *)addSubmenuWithTitle: (const char *)title { NSString *titleStr = [NSString stringWithUTF8String: title]; NSMenuItem *item = [self addItemWithTitle: titleStr action: (SEL)nil /*@selector (menuDown:) */ keyEquivalent: @""]; - EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr frame: f]; + EmacsMenu *submenu = [[EmacsMenu alloc] initWithTitle: titleStr]; [self setSubmenu: submenu forItem: item]; [submenu release]; return submenu; @@ -1022,15 +956,12 @@ update_frame_tool_bar (struct frame *f) int i, k = 0; EmacsView *view = FRAME_NS_VIEW (f); EmacsToolbar *toolbar = [view toolbar]; - int oldh; NSTRACE ("update_frame_tool_bar"); if (view == nil || toolbar == nil) return; block_input (); - oldh = FRAME_TOOLBAR_HEIGHT (f); - #ifdef NS_IMPL_COCOA [toolbar clearActive]; #else @@ -1881,12 +1812,6 @@ DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_ void syms_of_nsmenu (void) { -#ifndef NS_IMPL_COCOA - /* Don't know how to keep track of this in Next/Open/GNUstep. Always - update menus there. */ - trackingMenu = 1; - PDUMPER_REMEMBER_SCALAR (trackingMenu); -#endif defsubr (&Sns_reset_menu); defsubr (&Smenu_or_popup_active_p); |