    Display the File->Open Previous menu using radio buttons

    This patch includes (and supercedes) the PrevfileTree.diff and
    radioOpenPrev2.diff patches.

    The radio buttons indicate which files are currently open.

    The rebuilding of the Open Previous menu is also performed when a document
    is closed, to allow the refresh of "torn-off" copies of this menu. This is
    why the invalidatePrevOpenMenus() function was made public rather than
    static.

    This version includes the essentials of TK Soh's patch
    [ 714486 ] prevfile menu in tree-view.

    2008-02-20

    Added window-local sort/tree options, triggered by two buttons at the top
    of the Open Previous menu. These are set by default to the global
    preference, but then change with user actions in the menu (especially
    useful when torn off). Also allows the user to remove an entry by
    shift-clicking (no clue to this in the menu though).

diff -aur nedit_official nedit_mod
diff -aur nedit_official/source/menu.c nedit_mod/source/menu.c
--- nedit_official/source/menu.c	2008-01-04 23:11:03.000000000 +0100
+++ nedit_mod/source/menu.c	2008-02-20 00:55:45.000000000 +0100
@@ -181,6 +181,8 @@
 static void searchWrapsDefCB(Widget w, WindowInfo *window, caddr_t callData);
 static void appendLFCB(Widget w, WindowInfo* window, caddr_t callData);
 static void sortOpenPrevDefCB(Widget w, WindowInfo *window, caddr_t callData);
+static void sortOpenPrevCB(Widget w, WindowInfo *window, caddr_t callData);
+static void showPrevOpenAsTreeCB(Widget w, WindowInfo *window, caddr_t callData);
 static void reposDlogsDefCB(Widget w, WindowInfo *window, caddr_t callData);
 static void autoScrollDefCB(Widget w, WindowInfo *window, caddr_t callData);
 static void modWarnDefCB(Widget w, WindowInfo *window, caddr_t callData);
@@ -354,7 +356,6 @@
 	char mnemonic, menuCallbackProc callback, void *cbArg, int set,
 	int mode);
 static Widget createMenuSeparator(Widget parent, char *name, int mode);
-static void invalidatePrevOpenMenus(void);
 static void updateWindowMenu(const WindowInfo *window);
 static void updatePrevOpenMenu(WindowInfo *window);
 static void updateTagsFileMenu(WindowInfo *window);
@@ -374,6 +375,7 @@
 static void updateWindowSizeMenu(WindowInfo *win);
 static int strCaseCmp(const char *str1, const char *str2);
 static int compareWindowNames(const void *windowA, const void *windowB);
+static int compareWindowShell(const void *windowA, const void *windowB);
 static void bgMenuPostAP(Widget w, XEvent *event, String *args,
 	Cardinal *nArgs);
 static void tabMenuPostAP(Widget w, XEvent *event, String *args,
@@ -670,9 +672,20 @@
     if (GetPrefMaxPrevOpenFiles() > 0) {
 	window->prevOpenMenuPane = createMenu(menuPane, "openPrevious",
     		"Open Previous", 'v', &window->prevOpenMenuItem, SHORT);
+	XtVaSetValues(window->prevOpenMenuPane, XmNadjustMargin, False, NULL);    
 	XtSetSensitive(window->prevOpenMenuItem, NPrevOpen != 0);
 	XtAddCallback(window->prevOpenMenuItem, XmNcascadingCallback,
     		(XtCallbackProc)prevOpenMenuCB, window);
+
+        window->sortOpenPrevItem = createMenuToggle(window->prevOpenMenuPane,
+            "sortOpenPrevMenu", "Sort Open Previous", 'o',
+            sortOpenPrevCB, window,
+            window->sortOpenPrevMenu, FULL);
+        window->showPrevOpenAsTreeItem = createMenuToggle(window->prevOpenMenuPane,
+            "showPrevOpenAsTree", "Display As Tree", 'T',
+            showPrevOpenAsTreeCB, window,
+            window->showPrevOpenAsTree, FULL);
+        createMenuSeparator(window->prevOpenMenuPane, "sep1", SHORT);
     }
     createMenuSeparator(menuPane, "sep1", SHORT);
     window->closeItem = createMenuItem(menuPane, "close", "Close", 'C',
@@ -2203,6 +2216,28 @@
     }
 }
 
