    Avoid rebuilding menu structures if only macro/shell text has changed

    If you change a macro or shell commmand menu entry using the appropriate
    dialog, this patch checks what has changed so that menus are only rebuilt
    if their structure is altered in any way. If only the text of the macro
    or command has changed, the menus are not rebuilt.

    This avoids losing tear-off menus in many cases.

diff -ru nedit_official/source/userCmds.c nedit_mod/source/userCmds.c
--- nedit_official/source/userCmds.c	2005-01-31 15:34:24.000000000 +0100
+++ nedit_mod/source/userCmds.c	2006-07-19 14:22:08.468750000 +0200
@@ -105,16 +105,16 @@
 
 /* Structure representing a menu item for shell, macro and BG menus*/
 typedef struct {
-    char *name;
-    unsigned int modifiers;
-    KeySym keysym;
-    char mnemonic;
-    char input;
-    char output;
-    char repInput;
-    char saveFirst;
-    char loadAfter;
-    char *cmd;
+    char *name;             /* fully qualified menu item name */
+    unsigned int modifiers; /* accelerator modifier keys etc */
+    KeySym keysym;          /* accelerator key */
+    char mnemonic;          /* menu's mnemonic/quick access char */
+    char input;             /* shell: input type (enum inSrcs) */
+    char output;            /* shell: output type (enum outDests) */
+    char repInput;          /* shell: output replaces input? */
+    char saveFirst;         /* shell: save before executing? */
+    char loadAfter;         /* shell: reload after executing? */
+    char *cmd;              /* text of macro/shell command  */
 } menuItemRec;
 
 /* Structure for widgets and flags associated with shell command,
@@ -2103,52 +2103,114 @@
 
 static int applyDialogChanges(userCmdDialog *ucd)
 {
-    int i;
-    
+    int i, nItems, nNewItems, nOldItems, *pnItems;
+    Boolean changedMenus = False, changeMenu;
+    Boolean changedPrefs = False;
+    menuItemRec **items, *newItem, *oldItem;
+    userMenuInfo **menuInfo;
+    userSubMenuCache *psubMenus;
+
     /* Get the current contents of the dialog fields */
     if (!UpdateManagedList(ucd->managedList, True))
-    	return False;
-    
+        return False;
+
     /* Test compile the macro */
     if (ucd->dialogType == MACRO_CMDS)
