1 /*******************************************************************************
2 *                                                                              *
3 * Getfiles.c -- File Interface Routines                                        *
4 *                                                                              *
5 * Copyright (C) 1999 Mark Edel                                                 *
6 *                                                                              *
7 * This is free software; you can redistribute it and/or modify it under the    *
8 * terms of the GNU General Public License as published by the Free Software    *
9 * Foundation; either version 2 of the License, or (at your option) any later   *
10 * version. In addition, you may distribute version of this program linked to   *
11 * Motif or Open Motif. See README for details.                                 *
12 *                                                                              *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
15 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
16 * for more details.                                                            *
17 *                                                                              *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
20 * Place, Suite 330, Boston, MA  02111-1307 USA                                 *
21 *                                                                              *
22 * Nirvana Text Editor                                                          *
23 * May 23, 1991                                                                 *
24 *                                                                              *
25 * Written by Donna Reid                                                        *
26 *                                                                              *
27 * modified 11/5/91 by JMK: integrated changes made by M. Edel; updated for     *
28 *                          destroy widget problem (took out ManageModalDialog  *
29 *                          call; added comments.                               *
30 *          10/1/92 by MWE: Added help dialog and fixed a few bugs              *
31 *           4/7/93 by DR:  Port to VMS                                         *
32 *           6/1/93 by JMK: Integrate Port and changes by MWE to make           *
33 *                          directories "sticky" and a fix to prevent opening   *
34 *                          a directory when no filename was specified          *
35 *          6/24/92 by MWE: Made filename list and directory list typeable,     *
36 *                          set initial focus to filename list                  *
37 *          6/25/93 by JMK: Fix memory leaks found by Purify.                   *
38 *                                                                              *
39 * Included are two routines written using Motif for accessing files:           *
40 *                                                                              *
41 * GetExistingFilename  presents a FileSelectionBox dialog where users can      *
42 *                      choose an existing file to open.                        *
43 *                                                                              *
44 *******************************************************************************/
45 
46 #ifdef HAVE_CONFIG_H
47 #include "../config.h"
48 #endif
49 
50 #include "getfiles.h"
51 #include "fileUtils.h"
52 #include "misc.h"
53 #include "nedit_malloc.h"
54 
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <ctype.h>
59 #include <sys/types.h>
60 #ifdef VMS
61 #include <unixio.h>
62 #include <file.h>
63 #include "VMSparam.h"
64 #else
65 #include <unistd.h>
66 #include <fcntl.h>
67 #include <dirent.h>
68 #ifndef __MVS__
69 #include <sys/param.h>
70 #endif
71 #endif /*VMS*/
72 #include <sys/stat.h>
73 
74 #include <X11/keysym.h>
75 #include <Xm/Xm.h>
76 #include <Xm/FileSB.h>
77 #include <Xm/Form.h>
78 #include <Xm/List.h>
79 #include <Xm/MessageB.h>
80 #include <Xm/PushBG.h>
81 #include <Xm/Text.h>
82 #include <Xm/TextF.h>
83 
84 #ifdef HAVE_DEBUG_H
85 #include "../debug.h"
86 #endif
87 
88 #define MAX_ARGS 20			/* Maximum number of X arguments */
89 #define PERMS 0666     			/* UNIX file permission, RW for owner,
90 					   group, world */
91 #define MAX_LIST_KEYSTROKES 100		/* Max # of keys user can type to
92 					   a file list */
93 #define MAX_LIST_KESTROKE_WAIT 2000	/* Allowable delay in milliseconds
94 					   between characters typed to a list
95 					   before starting over (throwing
96 					   out the accumulated characters */
97 
98 #define SET_ONE_RSRC(widget, name, newValue) \
99 { \
100     static Arg tmpargs[1] = {{name, (XtArgVal)0}}; \
101     tmpargs[0].value = (XtArgVal)newValue; \
102     XtSetValues(widget, tmpargs, 1); \
103 }
104 
105 enum yesNoValues {ynNone, ynYes, ynNo};
106 
107 /* Saved default directory and pattern from last successful call */
108 static XmString DefaultDirectory = NULL;
109 static XmString DefaultPattern = NULL;
110 
111 /* User settable option for leaving the file name text field in
112    GetExistingFilename dialogs.  Off by default so new users will get
113    used to typing in the list rather than in the text field */
114 static int RemoveRedundantTextField = True;
115 
116 /* Text for help button help display */
117 /* ... needs variant for VMS */
118 #ifndef SGI_CUSTOM
119 static const char *HelpExist =
120 "The file open dialog shows a list of directories on the left, and a list \
121 of files on the right.  Double clicking on a file name in the list on the \
122 right, or selecting it and pressing the OK button, will open that file.  \
123 Double clicking on a directory name, or selecting \
124 it and pressing \"Filter\", will move into that directory.  To move upwards in \
125 the directory tree, double click on the directory entry ending in \"..\".  \
126 You can also begin typing a file name to select from the file list, or \
127 directly type in directory and file specifications in the \
128 field labeled \"Filter\".\n\
129 \n\
130 If you use the filter field, remember to include \
131 either a file name, \"*\" is acceptable, or a trailing \"/\".  If \
132 you don't, the name after the last \"/\" is interpreted as the file name to \
133 match.  When you leave off the file name or trailing \"/\", you won't see \
134 any files to open in the list \
135 because the filter specification matched the directory file itself, rather \
136 than the files in the directory.";
137 
138 static const char *HelpNew =
139 "This dialog allows you to create a new file, or to save the current file \
140 under a new name.  To specify a file \
141 name in the current directory, complete the name displayed in the \"Save File \
142 As:\" field near the bottom of the dialog.  If you delete or change \
143 the path shown in the field, the file will be saved using whatever path \
144 you type, provided that it is a valid Unix file specification.\n\
145 \n\
146 To replace an existing file, select it from the Files list \
147 and press \"OK\", or simply double click on the name.\n\
148 \n\
149 To save a file in another directory, use the Directories list \
150 to move around in the file system hierarchy.  Double clicking on \
151 directory names in the list, or selecting them and pressing the \
152 \"Filter\" button will select that directory.  To move upwards \
153 in the directory tree, double \
154 click on the directory entry ending in \"..\".  You can also move directly \
155 to a directory by typing the file specification of the path in the \"Filter\" \
156 field and pressing the \"Filter\" button.";
157 
158 #else /* SGI_CUSTOM */
159 static const char *HelpExist =
160 "The \"File to Edit:\" field shows a list of directories and files in the \
161 current directory.\n\
162 \n\
163 Double clicking on a file name in the list, or selecting it and pressing \
164 the OK button, will open that file.\n\
165 \n\
166 Double clicking on a directory name, or selecting it and pressing the OK \
167 button will move into that directory.  To navigate upwards in the file \
168 system hierarchy you can use the buttons above the \"Selection\" field  \
169 (each of these buttons represent a directory level). \n\
170 \n\
171 You can also enter a file or directory name to open in the field \
172 labeled \"Selection\".  Pressing the space bar will complete a partial file \
173 name, or beep if no files match.  The drop pocket to the right of the field \
174 will accept icons dragged from the desktop, and the button with the circular \
175 arrows, to the right, of the field recalls previously selected \
176 directories.\n\
177 \n\
178 The \"Filter\" button allows you to narrow down the list of files and \
179 directories shown in the \"File to Edit:\" field.  The default filter of \
180 \"*\" allows all files to be listed.";
181 
182 static const char *HelpNew =
183 "This dialog allows you to create a new file or to save the current file \
184 under a new name.\n\
185 \n\
186 To specify a file name in the current directory, complete the name displayed \
187 in the \"Save File As:\" field.  If you delete or change the path shown \
188 in the field, the file will be saved using whatever path you type, provided \
189 that it is a valid Unix file specification.\n\
190 \n\
191 To replace an existing file, select it from the \"Files\" list and press \
192 \"OK\", or simply double click on the name in the \"Files\" list.\n\
193 \n\
194 To save a file in another directory, use the \"Files\" list to move around \
195 in the file system hierarchy.  Double clicking on a directory name, or \
196 selecting it and pressing the OK button, will move into that directory. \
197 To navigate upwards in the file system hierarchy you can use the buttons \
198 above the \"Selection\" field (each of these buttons represent a directory \
199 level).\n\
200 \n\
201 You can also move directly to a directory by typing the file specification \
202 of the path in the \"Save File As:\" field.  Pressing the space bar will \
203 complete a partial directory or file \
204 name, or beep if nothing matches.  The drop pocket to the right of the field \
205 will accept icons dragged from the desktop, and the button with the circular \
206 arrows, to the right, of the field recalls previously selected \
207 directories.\n\
208 \n\
209 The \"Filter\" button allows you to narrow down the list of files and \
210 directories shown in the \"Files\" field.  The default filter of \
211 \"*\" allows all files to be listed.";
212 #endif /* SGI_CUSTOM */
213 
214 /*                    Local Callback Routines and variables                */
215 
216 static void newFileOKCB(Widget w, Boolean *client_data,
217 	       XmFileSelectionBoxCallbackStruct *call_data);
218 static void newFileCancelCB(Widget w, Boolean *client_data, caddr_t
219                call_data);
220 static void newHelpCB(Widget w, Widget helpPanel, caddr_t call_data);
221 static void createYesNoDialog(Widget parent);
222 static void createErrorDialog(Widget parent);
223 static int  doYesNoDialog(const char *msg);
224 static void doErrorDialog(const char *errorString, const char *filename);
225 static void existOkCB(Widget w, Boolean * client_data,
226 	              XmFileSelectionBoxCallbackStruct *call_data);
227 static void existCancelCB(Widget w, Boolean * client_data, caddr_t call_data);
228 static void existHelpCB(Widget w, Widget helpPanel, caddr_t call_data);
229 static void errorOKCB(Widget w, caddr_t client_data, caddr_t call_data);
230 static void yesNoOKCB(Widget w, caddr_t client_data, caddr_t call_data);
231 static void yesNoCancelCB(Widget w, caddr_t client_data, caddr_t call_data);
232 static Widget createPanelHelp(Widget parent, const char *text, const char *title);
233 static void helpDismissCB(Widget w, Widget helpPanel, caddr_t call_data);
234 static void makeListTypeable(Widget listW);
235 static void listCharEH(Widget w, XtPointer callData, XEvent *event,
236 	Boolean *continueDispatch);
237 static void replacementDirSearchProc(Widget w, XtPointer searchData);
238 static void replacementFileSearchProc(Widget w, XtPointer searchData);
239 static void sortWidgetList(Widget listWidget);
240 static int compareXmStrings(const void *string1, const void *string2);
241 
242 static int  SelectResult = GFN_CANCEL;  /*  Initialize results as cancel   */
243 static Widget YesNoDialog;		/* "Overwrite?" dialog widget	   */
244 static int YesNoResult;			/* Result of overwrite dialog	   */
245 static Widget ErrorDialog;		/* Dialog widget for error msgs	   */
246 static int ErrorDone;			/* Flag to mark dialog completed   */
247 static void (*OrigDirSearchProc)();	/* Built in Motif directory search */
248 static void (*OrigFileSearchProc)();	/* Built in Motif file search proc */
249 
250 /*
251  * Do the hard work of setting up a file selection dialog
252  */
getFilenameHelper(Widget parent,char * promptString,char * filename,int existing)253 Widget getFilenameHelper(Widget parent, char *promptString, char *filename,
254         int existing)
255 {
256     int       n;                      /* number of arguments               */
257     Arg	      args[MAX_ARGS];	      /* arg list	                   */
258     Widget    fileSB;	              /* widget file select box 	   */
259     XmString  titleString;	      /* compound string for dialog title  */
260 
261     n = 0;
262     titleString = XmStringCreateSimple(promptString);
263     XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
264     XtSetArg(args[n], XmNdialogTitle, titleString); n++;
265     fileSB = CreateFileSelectionDialog(parent,"FileSelect",args,n);
266     XmStringFree(titleString);
267 #ifndef SGI_CUSTOM
268     if (existing && RemoveRedundantTextField)
269         XtUnmanageChild(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
270     XtUnmanageChild(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_SELECTION_LABEL));
271 
272     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_LABEL),
273             XmNmnemonic, 'l',
274             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT),
275             NULL);
276     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST_LABEL),
277             XmNmnemonic, 'D',
278             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST),
279             NULL);
280     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST_LABEL),
281             XmNmnemonic, promptString[strspn(promptString, "lD")],
282             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST),
283             NULL);
284     AddDialogMnemonicHandler(fileSB, FALSE);
285     RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
286     RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
287 #endif
288     return fileSB;
289 }
290 
291 /*  GetExistingFilename				  	                   */
292 /*									   */
293 /*  This routine will popup a file selection box so that the user can      */
294 /*  select an existing file from the scrollable list.  The user is         */
295 /*  prevented from entering a new filename because the edittable text      */
296 /*  area of the file selection box widget is unmanaged.  After the user    */
297 /*  selects a file, GetExistingFilename returns the selected filename and  */
298 /*  GFN_OK, indicating that the OK button was pressed.  If the user        */
299 /*  pressed the cancel button, the return value is GFN_CANCEL, and the     */
300 /*  filename character string supplied in the call is not altered.	   */
301 /*									   */
302 /*  Arguments:								   */
303 /*									   */
304 /*	Widget  parent	      - parent widget id			   */
305 /*	char *  promptString  - prompt string				   */
306 /*	char *  filename      - a string to receive the selected filename  */
307 /*				(this string will not be altered if the    */
308 /*				user pressed the cancel button)		   */
309 /*									   */
310 /*  Returns:	GFN_OK	      - file was selected and OK button pressed	   */
311 /*		GFN_CANCEL    - Cancel button pressed and no returned file */
312 /*									   */
GetExistingFilename(Widget parent,char * promptString,char * filename)313 int GetExistingFilename(Widget parent, char *promptString, char *filename)
314 {
315     Widget existFileSB = getFilenameHelper(parent, promptString, filename,
316             True);
317     return HandleCustomExistFileSB(existFileSB, filename);
318 }
319 
320 /* GetNewFilename
321  *
322  * Same as GetExistingFilename but pick a new file instead of an existing one.
323  * In this case the text area of the FSB is *not* unmanaged, so the user can
324  * enter a new filename.
325  */
GetNewFilename(Widget parent,char * promptString,char * filename,char * defaultName)326 int GetNewFilename(Widget parent, char *promptString, char *filename,
327         char *defaultName)
328 {
329     Widget fileSB = getFilenameHelper(parent, promptString, filename, False);
330     return HandleCustomNewFileSB(fileSB, filename, defaultName);
331 }
332 
333 /*
334 ** HandleCustomExistFileSB
335 **
336 ** Manage a customized file selection box for opening existing files.
337 ** Use this if you want to change the standard file selection dialog
338 ** from the defaults provided in GetExistingFilename, but still
339 ** want take advantage of the button processing, help messages, and
340 ** file checking of GetExistingFilename.
341 **
342 **  Arguments:
343 **
344 **	Widget  existFileSB   - your custom file selection box widget id
345 **	char *  filename      - a string to receive the selected filename
346 **				(this string will not be altered if the
347 **				user pressed the cancel button)
348 **
349 **  Returns:	GFN_OK	      - file was selected and OK button pressed
350 **		GFN_CANCEL    - Cancel button pressed and no returned file
351 **
352 */
HandleCustomExistFileSB(Widget existFileSB,char * filename)353 int HandleCustomExistFileSB(Widget existFileSB, char *filename)
354 {
355     Boolean   done_with_dialog=False; /* ok to destroy dialog flag	   */
356     char      *fileString;            /* C string for file selected        */
357     char      *dirString;             /* C string for dir of file selected */
358     XmString  cFileString;            /* compound string for file selected */
359     XmString  cDir;	              /* compound directory selected	   */
360     XmString  cPattern;               /* compound filter pattern	   */
361     Widget    help;		      /* help window form dialog	   */
362 #if XmVersion < 1002
363     int       i;
364 #endif
365 
366     XtAddCallback(existFileSB, XmNokCallback, (XtCallbackProc)existOkCB,
367     	    &done_with_dialog);
368     XtAddCallback(existFileSB, XmNcancelCallback, (XtCallbackProc)existCancelCB,
369 	    &done_with_dialog);
370     AddMotifCloseCallback(XtParent(existFileSB), (XtCallbackProc)existCancelCB,
371 	    &done_with_dialog);
372     help = createPanelHelp(existFileSB, HelpExist, "Selecting Files to Open");
373     createErrorDialog(existFileSB);
374     XtAddCallback(existFileSB, XmNhelpCallback, (XtCallbackProc)existHelpCB,
375     	    (char *)help);
376     if (DefaultDirectory != NULL || DefaultPattern != NULL)
377     	XtVaSetValues(existFileSB, XmNdirectory, DefaultDirectory,
378     		XmNpattern, DefaultPattern, NULL);
379 #ifndef SGI_CUSTOM
380     makeListTypeable(XmFileSelectionBoxGetChild(existFileSB,XmDIALOG_LIST));
381     makeListTypeable(XmFileSelectionBoxGetChild(existFileSB,XmDIALOG_DIR_LIST));
382 #if XmVersion >= 1002
383     XtVaSetValues(existFileSB, XmNinitialFocus, XtParent(
384     	    XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST)), NULL);
385 #endif
386 #endif
387     ManageDialogCenteredOnPointer(existFileSB);
388 
389 #ifndef SGI_CUSTOM
390     /* Typing in the directory list is dependent on the list being in the
391        same form of alphabetical order expected by the character processing
392        routines.  As of about 1.2.3, some Motif libraries seem to have a
393        different idea of ordering than is usual for Unix directories.
394        To sort them properly, we have to patch the directory and file
395        searching routines to re-sort the lists when they change */
396     XtVaGetValues(existFileSB, XmNdirSearchProc, &OrigDirSearchProc,
397     	    XmNfileSearchProc, &OrigFileSearchProc, NULL);
398     XtVaSetValues(existFileSB, XmNdirSearchProc, replacementDirSearchProc,
399     	    XmNfileSearchProc, replacementFileSearchProc, NULL);
400     sortWidgetList(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_DIR_LIST));
401     sortWidgetList(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST));
402 #if XmVersion < 1002
403     /* To give file list initial focus, revoke default button status for
404        the "OK" button.  Dynamic defaulting will restore it as the default
405        button after the keyboard focus is established.  Note the voodoo
406        below: calling XmProcess traversal extra times (a recommendation from
407        OSF technical support) somehow succeedes in giving the file list focus */
408     XtVaSetValues(existFileSB, XmNdefaultButton, NULL, NULL);
409     for (i=1; i<30; i++)
410     	XmProcessTraversal(XmFileSelectionBoxGetChild(existFileSB,
411     		XmDIALOG_LIST), XmTRAVERSE_CURRENT);
412 #endif
413 #endif /* SGI_CUSTOM */
414 
415     while (!done_with_dialog)
416         XtAppProcessEvent(XtWidgetToApplicationContext(existFileSB), XtIMAll);
417 
418     if (SelectResult == GFN_OK) {
419 	XtVaGetValues(existFileSB, XmNdirSpec, &cFileString, XmNdirectory,
420 		&cDir, XmNpattern, &cPattern, NULL);
421 	/* Undocumented: file selection box widget allocates copies of these
422 	   strings on getValues calls.  I have risked freeing them to avoid
423 	   memory leaks, since I assume other developers have made this same
424 	   realization, therefore OSF can't easily go back and change it */
425 	if (DefaultDirectory != NULL) XmStringFree(DefaultDirectory);
426 	if (DefaultPattern != NULL) XmStringFree(DefaultPattern);
427 	DefaultDirectory = cDir;
428 	DefaultPattern = cPattern;
429 	XmStringGetLtoR(cFileString, XmSTRING_DEFAULT_CHARSET, &fileString);
430         /* Motif 2.x seem to contain a bug that causes it to return only
431            the relative name of the file in XmNdirSpec when XmNpathMode is set
432            to XmPATH_MODE_RELATIVE (through X resources), although the man
433            page states that it always returns the full path name. We can
434            easily work around this by checking that the first character of the
435            file name is a `/'. */
436 #ifdef VMS
437        /* VMS  won't return `/' as the 1st character of the full file spec.
438          `:' terminates the device name and is not allowed elsewhere */
439         if (strchr(fileString, ':') != NULL) {
440 #else
441         if (fileString[0] == '/') {
442 #endif        /* VMS */
443 	    /* The directory name is already present in the file name or
444 	       the user entered a full path name. */
445 	    strcpy(filename, fileString);
446 	} else {
447 	    /* Concatenate the directory name and the file name */
448    	    XmStringGetLtoR(cDir, XmSTRING_DEFAULT_CHARSET, &dirString);
449 	    strcpy(filename, dirString);
450 	    strcat(filename, fileString);
451   	    NEditFree(dirString);
452 	}
453 	XmStringFree(cFileString);
454 	NEditFree(fileString);
455     }
456     /* Destroy the dialog _shell_ iso. the dialog. Normally, this shouldn't
457        be necessary as the shell is destroyed automatically when the dialog
458        is. However, due to a bug in various Lesstif versions, the latter
459        messes up the grab cascades and leaves new windows without grabs, such
460        that they appear to be frozen. */
461     XtDestroyWidget(XtParent(existFileSB));
462     return SelectResult;
463 }
464 
465 
466 /*
467 ** HandleCustomNewFileSB
468 **
469 ** Manage a customized file selection box for opening new files.
470 **
471 **  Arguments:
472 **
473 **	Widget  newFileSB     - your custom file selection box widget id
474 **	char *  filename      - a string to receive the selected filename
475 **				(this string will not be altered if the
476 **				user pressed the cancel button)
477 **  	char*	defaultName   - default name to be pre-entered in filename
478 **  	    	    	    	text field.
479 **
480 **  Returns:	GFN_OK	      - file was selected and OK button pressed
481 **		GFN_CANCEL    - Cancel button pressed and no returned file
482 **
483 */
484 int HandleCustomNewFileSB(Widget newFileSB, char *filename, char *defaultName)
485 {
486     Boolean   done_with_dialog=False; /* ok to destroy dialog flag	   */
487     Widget    help;		      /* help window form dialog	   */
488     XmString  cFileString;            /* compound string for file selected */
489     XmString  cDir;	              /* compound directory selected	   */
490     XmString  cPattern;               /* compound filter pattern	   */
491     char      *fileString;            /* C string for file selected        */
492     char      *dirString;             /* C string for dir of file selected */
493 #if XmVersion < 1002
494     int       i;
495 #endif
496 
497     XtAddCallback(newFileSB, XmNokCallback, (XtCallbackProc)newFileOKCB,
498     	    &done_with_dialog);
499     XtAddCallback(newFileSB, XmNcancelCallback, (XtCallbackProc)newFileCancelCB,
500 	    &done_with_dialog);
501 
502 #ifndef SGI_CUSTOM
503     makeListTypeable(XmFileSelectionBoxGetChild(newFileSB,XmDIALOG_LIST));
504     makeListTypeable(XmFileSelectionBoxGetChild(newFileSB,XmDIALOG_DIR_LIST));
505 #endif
506     if (DefaultDirectory != NULL || DefaultPattern != NULL)
507     	XtVaSetValues(newFileSB, XmNdirectory, DefaultDirectory,
508     		XmNpattern, DefaultPattern, NULL);
509     help = createPanelHelp(newFileSB, HelpNew, "Saving a File");
510     createYesNoDialog(newFileSB);
511     createErrorDialog(newFileSB);
512     XtAddCallback(newFileSB, XmNhelpCallback, (XtCallbackProc)newHelpCB,
513     	    (char *)help);
514 #if XmVersion >= 1002
515 #ifndef SGI_CUSTOM
516     XtVaSetValues(newFileSB, XmNinitialFocus,
517     	    XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT), NULL);
518 #else /* SGI_CUSTOM */
519     { Widget finder = XmFileSelectionBoxGetChild(newFileSB, SgDIALOG_FINDER);
520       if ( finder != NULL )
521     	  XtVaSetValues(newFileSB, XmNinitialFocus, finder, NULL);
522     }
523 #endif
524 #endif
525     ManageDialogCenteredOnPointer(newFileSB);
526 
527 #ifndef SGI_CUSTOM
528 #if XmVersion < 1002
529     /* To give filename text initial focus, revoke default button status for
530        the "OK" button.  Dynamic defaulting will restore it as the default
531        button after the keyboard focus is established.  Note the voodoo
532        below: calling XmProcess traversal FOUR times (a recommendation from
533        OSF technical support) somehow succeedes in changing the focus */
534     XtVaSetValues(newFileSB, XmNdefaultButton, NULL, NULL);
535     for (i=1; i<30; i++)
536     	XmProcessTraversal(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT),
537 	    XmTRAVERSE_CURRENT);
538 #endif
539 
540     /* Typing in the directory list is dependent on the list being in the
541        same form of alphabetical order expected by the character processing
542        routines.  As of about 1.2.3, some Motif libraries seem to have a
543        different idea of ordering than is usual for Unix directories.
544        To sort them properly, we have to patch the directory and file
545        searching routines to re-sort the lists when they change */
546     XtVaGetValues(newFileSB, XmNdirSearchProc, &OrigDirSearchProc,
547     	    XmNfileSearchProc, &OrigFileSearchProc, NULL);
548     XtVaSetValues(newFileSB, XmNdirSearchProc, replacementDirSearchProc,
549     	    XmNfileSearchProc, replacementFileSearchProc, NULL);
550     sortWidgetList(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_DIR_LIST));
551     sortWidgetList(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_LIST));
552 #endif /* SGI_CUSTOM */
553 
554     /* Delay the setting of the default name till after the replacement of
555        the search procedures. Otherwise the field is cleared again by certain
556        *tif implementations */
557     if (defaultName != NULL) {
558 	Widget nameField = XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT);
559 #ifdef LESSTIF_VERSION
560         /* Workaround for Lesstif bug (0.93.94 and possibly other versions):
561            if a proportional font is used for the text field and text is
562            inserted while the dialog is managed, Lesstif crashes because it
563            tries to access a non-existing selection. By creating a temporary
564            dummy selection, the crash is avoided. */
565         XmTextFieldSetSelection(nameField, 0, 1, CurrentTime);
566 	XmTextInsert(nameField, XmTextGetLastPosition(nameField), defaultName);
567         XmTextFieldSetSelection(nameField, 0, 0, CurrentTime);
568 #else
569 	XmTextInsert(nameField, XmTextGetLastPosition(nameField), defaultName);
570 #endif
571     }
572 
573     while (!done_with_dialog)
574         XtAppProcessEvent (XtWidgetToApplicationContext(newFileSB), XtIMAll);
575 
576     if (SelectResult == GFN_OK) {
577 	/* See note in existing file routines about freeing the values
578 	   obtained in the following call */
579 	XtVaGetValues(newFileSB, XmNdirSpec, &cFileString, XmNdirectory,
580 		&cDir, XmNpattern, &cPattern, NULL);
581 	if (DefaultDirectory != NULL) XmStringFree(DefaultDirectory);
582 	if (DefaultPattern != NULL) XmStringFree(DefaultPattern);
583 	DefaultDirectory = cDir;
584 	DefaultPattern = cPattern;
585 	XmStringGetLtoR(cFileString, XmSTRING_DEFAULT_CHARSET, &fileString);
586 	/* See note in existing file routines about Motif 2.x bug. */
587 #ifdef VMS
588 	/* VMS  won't return `/' as the 1st character of the full file spec.
589 	 `:' terminates the device name and is not allowed elsewhere */
590 	if (strchr(fileString, ':') != NULL) {
591 #else
592 	if (fileString[0] == '/') {
593 #endif /* VMS */
594 	    /* The directory name is already present in the file name or
595 	       the user entered a full path name. */
596 	    strcpy(filename, fileString);
597 	} else {
598 	    /* Concatenate the directory name and the file name */
599    	    XmStringGetLtoR(cDir, XmSTRING_DEFAULT_CHARSET, &dirString);
600 	    strcpy(filename, dirString);
601 	    strcat(filename, fileString);
602   	    NEditFree(dirString);
603 	}
604 	XmStringFree(cFileString);
605 	NEditFree(fileString);
606     }
607     XtDestroyWidget(newFileSB);
608     return SelectResult;
609 }
610 
611 /*
612 ** Return current default directory used by GetExistingFilename.
613 ** Can return NULL if no default directory has been set (meaning
614 ** use the application's current working directory) String must
615 ** be freed by the caller using NEditFree.
616 */
617 char *GetFileDialogDefaultDirectory(void)
618 {
619     char *string;
620 
621     if (DefaultDirectory == NULL)
622     	return NULL;
623     XmStringGetLtoR(DefaultDirectory, XmSTRING_DEFAULT_CHARSET, &string);
624     return string;
625 }
626 
627 /*
628 ** Return current default match pattern used by GetExistingFilename.
629 ** Can return NULL if no default pattern has been set (meaning use
630 ** a pattern matching all files in the directory) String must be
631 ** freed by the caller using NEditFree.
632 */
633 char *GetFileDialogDefaultPattern(void)
634 {
635     char *string;
636 
637     if (DefaultPattern == NULL)
638     	return NULL;
639     XmStringGetLtoR(DefaultPattern, XmSTRING_DEFAULT_CHARSET, &string);
640     return string;
641 }
642 
643 /*
644 ** Set the current default directory to be used by GetExistingFilename.
645 ** "dir" can be passed as NULL to clear the current default directory
646 ** and use the application's working directory instead.
647 */
648 void SetFileDialogDefaultDirectory(char *dir)
649 {
650     if (DefaultDirectory != NULL)
651     	XmStringFree(DefaultDirectory);
652     DefaultDirectory = dir==NULL ? NULL : XmStringCreateSimple(dir);
653 }
654 
655 /*
656 ** Set the current default match pattern to be used by GetExistingFilename.
657 ** "pattern" can be passed as NULL as the equivalent a pattern matching
658 ** all files in the directory.
659 */
660 void SetFileDialogDefaultPattern(char *pattern)
661 {
662     if (DefaultPattern != NULL)
663     	XmStringFree(DefaultPattern);
664     DefaultPattern = pattern==NULL ? NULL : XmStringCreateSimple(pattern);
665 }
666 
667 /*
668 ** Turn on or off the text fiend in the GetExistingFilename file selection
669 ** box, where users can enter the filename by typing.  This is redundant
670 ** with typing in the list, and leads users who are new to nedit to miss
671 ** the more powerful feature in favor of changing the focus and typing
672 ** in the text field.
673 */
674 void SetGetEFTextFieldRemoval(int state)
675 {
676     RemoveRedundantTextField = state;
677 }
678 
679 /*
680 ** createYesNoDialog, createErrorDialog, doYesNoDialog, doErrorDialog
681 **
682 ** Error Messages and question dialogs to be used with the file selection
683 ** box.  Due to a crash bug in Motif 1.1.1 thru (at least) 1.1.5
684 ** getfiles can not use DialogF.  According to OSF, there is an error
685 ** in the creation of pushButtonGadgets involving the creation and
686 ** destruction of some sort of temporary object.  These routines create
687 ** the dialogs along with the file selection dialog and manage them
688 ** to display messages.  This somehow avoids the problem
689 */
690 static void createYesNoDialog(Widget parent)
691 {
692     XmString  buttonString;	      /* compound string for dialog buttons */
693     int       n;                      /* number of arguments               */
694     Arg       args[MAX_ARGS];	      /* arg list                          */
695 
696     n = 0;
697     XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
698     XtSetArg(args[n], XmNtitle, " "); n++;
699     YesNoDialog = CreateQuestionDialog(parent, "yesNo", args, n);
700     XtAddCallback (YesNoDialog, XmNokCallback, (XtCallbackProc)yesNoOKCB, NULL);
701     XtAddCallback (YesNoDialog, XmNcancelCallback,
702     	    (XtCallbackProc)yesNoCancelCB, NULL);
703     XtUnmanageChild(XmMessageBoxGetChild (YesNoDialog, XmDIALOG_HELP_BUTTON));
704     buttonString = XmStringCreateSimple("Yes");
705     SET_ONE_RSRC(YesNoDialog, XmNokLabelString, buttonString);
706     XmStringFree(buttonString);
707     buttonString = XmStringCreateSimple("No");
708     SET_ONE_RSRC(YesNoDialog, XmNcancelLabelString, buttonString);
709     XmStringFree(buttonString);
710 }
711 
712 static void createErrorDialog(Widget parent)
713 {
714     XmString  buttonString;	      /* compound string for dialog button */
715     int       n;                      /* number of arguments               */
716     Arg       args[MAX_ARGS];	      /* arg list                          */
717 
718     n = 0;
719     XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
720     XtSetArg(args[n], XmNtitle, " "); n++;
721     ErrorDialog = CreateErrorDialog(parent, "error", args, n);
722     XtAddCallback(ErrorDialog, XmNcancelCallback, (XtCallbackProc)errorOKCB,
723     	    NULL);
724     XtUnmanageChild(XmMessageBoxGetChild(ErrorDialog, XmDIALOG_OK_BUTTON));
725     XtUnmanageChild(XmMessageBoxGetChild(ErrorDialog, XmDIALOG_HELP_BUTTON));
726     buttonString = XmStringCreateLtoR("OK", XmSTRING_DEFAULT_CHARSET);
727     XtVaSetValues(ErrorDialog, XmNcancelLabelString, buttonString, NULL);
728     XtVaSetValues(XmMessageBoxGetChild(ErrorDialog, XmDIALOG_CANCEL_BUTTON),
729             XmNmarginWidth, BUTTON_WIDTH_MARGIN,
730             NULL);
731     XmStringFree(buttonString);
732 }
733 
734 static int doYesNoDialog(const char *filename)
735 {
736     char string[255];
737     XmString mString;
738 
739     YesNoResult = ynNone;
740 
741     sprintf(string, "File %s already exists,\nOk to overwrite?", filename);
742     mString = XmStringCreateLtoR(string, XmSTRING_DEFAULT_CHARSET);
743 
744     SET_ONE_RSRC(YesNoDialog, XmNmessageString, mString);
745     XmStringFree(mString);
746     ManageDialogCenteredOnPointer(YesNoDialog);
747 
748     while (YesNoResult == ynNone)
749 	XtAppProcessEvent(XtWidgetToApplicationContext(YesNoDialog), XtIMAll);
750 
751     XtUnmanageChild(YesNoDialog);
752 
753     /* Nasty motif bug here, patched around by waiting for a ReparentNotify
754        event (with timeout) before allowing file selection dialog to pop
755        down.  If this routine returns too quickly, and the file selection
756        dialog (and thereby, this dialog as well) are destroyed while X
757        is still sorting through the events generated by the pop-down,
758        something bad happens and we get a crash */
759     if (YesNoResult == ynYes)
760     	PopDownBugPatch(YesNoDialog);
761 
762     return YesNoResult == ynYes;
763 }
764 
765 static void doErrorDialog(const char *errorString, const char *filename)
766 {
767     char string[255];
768     XmString mString;
769 
770     ErrorDone = False;
771 
772     sprintf(string, errorString, filename);
773     mString = XmStringCreateLtoR(string, XmSTRING_DEFAULT_CHARSET);
774 
775     SET_ONE_RSRC(ErrorDialog, XmNmessageString, mString);
776     XmStringFree(mString);
777     ManageDialogCenteredOnPointer(ErrorDialog);
778 
779     while (!ErrorDone)
780 	XtAppProcessEvent (XtWidgetToApplicationContext(ErrorDialog), XtIMAll);
781 
782     XtUnmanageChild(ErrorDialog);
783 }
784 
785 static void newFileOKCB(Widget	w, Boolean *client_data,
786                  XmFileSelectionBoxCallbackStruct *call_data)
787 
788 {
789     char *filename;                   /* name of chosen file             */
790     int  fd;                          /* file descriptor                 */
791     int  length;		      /* length of file name		 */
792     int  response;		      /* response to dialog		 */
793     struct stat buf;		      /* status from fstat		 */
794 
795     XmStringGetLtoR(call_data->value, XmSTRING_DEFAULT_CHARSET, &filename);
796     SelectResult = GFN_OK;
797     length = strlen(filename);
798     if (length == 0 || filename[length-1] == '/') {
799     	doErrorDialog("Please supply a name for the file", NULL);
800 	NEditFree(filename);
801     	return;
802     }
803 
804 #ifdef VMS
805     if (strchr(filename,';') && (fd = open(filename, O_RDONLY, 0)) != -1) {
806 #else  /* not VMS*/
807     if ((fd = open(filename, O_RDONLY, 0)) != -1) {     /* exists */
808 #endif /*VMS*/
809 	fstat(fd, &buf);
810         close(fd);
811 	if (buf.st_mode & S_IFDIR) {
812             doErrorDialog("Error: %s is a directory", filename);
813 	    NEditFree(filename);
814 	    return;
815 	}
816         response = doYesNoDialog(filename);
817 #ifdef VMS
818 	if (response) {
819             if (access(filename, 2) != 0) { /* have write/delete access? */
820 		doErrorDialog("Error: can't overwrite %s ", filename);
821 		NEditFree(filename);
822 		return;
823 	    }
824 	} else {
825 #else
826         if (!response) {
827 #endif /*VMS*/
828              return;
829 	}
830     } else {
831 	if ((fd = creat(filename, PERMS)) == -1) {
832             doErrorDialog("Error: can't create %s ", filename);
833 	    NEditFree(filename);
834 	    return;
835 	} else {
836 	    close(fd);
837             remove(filename);
838 	}
839     }
840     NEditFree(filename);
841     *client_data = True;			    /* done with dialog */
842 }
843 
844 
845 static void newFileCancelCB(Widget w, Boolean *client_data, caddr_t call_data)
846 {
847     SelectResult = GFN_CANCEL;
848     *client_data = True;
849 }
850 
851 static void newHelpCB(Widget w, Widget helpPanel, caddr_t call_data)
852 {
853     ManageDialogCenteredOnPointer(helpPanel);
854 }
855 
856 static void existOkCB(Widget w, Boolean * client_data,
857 	       XmFileSelectionBoxCallbackStruct *call_data)
858 {
859     char *filename;                   /* name of chosen file             */
860     int  fd;                          /* file descriptor                 */
861     int  length;		      /* length of file name		 */
862 
863     XmStringGetLtoR(call_data->value, XmSTRING_DEFAULT_CHARSET, &filename);
864     SelectResult = GFN_OK;
865     length = strlen(filename);
866     if (length == 0 || filename[length-1] == '/') {
867     	doErrorDialog("Please select a file to open", NULL);
868     	NEditFree(filename);
869     	return;
870     } else    if ((fd = open(filename, O_RDONLY,0))  == -1) {
871     	doErrorDialog("Error: can't open %s ", filename);
872     	NEditFree(filename);
873         return;
874     } else
875     	close(fd);
876     NEditFree(filename);
877 
878     *client_data = True;		/* done with dialog		*/
879 }
880 
881 
882 static void existCancelCB(Widget w, Boolean * client_data, caddr_t call_data)
883 {
884     SelectResult = GFN_CANCEL;
885     *client_data = True;		/* done with dialog		*/
886 }
887 
888 static void yesNoOKCB(Widget w, caddr_t client_data, caddr_t call_data)
889 {
890     YesNoResult = ynYes;
891 }
892 
893 static void existHelpCB(Widget w, Widget helpPanel, caddr_t call_data)
894 {
895     ManageDialogCenteredOnPointer(helpPanel);
896 }
897 
898 static void errorOKCB(Widget w, caddr_t client_data, caddr_t call_data)
899 {
900     ErrorDone = True;
901 }
902 
903 static void yesNoCancelCB(Widget w, caddr_t client_data, caddr_t call_data)
904 {
905     YesNoResult = ynNo;
906 }
907 
908 static Widget createPanelHelp(Widget parent, const char *helpText, const char *title)
909 {
910     Arg al[20];
911     int ac;
912     Widget form, text, button;
913     XmString st1;
914 
915     ac = 0;
916     form = CreateFormDialog(parent, "helpForm", al, ac);
917 
918     ac = 0;
919     XtSetArg (al[ac], XmNbottomAttachment, XmATTACH_FORM);  ac++;
920     XtSetArg (al[ac], XmNtopAttachment, XmATTACH_NONE);  ac++;
921     XtSetArg(al[ac], XmNlabelString, st1=XmStringCreateLtoR ("OK",
922                       XmSTRING_DEFAULT_CHARSET)); ac++;
923     XtSetArg (al[ac], XmNmarginWidth, BUTTON_WIDTH_MARGIN);  ac++;
924     button = XmCreatePushButtonGadget(form, "ok", al, ac);
925     XtAddCallback(button, XmNactivateCallback, (XtCallbackProc)helpDismissCB,
926     	    (char *)form);
927     XmStringFree(st1);
928     XtManageChild(button);
929     SET_ONE_RSRC(form, XmNdefaultButton, button);
930 
931     ac = 0;
932     XtSetArg(al[ac], XmNrows, 15);  ac++;
933     XtSetArg(al[ac], XmNcolumns, 60);  ac++;
934     XtSetArg(al[ac], XmNresizeHeight, False);  ac++;
935     XtSetArg(al[ac], XmNtraversalOn, False); ac++;
936     XtSetArg(al[ac], XmNwordWrap, True);  ac++;
937     XtSetArg(al[ac], XmNscrollHorizontal, False);  ac++;
938     XtSetArg(al[ac], XmNeditMode, XmMULTI_LINE_EDIT);  ac++;
939     XtSetArg(al[ac], XmNeditable, False);  ac++;
940     XtSetArg(al[ac], XmNvalue, helpText);  ac++;
941     XtSetArg(al[ac], XmNtopAttachment, XmATTACH_FORM);  ac++;
942     XtSetArg(al[ac], XmNleftAttachment, XmATTACH_FORM);  ac++;
943     XtSetArg(al[ac], XmNbottomAttachment, XmATTACH_WIDGET);  ac++;
944     XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM);  ac++;
945     XtSetArg(al[ac], XmNbottomWidget, button);  ac++;
946     text = XmCreateScrolledText(form, "helpText", al, ac);
947     AddMouseWheelSupport(text);
948     XtManageChild(text);
949 
950     SET_ONE_RSRC(XtParent(form), XmNtitle, title);
951 
952     return form;
953 }
954 
955 static void helpDismissCB(Widget w, Widget helpPanel, caddr_t call_data)
956 {
957     XtUnmanageChild(helpPanel);
958 }
959 
960 /*
961 ** Add ability for user to type filenames to a list widget
962 */
963 static void makeListTypeable(Widget listW)
964 {
965     XtAddEventHandler(listW, KeyPressMask, False, listCharEH, NULL);
966 }
967 
968 /*
969 ** Action procedure for processing characters typed in a list, finds the
970 ** first item matching the characters typed so far.
971 */
972 static int nKeystrokes = 0; /* Global key stroke history counter */
973 static void listCharEH(Widget w, XtPointer callData, XEvent *event,
974 	Boolean *continueDispatch)
975 {
976     char charString[5], c, *itemString;
977     int nChars, nItems, i, cmp, selectPos, topPos, nVisible;
978     XmString *items;
979     KeySym kSym;
980     char name[MAXPATHLEN], path[MAXPATHLEN];
981     static char keystrokes[MAX_LIST_KEYSTROKES];
982     static Time lastKeyTime = 0;
983 
984     /* Get the ascii character code represented by the event */
985     nChars = XLookupString((XKeyEvent *)event, charString, sizeof(charString),
986     	    &kSym, NULL);
987     c = charString[0];
988 
989     /* Process selected control keys, but otherwise ignore the keystroke
990        if it isn't a single printable ascii character */
991     *continueDispatch = False;
992     if (kSym==XK_BackSpace || kSym==XK_Delete) {
993     	nKeystrokes = nKeystrokes > 0 ? nKeystrokes-1 : 0;
994     	return;
995     } else if (kSym==XK_Clear || kSym==XK_Cancel || kSym==XK_Break) {
996     	nKeystrokes = 0;
997     	return;
998     } else if (nChars!=1 || c<0x021 || c>0x07e) {
999     	*continueDispatch = True;
1000     	return;
1001     }
1002 
1003     /* Throw out keystrokes and start keystroke accumulation over from
1004        scratch if user waits more than MAX_LIST_KESTROKE_WAIT milliseconds */
1005     if (((XKeyEvent *)event)->time - lastKeyTime > MAX_LIST_KESTROKE_WAIT)
1006     	nKeystrokes = 0;
1007     lastKeyTime = ((XKeyEvent *)event)->time;
1008 
1009     /* Accumulate the current keystroke, just beep if there are too many */
1010     if (nKeystrokes >= MAX_LIST_KEYSTROKES)
1011     	XBell(XtDisplay(w), 0);
1012     else
1013 #ifdef VMS
1014     	keystrokes[nKeystrokes++] = toupper(c);
1015 #else
1016     	keystrokes[nKeystrokes++] = c;
1017 #endif
1018 
1019     /* Get the items (filenames) in the list widget */
1020     XtVaGetValues(w, XmNitems, &items, XmNitemCount, &nItems, NULL);
1021 
1022     /* compare them with the accumulated user keystrokes & decide the
1023        appropriate line in the list widget to select */
1024     selectPos = 0;
1025     for (i=0; i<nItems; i++) {
1026     	XmStringGetLtoR(items[i], XmSTRING_DEFAULT_CHARSET, &itemString);
1027     	if (ParseFilename(itemString, name, path) != 0) {
1028 	   NEditFree(itemString);
1029 	   return;
1030 	}
1031 	NEditFree(itemString);
1032     	cmp = strncmp(name, keystrokes, nKeystrokes);
1033     	if (cmp == 0) {
1034     	    selectPos = i+1;
1035     	    break;
1036     	} else if (cmp > 0) {
1037     	    selectPos = i;
1038     	    break;
1039     	}
1040     }
1041 
1042     /* Make the selection, and make sure it will be visible */
1043     XmListSelectPos(w, selectPos, True);
1044     if (selectPos == 0) /* XmListSelectPos curiously returns 0 for last item */
1045     	selectPos = nItems + 1;
1046     XtVaGetValues(w, XmNtopItemPosition, &topPos,
1047     	    XmNvisibleItemCount, &nVisible, NULL);
1048     if (selectPos < topPos)
1049     	XmListSetPos(w, selectPos-2 > 1 ? selectPos-2 : 1);
1050     else if (selectPos > topPos+nVisible-1)
1051     	XmListSetBottomPos(w, selectPos+2 <= nItems ? selectPos+2 : 0);
1052     /* For LessTif 0.89.9. Obsolete now? */
1053     XmListSelectPos(w, selectPos, True);
1054 }
1055 
1056 /*
1057 ** Replacement directory and file search procedures for the file selection
1058 ** box to re-sort the items in a standard order.  This is a patch, and not
1059 ** a very good one, for the problem that in some Motif versions, the directory
1060 ** list is sorted differently, such that typing of filenames fails because
1061 ** it expects strcmp alphabetical order, as opposed to strcasecmp.  Most
1062 ** users prefer the old ordering, which is what this enforces, but if
1063 ** ifdefs can be found that will correctly predict the ordering and adjust
1064 ** listCharEH above, instead of resorting to re-sorting, it should be done.
1065 ** This obviously wastes valuable time as the selection box is popping up
1066 ** and should be removed.  These routines also leak memory like a seive,
1067 ** because Motif's inconsistent treatment of memory in list widgets does
1068 ** not allow us to free lists that we pass in, and most Motif versions
1069 ** don't clean it up properly.
1070 */
1071 static void replacementDirSearchProc(Widget w, XtPointer searchData)
1072 {
1073     Boolean updated;
1074 
1075     /* Call the original search procedure to do the actual search */
1076     (*OrigDirSearchProc)(w, searchData);
1077     /* Refreshing a list clears the keystroke history, even if no update. */
1078     nKeystrokes = 0;
1079     XtVaGetValues(w, XmNlistUpdated, &updated, NULL);
1080     if (!updated)
1081     	return;
1082 
1083     /* Sort the items in the list */
1084     sortWidgetList(XmFileSelectionBoxGetChild(w, XmDIALOG_DIR_LIST));
1085 }
1086 
1087 static void replacementFileSearchProc(Widget w, XtPointer searchData)
1088 {
1089     Boolean updated;
1090 
1091     /* Call the original search procedure to do the actual search */
1092     (*OrigFileSearchProc)(w, searchData);
1093     /* Refreshing a list clears the keystroke history, even if no update. */
1094     nKeystrokes = 0;
1095     XtVaGetValues(w, XmNlistUpdated, &updated, NULL);
1096     if (!updated)
1097     	return;
1098 
1099     /* Sort the items in the list */
1100     sortWidgetList(XmFileSelectionBoxGetChild(w, XmDIALOG_LIST));
1101 }
1102 
1103 /*
1104 ** Sort the items in a list widget "listWidget"
1105 */
1106 static void sortWidgetList(Widget listWidget)
1107 {
1108     XmString *items, *sortedItems;
1109     int nItems, i;
1110 
1111     /* OpenMotif 2.3 will crash if we try to replace the items, when they
1112        are selected. This function is only called when we refresh the
1113        contents anyway. */
1114     XmListDeselectAllItems(listWidget);
1115     XtVaGetValues(listWidget, XmNitems, &items, XmNitemCount, &nItems, NULL);
1116     sortedItems = (XmString *)NEditMalloc(sizeof(XmString) * nItems);
1117     for (i=0; i<nItems; i++)
1118     	sortedItems[i] = XmStringCopy(items[i]);
1119     qsort(sortedItems, nItems, sizeof(XmString), compareXmStrings);
1120     XmListReplaceItemsPos(listWidget, sortedItems, nItems, 1);
1121     for (i=0; i<nItems; i++)
1122     	XmStringFree(sortedItems[i]);
1123     NEditFree((char *)sortedItems);
1124 }
1125 
1126 /*
1127 ** Compare procedure for qsort for sorting a list of XmStrings
1128 */
1129 static int compareXmStrings(const void *string1, const void *string2)
1130 {
1131     char *s1, *s2;
1132     int result;
1133 
1134     XmStringGetLtoR(*(XmString *)string1, XmSTRING_DEFAULT_CHARSET, &s1);
1135     XmStringGetLtoR(*(XmString *)string2, XmSTRING_DEFAULT_CHARSET, &s2);
1136     result = strcmp(s1, s2);
1137     NEditFree(s1);
1138     NEditFree(s2);
1139     return result;
1140 }
1141