+static void sortOpenPrevCB(Widget w, WindowInfo *window, caddr_t callData)
+{
+    int state = XmToggleButtonGetState(w);
+    window->sortOpenPrevMenu = state;
+
+    if (IsTopDocument(window)) {
+        XmToggleButtonSetState(window->sortOpenPrevItem, state, False);
+    }
+    updatePrevOpenMenu(window);
+}
+
+static void showPrevOpenAsTreeCB(Widget w, WindowInfo *window, caddr_t callData)
+{
+    int state = XmToggleButtonGetState(w);
+    window->showPrevOpenAsTree = state;
+
+    if (IsTopDocument(window)) {
+        XmToggleButtonSetState(window->showPrevOpenAsTreeItem, state, False);
+    }
+    updatePrevOpenMenu(window);
+}
+
 static void reposDlogsDefCB(Widget w, WindowInfo *window, caddr_t callData)
 {
     WindowInfo *win;
@@ -4481,7 +4516,7 @@
 ** Since actually changing the menus is slow, they're just marked and updated
 ** when the user pulls one down.
 */
-static void invalidatePrevOpenMenus(void)
+void InvalidatePrevOpenMenus(void)
 {
     WindowInfo *w;
 
@@ -4523,7 +4558,7 @@
     	    nameCopy = PrevOpen[i];
     	    memmove(&PrevOpen[1], &PrevOpen[0], sizeof(char *) * i);
     	    PrevOpen[0] = nameCopy;
-    	    invalidatePrevOpenMenus();
+    	    InvalidatePrevOpenMenus();
 	    WriteNEditDB();
     	    return;
     	}
@@ -4543,7 +4578,7 @@
     NPrevOpen++;
     
     /* Mark the Previously Opened Files menu as invalid in all windows */
-    invalidatePrevOpenMenus();
+    InvalidatePrevOpenMenus();
 
     /* Undim the menu in all windows if it was previously empty */
     if (NPrevOpen > 0) {
@@ -4558,6 +4593,43 @@
     WriteNEditDB();
 }
 
+/*
+** Remove a file from the list of previously opened files for display in the
+** File menu.
+*/
+void RemoveFromPrevOpenMenu(const char *filename)
+{
+    int i;
+
+    /* If the Open Previous command is disabled, just return */
+    if (GetPrefMaxPrevOpenFiles() < 1) {
+        return;
+    }
+
+    /*  Refresh list of previously opened files to avoid Big Race Condition,
+        where two sessions overwrite each other's changes in NEdit's
+        history file.
+        Of course there is still Little Race Condition, which occurs if a
+        Session A reads the list, then Session B reads the list and writes
+        it before Session A gets a chance to write.  */
+    ReadNEditDB();
+
+    /* If the name is already in the list, remove it */
+    for (i=0; i<NPrevOpen; i++) {
+        if (!strcmp(filename, PrevOpen[i])) {
+            XtFree(PrevOpen[i]);
+            if (i < (NPrevOpen - 1)) {
+                memmove(&PrevOpen[i], &PrevOpen[i + 1],
+                        sizeof(char *) * (NPrevOpen - i - 1));
+            }
+            PrevOpen[--NPrevOpen] = NULL;
+            InvalidatePrevOpenMenus();
+            WriteNEditDB();
+            return;
+        }
+    }
+}
+
 static char* getWindowsMenuEntry(const WindowInfo* window)
 {
     static char fullTitle[MAXPATHLEN * 2 + 3+ 1];
@@ -4672,24 +4744,37 @@
 ** Update the Previously Opened Files menu of a single window to reflect the
 ** current state of the list as retrieved from FIXME.
 ** Thanks to Markus Schwarzenberg for the sorting part.
+**
+** Optionally, display the prev-open list in directory-tree format.
 */
 static void updatePrevOpenMenu(WindowInfo *window)
 {
+    char filename[MAXPATHLEN], menuFilename[MAXPATHLEN]; 
+    char pathname[MAXPATHLEN], prevPathname[MAXPATHLEN];
+    char buf[MAXPATHLEN];
+    int matchPos;
+    Dimension indentMargin;
+    XmFontList fontlist;
     Widget btn;
     WidgetList items;
     Cardinal nItems;
-    int n, index;
+    int index;
     XmString st1;
     char **prevOpenSorted;
+    Dimension indicatorSize = 0, spacing = 0;
+    Boolean fileIsOpen = False;
 
     /*  Read history file to get entries written by other sessions.  */
     ReadNEditDB();
                 
+    /* Get font info for menus */
+    XtVaGetValues(window->prevOpenMenuItem, XmNfontList, &fontlist, NULL);
+
     /* Sort the previously opened file list if requested */
     prevOpenSorted = (char **)XtMalloc(NPrevOpen * sizeof(char*));
     memcpy(prevOpenSorted, PrevOpen, NPrevOpen * sizeof(char*));
-    if (GetPrefSortOpenPrevMenu())
-    	qsort(prevOpenSorted, NPrevOpen, sizeof(char*), cmpStrPtr);
+    if (window->sortOpenPrevMenu)
+        qsort(prevOpenSorted, NPrevOpen, sizeof(char*), cmpStrPtr);
 
     /* Go thru all of the items in the menu and rename them to match the file
        list.  In older Motifs (particularly ibm), it was dangerous to replace
@@ -4698,35 +4783,104 @@
        stick with this weird method of re-naming the items */
     XtVaGetValues(window->prevOpenMenuPane, XmNchildren, &items,
             XmNnumChildren, &nItems, NULL);
-    index = 0;
-    for (n=0; n<(int)nItems; n++) {
-        if (index >= NPrevOpen) {
-            /* unmanaging before destroying stops parent from displaying */
-            XtUnmanageChild(items[n]);
-            XtDestroyWidget(items[n]);          
-        } else {
-            XtVaSetValues(items[n], XmNlabelString,
-                    st1=XmStringCreateSimple(prevOpenSorted[index]), NULL);
-            XtRemoveAllCallbacks(items[n], XmNactivateCallback);
-            XtAddCallback(items[n], XmNactivateCallback,
-                    (XtCallbackProc)openPrevCB, prevOpenSorted[index]);
-            XmStringFree(st1);
-            index++;
-        }
-    }
+    /**//* skip the first three */
+    nItems -= 3;
+    items += 3;
+    /* get sizes (if any) */
+    if (nItems > 0) {
+        btn = items[0];
+        XtVaGetValues(btn,
+                      XmNindicatorSize, &indicatorSize,
+                      XmNspacing, &spacing,
+                      NULL);
+    }
+    /* in the begining ... */
+    prevPathname[0] = '\0';
+    
+    for (index=0; index<NPrevOpen; index++) {
+    	indentMargin = 0; 
+	if (window->showPrevOpenAsTree &&
+	        !ParseFilename(prevOpenSorted[index], filename, pathname)) {
+    	    /* search for common sub-dir in prev and current paths */
+	    for (matchPos=strlen(pathname); matchPos>1; matchPos--)
+		if (pathname[matchPos] == '/' &&
+		        !strncmp(pathname, prevPathname, matchPos+1))
+		    break;
+
+    	    if (matchPos > 1) {
+                ++matchPos;
+		/* file is in sub-directory or same directory as
+		   previous file */
+		strcpy(buf,prevOpenSorted[index]);
+		buf[matchPos] = '\0';
+
+		/* calculate margin to be indented */
+		st1=XmStringCreateSimple(buf);
+    	    	indentMargin = XmStringWidth(fontlist, st1);
+                indentMargin += indicatorSize + spacing;
+                XmStringFree(st1);
+
+    	    	/* create filepath without parent's dirname */
+		sprintf(menuFilename, "%s",
+		        &prevOpenSorted[index][matchPos]);
+    		st1=XmStringCreateSimple(menuFilename);
+            }
+	    else {
+		/* file is in a totally different path */
+		st1=XmStringCreateSimple(prevOpenSorted[index]);
+            }
     
-    /* Add new items for the remaining file names to the menu */
-    for (; index<NPrevOpen; index++) {
-        btn = XtVaCreateManagedWidget("win", xmPushButtonWidgetClass,
-                window->prevOpenMenuPane, 
-                XmNlabelString, st1=XmStringCreateSimple(prevOpenSorted[index]),
-                XmNmarginHeight, 0,
-                XmNuserData, TEMPORARY_MENU_ITEM, NULL);
-        XtAddCallback(btn, XmNactivateCallback, (XtCallbackProc)openPrevCB, 
-                prevOpenSorted[index]);
+	    strcpy(prevPathname, pathname);	    
+	}
+	else {
+	    /* full pathname mode */
+	    st1=XmStringCreateSimple(prevOpenSorted[index]);
+            ParseFilename(prevOpenSorted[index], filename, pathname);
+    	}
+
+	fileIsOpen = !!FindWindowWithFile(filename, pathname);
+    	/* update/add filepath onto menu */
+	if ((int)nItems <= index) {
+            btn = XtVaCreateManagedWidget("win", xmToggleButtonWidgetClass,
+                    window->prevOpenMenuPane, 
+                    XmNmarginHeight, 0,
+                    XmNuserData, TEMPORARY_MENU_ITEM,
+                    XmNset, fileIsOpen,
+                    XmNindicatorType, XmONE_OF_MANY, NULL);
+            if (indicatorSize == 0) {
+                XtVaGetValues(btn,
+                              XmNindicatorSize, &indicatorSize,
+                              XmNspacing, &spacing,
+                              NULL);
+            }
+            XtVaSetValues(btn,
+                          XmNmarginLeft, indentMargin /* + indicatorSize + spacing */,
+                          XmNlabelString, st1,
+                          NULL);
+            XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc)openPrevCB, 
+                    prevOpenSorted[index]);
+	}
+	else {
+            XtVaSetValues(items[index],
+	            XmNlabelString, st1,
+                    XmNmarginLeft, indentMargin,
+                    XmNset, fileIsOpen, NULL);
+            XtRemoveAllCallbacks(items[index], XmNvalueChangedCallback);
+
+            XtAddCallback(items[index], XmNvalueChangedCallback,
+                    (XtCallbackProc)openPrevCB, prevOpenSorted[index]);
+	}
+
         XmStringFree(st1);
     }
                 
+    /* delete the extras (???) in the menu */
+    for (;  index<(int)nItems; index++) {
+        /* unmanaging before destroying stops parent from displaying */
+        XtUnmanageChild(items[index]);
+        XtDestroyWidget(items[index]);  
+    }
+                
     XtFree((char*)prevOpenSorted);
 }
 
@@ -5163,16 +5317,28 @@
     RaiseFocusDocumentWindow(window, True /* always focus */);
 }
 