-	if (!checkMacro(ucd))
-	    return False;
-    
-    /* Update the menu information */
-    if (ucd->dialogType == SHELL_CMDS) {
-    	for (i=0; i<NShellMenuItems; i++)
-    	    freeMenuItemRec(ShellMenuItems[i]);
-        freeUserMenuInfoList(ShellMenuInfo, NShellMenuItems);
-        freeSubMenuCache(&ShellSubMenus);
-    	for (i=0; i<ucd->nMenuItems; i++)
-    	    ShellMenuItems[i] = copyMenuItemRec(ucd->menuItemsList[i]);
-    	NShellMenuItems = ucd->nMenuItems;
-        parseMenuItemList(ShellMenuItems, NShellMenuItems, ShellMenuInfo, &ShellSubMenus);
-    } else if (ucd->dialogType == MACRO_CMDS) {
-    	for (i=0; i<NMacroMenuItems; i++)
-    	    freeMenuItemRec(MacroMenuItems[i]);
-        freeUserMenuInfoList(MacroMenuInfo, NMacroMenuItems);
-        freeSubMenuCache(&MacroSubMenus);
-    	for (i=0; i<ucd->nMenuItems; i++)
-    	    MacroMenuItems[i] = copyMenuItemRec(ucd->menuItemsList[i]);
-    	NMacroMenuItems = ucd->nMenuItems;
-        parseMenuItemList(MacroMenuItems, NMacroMenuItems, MacroMenuInfo, &MacroSubMenus);
-    } else { /* BG_MENU_CMDS */
-    	for (i=0; i<NBGMenuItems; i++)
-    	    freeMenuItemRec(BGMenuItems[i]);
-        freeUserMenuInfoList(BGMenuInfo, NBGMenuItems);
-        freeSubMenuCache(&BGSubMenus);
-    	for (i=0; i<ucd->nMenuItems; i++)
-    	    BGMenuItems[i] = copyMenuItemRec(ucd->menuItemsList[i]);
-    	NBGMenuItems = ucd->nMenuItems;
-        parseMenuItemList(BGMenuItems, NBGMenuItems, BGMenuInfo, &BGSubMenus);
+        if (!checkMacro(ucd))
+            return False;
+
+    /* Update the menu information: Note that you only need to rebuild the
+       menus if the structure has changed in any way */
+    switch (ucd->dialogType)
+    {
+    case SHELL_CMDS:
+        pnItems = &NShellMenuItems;
+        items = ShellMenuItems;
+        menuInfo = ShellMenuInfo;
+        psubMenus = &ShellSubMenus;
+        break;
+    case MACRO_CMDS:
+        pnItems = &NMacroMenuItems;
+        items = MacroMenuItems;
+        menuInfo = MacroMenuInfo;
+        psubMenus = &MacroSubMenus;
+        break;
+    case BG_MENU_CMDS:
+        pnItems = &NBGMenuItems;
+        items = BGMenuItems;
+        menuInfo = BGMenuInfo;
+        psubMenus = &BGSubMenus;
+        break;
+    default:
+        return False;
+    }
+    nItems = *pnItems;
+
+    /* How many new, how many old? Fix things up so that nItems is the smaller
+       of the number of items before and after the changes, nOldItems is the
+       number of items before the changes and nNewItems is the
+       number of items after the changes. */
+    if (nItems > ucd->nMenuItems) {
+        /* some deleted */
+        nOldItems = nItems;
+        nItems = ucd->nMenuItems;
+        nNewItems = nItems;
+    } else {
+        /* some added */
+        nNewItems = ucd->nMenuItems;
+        nOldItems = nItems;
+    }
+    /* now replace items where different, noting where name differs, or other
+       stuff (like selection sensitivity, accelerators, etc) */
+    for (i = 0; i < nItems; i++) {
+        oldItem = items[i];
+        newItem = ucd->menuItemsList[i];
+        changeMenu = (strcmp(oldItem->name, newItem->name) != 0 ||
+                      oldItem->modifiers != newItem->modifiers  ||
+                      oldItem->keysym    != newItem->keysym     ||
+                      oldItem->mnemonic  != newItem->mnemonic   ||
+                      oldItem->input     != newItem->input);
+        if (changeMenu ||
+            oldItem->output    != newItem->output    ||
+            oldItem->repInput  != newItem->repInput  ||
+            oldItem->saveFirst != newItem->saveFirst ||
+            oldItem->loadAfter != newItem->loadAfter ||
+            strcmp(oldItem->cmd, newItem->cmd) != 0)
+        {
+            items[i] = copyMenuItemRec(newItem);
+            freeMenuItemRec(oldItem);
+            changedPrefs = True;
+        }
+        changedMenus |= changeMenu;
+    }
+    for (i = nItems; i < nOldItems; i++) {
+        freeMenuItemRec(items[i]);
+        items[i] = NULL;
+    }
+    for (i = nItems; i < nNewItems; i++) {
+        items[i] = copyMenuItemRec(ucd->menuItemsList[i]);
+    }
+    if (nItems != nNewItems || nItems != nOldItems) {
+        changedMenus = changedPrefs = True;
+    }
+    if (changedMenus) {
+        /* rebuild the menus */
+        freeUserMenuInfoList(menuInfo, nOldItems);
+        freeSubMenuCache(psubMenus);
+        *pnItems = nNewItems;
+        parseMenuItemList(items, nNewItems, menuInfo, psubMenus);
     }
     
-    /* Update the menus themselves in all of the NEdit windows */
-    rebuildMenuOfAllWindows(ucd->dialogType);
-    
-    /* Note that preferences have been changed */
-    MarkPrefsChanged();
+    if (changedMenus) {
+        /* Update the menus themselves in all of the NEdit windows */
+        rebuildMenuOfAllWindows(ucd->dialogType);
+    }
+
+    if (changedPrefs) {
+        /* Note that preferences have been changed */
+        MarkPrefsChanged();
+    }
+
     return True;
 }
 
