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