+/*
+** Open a file listed in the "Open Previous" menu. If the shift key is held
+** down then the file is not opened, but the entry is removed.
+*/
 static void openPrevCB(Widget w, char *name, caddr_t callData)
 {
     char *params[1];
     Widget menu = MENU_WIDGET(w);
-    
+
+    XtVaSetValues(w,
+                  XmNset, True, NULL);
+
     HidePointerOnKeyedEvent(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
             ((XmAnyCallbackStruct *)callData)->event);
     params[0] = name;
-    XtCallActionProc(WidgetToWindow(menu)->lastFocus, "open",
-    	    ((XmAnyCallbackStruct *)callData)->event, params, 1);
+    if (((XmAnyCallbackStruct *)callData)->event->xbutton.state & ShiftMask) {
+        RemoveFromPrevOpenMenu(name);
+    }
+    else {
+        XtCallActionProc(WidgetToWindow(menu)->lastFocus, "open",
+                ((XmAnyCallbackStruct *)callData)->event, params, 1);
+    }
     CheckCloseDim();
 }
 
@@ -5243,6 +5409,40 @@
     return rc;
 }
 
+static int compareWindowShell(const void *windowA, const void *windowB)
+{
+    const WindowInfo *a = *((WindowInfo**)windowA);
+    const WindowInfo *b = *((WindowInfo**)windowB);
+
+    return a->shell > b->shell;
+}
+
+/*
+** create & return a sorted list of windows
+** Windows are first sort by their filename then,
+** if windows are tabbed, grouped by their shell windows
+**
+** Note: caller must XtFree the returned window list.
+*/
+WindowInfo **MakeSortedWindowArray(void)
+{
+    WindowInfo *w, **windows;
+    int i, nWindows;
+    
+    /* Make a sorted list of windows */
+    for (w=WindowList, nWindows=0; w!=NULL; w=w->next, nWindows++);
+    windows = (WindowInfo **)XtMalloc(sizeof(WindowInfo *) * nWindows);
+    for (w=WindowList, i=0; w!=NULL; w=w->next, i++)
+    	windows[i] = w;
+    qsort(windows, nWindows, sizeof(WindowInfo *), compareWindowNames);
+    
+    /* group the documents together by their shell window */
+    if (GetPrefOpenInTab())
+        qsort(windows, nWindows, sizeof(WindowInfo *), compareWindowShell);
+
+    return windows;
+}
+
 /*
 ** Create popup for right button programmable menu
 */
