diff options
author | Alan Third <alan@idiocy.org> | 2020-12-23 20:12:02 +0000 |
---|---|---|
committer | Alan Third <alan@idiocy.org> | 2020-12-27 16:51:47 +0000 |
commit | c9b37634b131f3617314bd5a38090e96d0b465cf (patch) | |
tree | 564c27528be75eb9fb3e5d4097020c440f2c25ac /src/nsmenu.m | |
parent | df882c9701755e2ae063f05d3381de14ae09951e (diff) | |
download | emacs-c9b37634b131f3617314bd5a38090e96d0b465cf.tar.gz |
Remove NS menu synthesized events (bug#44333)
Remove the frame tracking stuff as it's not used for anything, and
move the update tracking into the EmacsMenu class.
* src/nsmenu.m (ns_update_menubar): Copy the parsing code from xmenu.c
and rework the NS specific code around to update the existing menus
instead of rebuilding them completely.
(ns_activate_menubar):
([EmacsMenu trackingNotification:]):
([EmacsMenu menuWillOpen:]):
([EmacsMenu menuDidClose:]): Remove unused functions.
([EmacsMenu menuNeedsUpdate:]): Remove menu tracking code and add code
to check whether an update is required.
([EmacsMenu fillWithWidgetValue:]):
([EmacsMenu addSubmenuWithTitle:]):
([EmacsMenu initWithTitle:]): Remove references to frame.
([EmacsMenu setFrame:]): Remove method.
([EmacsMenu clear]): Rename to removeAllItems.
([EmacsMenu removeAllItems]): Use built-in removeAllItems, if
available.
(syms_of_nsmenu): Remove tracking code.
* src/nsterm.m (ns_check_menu_open):
(ns_check_pending_open_menu):
(ns_create_terminal): Remove unused functions.
(ns_term_init): Get rid of menu tracking.
* src/nsterm.h (EmacsMenu): Remove frame, add needsUpdate and update
method definitions.
Diffstat (limited to 'src/nsmenu.m')
-rw-r--r-- | src/nsmenu.m | 457 |
1 files changed, 155 insertions, 302 deletions
diff --git a/src/nsmenu.m b/src/nsmenu.m index efad978316e..3f0cd0c6ed4 100644 --- a/src/nsmenu.m +++ b/src/nsmenu.m @@ -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. */ @@ -98,16 +88,18 @@ 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; - bool owfi; + id menu = [NSApp mainMenu]; + 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; @@ -141,115 +133,94 @@ 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. */ - owfi = waiting_for_input; - waiting_for_input = 0; + set_buffer_internal_1 (XBUFFER (buffer)); - /* 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 +230,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 +310,68 @@ ns_update_menubar (struct frame *f, bool deep_p, EmacsMenu *submenu) prev_wv = wv; } - last_f = f; - if (n < 100) - n_previous_strings = n; - else - n_previous_strings = 0; + /* 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. */ + if (deep_p) + { + /* This path is typically used when a menu has been clicked. I + think Apple expect us to only update that one menu, however + to update one we need to do the hard work of parsing the + whole tree, so we may as well update them all. */ +#ifdef NS_IMPL_COCOA + int i = 1; +#else + int i = 0; +#endif + for (wv = first_wv->contents; wv; wv = wv->next) + { + /* The contents of wv should match the top level menu. */ + EmacsMenu *submenu = (EmacsMenu*)[[menu itemAtIndex:i++] submenu]; + [submenu fillWithWidgetValue: wv->contents]; + } } - free_menubar_widget_value_tree (first_wv); + else + { + /* Make sure we skip the "application" menu, which is always the + first entry in our top-level menu. */ +#ifdef NS_IMPL_COCOA + int i = 1; +#else + int i = 0; +#endif + for (wv = first_wv->contents; wv; wv = wv->next) + { + if (i < [menu numberOfItems]) + { + NSString *titleStr = [NSString stringWithUTF8String: wv->name]; + NSMenuItem *item = [menu itemAtIndex:i]; + EmacsMenu *submenu = (EmacsMenu*)[item submenu]; + [item setTitle:titleStr]; + [submenu setTitle:titleStr]; + [submenu removeAllItems]; + } + else + [menu addSubmenuWithTitle: wv->name]; + + i += 1; + } + + while (i < [menu numberOfItems]) + { + /* Remove any extra items. */ + [menu removeItemAtIndex:i]; + } + } + + + free_menubar_widget_value_tree (first_wv); #if NSMENUPROFILE ftime (&tb); @@ -460,21 +394,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 +413,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 - /* 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); -} + needsUpdate = YES; -- (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); } @@ -674,8 +531,13 @@ ns_activate_menubar (struct frame *f) /* 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--) @@ -687,20 +549,18 @@ ns_activate_menubar (struct frame *f) continue; [self removeItemAtIndex: n]; } +#endif + + needsUpdate = YES; } - (void)fillWithWidgetValue: (void *)wvptr { - [self fillWithWidgetValue: wvptr frame: (struct frame *)nil]; -} - -- (void)fillWithWidgetValue: (void *)wvptr frame: (struct frame *)f -{ widget_value *wv = (widget_value *)wvptr; /* clear existing contents */ - [self clear]; + [self removeAllItems]; /* add new contents */ for (; wv != NULL; wv = wv->next) @@ -711,10 +571,7 @@ ns_activate_menubar (struct frame *f) { 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 +580,8 @@ ns_activate_menubar (struct frame *f) } } + needsUpdate = NO; + #ifdef NS_IMPL_GNUSTEP if ([[self window] isVisible]) [self sizeToFit]; @@ -731,13 +590,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; @@ -1881,12 +1740,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); |