diff -aur nedit_official/source/menu.h nedit_mod/source/menu.h
--- nedit_official/source/menu.h	2008-01-04 23:11:03.000000000 +0100
+++ nedit_mod/source/menu.h	2008-02-20 00:16:35.000000000 +0100
@@ -40,6 +40,7 @@
 void InstallMenuActions(XtAppContext context);
 XtActionsRec *GetMenuActions(int *nActions);
 void InvalidateWindowMenus(void);
+void InvalidatePrevOpenMenus(void);
 void CheckCloseDim(void);
 void AddToPrevOpenMenu(const char *filename);
 void WriteNEditDB(void);
diff -aur nedit_official/source/nedit.h nedit_mod/source/nedit.h
--- nedit_official/source/nedit.h	2008-01-04 23:11:03.000000000 +0100
+++ nedit_mod/source/nedit.h	2008-02-20 00:37:44.000000000 +0100
@@ -346,6 +346,8 @@
     Widget 	replaceAgainItem;
     Widget      gotoSelItem;
     Widget	langModeCascade;
+    Widget	sortOpenPrevItem;
+    Widget	showPrevOpenAsTreeItem;
     Widget	findDefItem;
     Widget	showTipItem;
     Widget	autoIndentOffItem;
@@ -529,6 +531,10 @@
     Atom	fileClosedAtom;         /* Atom used to tell nc that the file is closed */
     int    	languageMode;	    	/* identifies language mode currently
     	    	    	    	    	   selected in the window */
+
+    Bool        showPrevOpenAsTree;     /* show "Open Previous" menu as tree */
+    Bool        sortOpenPrevMenu;       /* do we sort "Open Previous" menu */
+
     Boolean	multiFileReplSelected;	/* selected during last multi-window 
 					   replacement operation (history) */
     struct _WindowInfo**		/* temporary list of writable windows */
diff -aur nedit_official/source/preferences.c nedit_mod/source/preferences.c
--- nedit_official/source/preferences.c	2008-01-14 21:39:20.000000000 +0100
+++ nedit_mod/source/preferences.c	2008-02-20 00:16:35.000000000 +0100
@@ -295,6 +295,7 @@
     int autoScroll;             /* w. to autoscroll near top/bottom of screen */
     int autoScrollVPadding;     /* how close to get before autoscrolling */
     int sortOpenPrevMenu;   	/* whether to sort the "Open Previous" menu */
+    int showPrevOpenAsTree;	/* show "Open Previous" menu in tree format */
     int appendLF;       /* Whether to append LF at the end of each file */
     int mapDelete;		/* whether to map delete to backspace */
     int stdOpenDialog;		/* w. to retain redundant text field in Open */
@@ -892,6 +893,8 @@
         &PrefData.appendLF, NULL, True},
     {"sortOpenPrevMenu", "SortOpenPrevMenu", PREF_BOOLEAN, "True",
     	&PrefData.sortOpenPrevMenu, NULL, True},
+    {"showPrevOpenAsTree", "ShowPrevOpenAsTree", PREF_BOOLEAN, "True",
+    	&PrefData.showPrevOpenAsTree, NULL, False},
     {"statisticsLine", "StatisticsLine", PREF_BOOLEAN, "False",
     	&PrefData.statsLine, NULL, True},
     {"iSearchLine", "ISearchLine", PREF_BOOLEAN, "False",
@@ -1972,6 +1975,16 @@
     return PrefData.sortOpenPrevMenu;
 }
 
+void SetPrefShowPrevOpenAsTree(int state)
+{
+    setIntPref(&PrefData.showPrevOpenAsTree, state);
+}
+
+int GetPrefShowPrevOpenAsTree(void)
+{
+    return PrefData.showPrevOpenAsTree;
+}
+
 char *GetPrefTagFile(void)
 {
     return PrefData.tagFile;
diff -aur nedit_official/source/preferences.h nedit_mod/source/preferences.h
--- nedit_official/source/preferences.h	2008-01-04 23:11:03.000000000 +0100
+++ nedit_mod/source/preferences.h	2008-02-20 00:16:35.000000000 +0100
@@ -126,6 +126,8 @@
 int GetPrefAppendLF(void);
 void SetPrefSortOpenPrevMenu(int state);
 int GetPrefSortOpenPrevMenu(void);
+void SetPrefShowPrevOpenAsTree(int state);
+int GetPrefShowPrevOpenAsTree(void);
 char *GetPrefTagFile(void);
 int GetPrefSmartTags(void);
 void SetPrefSmartTags(int state);
diff -aur nedit_official/source/window.c nedit_mod/source/window.c
--- nedit_official/source/window.c	2008-01-05 03:24:13.000000000 +0100
+++ nedit_mod/source/window.c	2008-02-20 00:49:32.000000000 +0100
@@ -309,6 +309,10 @@
     window->macroCmdData = NULL;
     window->smartIndentData = NULL;
     window->languageMode = PLAIN_LANGUAGE_MODE;
+
+    window->showPrevOpenAsTree = GetPrefShowPrevOpenAsTree();
+    window->sortOpenPrevMenu = GetPrefSortOpenPrevMenu();
+
     window->iSearchHistIndex = 0;
     window->iSearchStartPos = -1;
     window->replaceLastRegexCase   = TRUE;
@@ -1040,6 +1044,7 @@
     /* remove the window from the global window list, update window menus */
     removeFromWindowList(window);
     InvalidateWindowMenus();
+    InvalidatePrevOpenMenus();
     CheckCloseDim(); /* Close of window running a macro may have been disabled. */
 
     /* remove the tab of the closing document from tab bar */
@@ -4332,7 +4337,10 @@
     window->inode = orgWin->inode;
     window->fileClosedAtom = orgWin->fileClosedAtom;
     orgWin->fileClosedAtom = None;
-    
+
+    window->showPrevOpenAsTree = orgWin->showPrevOpenAsTree;
+    window->sortOpenPrevMenu = orgWin->sortOpenPrevMenu;
+
     /* copy the text/split panes settings, cursor pos & selection */
     cloneTextPanes(window, orgWin);
     
