Fix for crash in shared tag allocation on Solaris (compiler bug?)
[nedit.git] / util / getfiles.c
blob5231149c200c8410eb263f84f699a3155b07aebe
1 static const char CVSID[] = "$Id: getfiles.c,v 1.15 2001/08/14 08:37:16 jlous 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 labelString; /* compound string for prompt label */
269 XmString titleString; /* compound string for dialog title */
271 n = 0;
272 labelString = XmStringCreateSimple(promptString);
273 titleString = XmStringCreateSimple(" ");
274 XtSetArg(args[n], XmNlistLabelString, labelString); n++;
275 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
276 XtSetArg(args[n], XmNdialogTitle, titleString); n++;
277 XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
278 existFileSB = CreateFileSelectionDialog(parent,"FileSelect",args,n);
279 XmStringFree(labelString);
280 XmStringFree(titleString);
281 #ifndef SGI_CUSTOM
282 if (RemoveRedundantTextField)
283 XtUnmanageChild(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_TEXT));
284 XtUnmanageChild(XmFileSelectionBoxGetChild(existFileSB,
285 XmDIALOG_SELECTION_LABEL));
286 XtVaSetValues(XmFileSelectionBoxGetChild(existFileSB,
287 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
288 XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_FILTER_TEXT), NULL);
289 XtVaSetValues(XmFileSelectionBoxGetChild(existFileSB,
290 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
291 XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_DIR_LIST), NULL);
292 XtVaSetValues(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST_LABEL),
293 XmNmnemonic, promptString[strspn(promptString, "lD")], XmNuserData,
294 XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST), NULL);
295 AddDialogMnemonicHandler(existFileSB, FALSE);
296 RemapDeleteKey(XmFileSelectionBoxGetChild(existFileSB,
297 XmDIALOG_FILTER_TEXT));
298 RemapDeleteKey(XmFileSelectionBoxGetChild(existFileSB,
299 XmDIALOG_TEXT));
300 #endif
301 return HandleCustomExistFileSB(existFileSB, filename);
305 ** HandleCustomExistFileSB
307 ** Manage a customized file selection box for opening existing files.
308 ** Use this if you want to change the standard file selection dialog
309 ** from the defaults provided in GetExistingFilename, but still
310 ** want take advantage of the button processing, help messages, and
311 ** file checking of GetExistingFilename.
313 ** Arguments:
315 ** Widget existFileSB - your custom file selection box widget id
316 ** char * filename - a string to receive the selected filename
317 ** (this string will not be altered if the
318 ** user pressed the cancel button)
320 ** Returns: GFN_OK - file was selected and OK button pressed
321 ** GFN_CANCEL - Cancel button pressed and no returned file
324 int HandleCustomExistFileSB(Widget existFileSB, char *filename)
326 Boolean done_with_dialog=False; /* ok to destroy dialog flag */
327 char *fileString; /* C string for file selected */
328 char *dirString; /* C string for dir of file selected */
329 XmString cFileString; /* compound string for file selected */
330 XmString cDir; /* compound directory selected */
331 XmString cPattern; /* compound filter pattern */
332 Widget help; /* help window form dialog */
333 #if XmVersion < 1002
334 int i;
335 #endif
337 XtAddCallback(existFileSB, XmNokCallback, (XtCallbackProc)existOkCB,
338 &done_with_dialog);
339 XtAddCallback(existFileSB, XmNcancelCallback, (XtCallbackProc)existCancelCB,
340 &done_with_dialog);
341 AddMotifCloseCallback(XtParent(existFileSB), (XtCallbackProc)existCancelCB,
342 &done_with_dialog);
343 help = createPanelHelp(existFileSB, HelpExist, "Selecting Files to Open");
344 createErrorDialog(existFileSB);
345 XtAddCallback(existFileSB, XmNhelpCallback, (XtCallbackProc)existHelpCB,
346 (char *)help);
347 if (DefaultDirectory != NULL || DefaultPattern != NULL)
348 XtVaSetValues(existFileSB, XmNdirectory, DefaultDirectory,
349 XmNpattern, DefaultPattern, NULL);
350 #ifndef SGI_CUSTOM
351 makeListTypeable(XmFileSelectionBoxGetChild(existFileSB,XmDIALOG_LIST));
352 makeListTypeable(XmFileSelectionBoxGetChild(existFileSB,XmDIALOG_DIR_LIST));
353 #if XmVersion >= 1002
354 XtVaSetValues(existFileSB, XmNinitialFocus, XtParent(
355 XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST)), NULL);
356 #endif
357 #endif
358 ManageDialogCenteredOnPointer(existFileSB);
360 #ifndef SGI_CUSTOM
361 /* Typing in the directory list is dependent on the list being in the
362 same form of alphabetical order expected by the character processing
363 routines. As of about 1.2.3, some Motif libraries seem to have a
364 different idea of ordering than is usual for Unix directories.
365 To sort them properly, we have to patch the directory and file
366 searching routines to re-sort the lists when they change */
367 XtVaGetValues(existFileSB, XmNdirSearchProc, &OrigDirSearchProc,
368 XmNfileSearchProc, &OrigFileSearchProc, NULL);
369 XtVaSetValues(existFileSB, XmNdirSearchProc, replacementDirSearchProc,
370 XmNfileSearchProc, replacementFileSearchProc, NULL);
371 sortWidgetList(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_DIR_LIST));
372 sortWidgetList(XmFileSelectionBoxGetChild(existFileSB, XmDIALOG_LIST));
373 #if XmVersion < 1002
374 /* To give file list initial focus, revoke default button status for
375 the "OK" button. Dynamic defaulting will restore it as the default
376 button after the keyboard focus is established. Note the voodoo
377 below: calling XmProcess traversal extra times (a recommendation from
378 OSF technical support) somehow succeedes in giving the file list focus */
379 XtVaSetValues(existFileSB, XmNdefaultButton, NULL, NULL);
380 for (i=1; i<30; i++)
381 XmProcessTraversal(XmFileSelectionBoxGetChild(existFileSB,
382 XmDIALOG_LIST), XmTRAVERSE_CURRENT);
383 #endif
384 #endif /* SGI_CUSTOM */
386 while (!done_with_dialog)
387 XtAppProcessEvent(XtWidgetToApplicationContext(existFileSB), XtIMAll);
389 if (SelectResult == GFN_OK) {
390 XtVaGetValues(existFileSB, XmNdirSpec, &cFileString, XmNdirectory,
391 &cDir, XmNpattern, &cPattern, NULL);
392 /* Undocumented: file selection box widget allocates copies of these
393 strings on getValues calls. I have risked freeing them to avoid
394 memory leaks, since I assume other developers have made this same
395 realization, therefore OSF can't easily go back and change it */
396 if (DefaultDirectory != NULL) XmStringFree(DefaultDirectory);
397 if (DefaultPattern != NULL) XmStringFree(DefaultPattern);
398 DefaultDirectory = cDir;
399 DefaultPattern = cPattern;
400 XmStringGetLtoR(cFileString, XmSTRING_DEFAULT_CHARSET, &fileString);
401 /* Motif 2.x seem to contain a bug that causes it to return only
402 the relative name of the file in XmNdirSpec when XmNpathMode is set
403 to XmPATH_MODE_RELATIVE (through X resources), although the man
404 page states that it always returns the full path name. We can
405 easily work around this by checking that the first character of the
406 file name is a `/'. */
407 #ifdef VMS
408 /* VMS won't return `/' as the 1st character of the full file spec.
409 `:' terminates the device name and is not allowed elsewhere */
410 if (strchr(fileString, ':') != NULL) {
411 #else
412 if (fileString[0] == '/') {
413 #endif /* VMS */
414 /* The directory name is already present in the file name or
415 the user entered a full path name. */
416 strcpy(filename, fileString);
417 } else {
418 /* Concatenate the directory name and the file name */
419 XmStringGetLtoR(cDir, XmSTRING_DEFAULT_CHARSET, &dirString);
420 strcpy(filename, dirString);
421 strcat(filename, fileString);
422 XtFree(dirString);
424 XmStringFree(cFileString);
425 XtFree(fileString);
427 XtDestroyWidget(existFileSB);
428 return SelectResult;
432 /* GetNewFilename */
433 /* */
434 /* This routine will popup a file selection box so that the user can */
435 /* select a file that will be, at a later time in the application, */
436 /* created and written to. After the user selects a file, GetNewFilename */
437 /* checks whether the file already exists, and if it does, asks the user */
438 /* if he/she wants to overwrite the file. Answering no, allows the user */
439 /* to select a new filename. GetNewFilename also checks that the file */
440 /* name specified by the user can be created, and allows re-entry if not. */
441 /* When the user presses the OK button to a filename satisying the above */
442 /* criteria, GetNewFilename returns the selected filename and GFN_OK. */
443 /* If the user presses the cancel button, the return value is GFN_CANCEL, */
444 /* and the filename character string supplied in the call is not altered. */
445 /* */
446 /* Arguments: */
447 /* */
448 /* Widget parent - parent widget id */
449 /* char * promptString - prompt string */
450 /* char * filename - a string to receive the selected filename */
451 /* (this string will not be altered if the */
452 /* user pressed the cancel button) */
453 /* */
454 /* Returns: GFN_OK - file was selected and OK button pressed */
455 /* GFN_CANCEL - Cancel button pressed and no returned file */
457 int GetNewFilename (Widget parent, char *promptString, char *filename)
459 int n; /* number of arguments */
460 Arg args[MAX_ARGS]; /* arg list */
461 XmString labelString; /* compound string for prompt label */
462 XmString titleString; /* compound string for dialog title */
463 Widget newFileSB; /* widget file select box for */
465 n = 0;
466 labelString = XmStringCreateLtoR (promptString,
467 XmSTRING_DEFAULT_CHARSET);
468 titleString = XmStringCreateLtoR (" ", XmSTRING_DEFAULT_CHARSET);
469 XtSetArg(args[n], XmNselectionLabelString, labelString); n++;
470 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
471 XtSetArg(args[n], XmNdialogTitle, titleString); n++;
472 XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
473 newFileSB=CreateFileSelectionDialog(parent,"FileSelect",args,n);
474 XmStringFree(labelString);
475 XmStringFree(titleString);
476 XtVaSetValues(XmFileSelectionBoxGetChild(newFileSB,
477 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
478 XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_FILTER_TEXT), NULL);
479 XtVaSetValues(XmFileSelectionBoxGetChild(newFileSB,
480 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
481 XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_DIR_LIST), NULL);
482 XtVaSetValues(XmFileSelectionBoxGetChild(newFileSB,
483 XmDIALOG_LIST_LABEL), XmNmnemonic, 'F', XmNuserData,
484 XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_LIST), NULL);
485 XtVaSetValues(XmFileSelectionBoxGetChild(newFileSB,
486 XmDIALOG_SELECTION_LABEL), XmNmnemonic,
487 promptString[strspn(promptString, "lFD")], XmNuserData,
488 XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT), NULL);
489 AddDialogMnemonicHandler(newFileSB, FALSE);
490 RemapDeleteKey(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_FILTER_TEXT));
491 RemapDeleteKey(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT));
492 return HandleCustomNewFileSB(newFileSB, filename, NULL);
496 ** HandleCustomNewFileSB
498 ** Manage a customized file selection box for opening new files.
499 ** Use this if you want to change the standard file selection dialog
500 ** from the defaults provided in GetNewFilename, but still
501 ** want take advantage of the button processing, help messages, and
502 ** file checking of GetExistingFilename.
504 ** Arguments:
506 ** Widget newFileSB - your custom file selection box widget id
507 ** char * filename - a string to receive the selected filename
508 ** (this string will not be altered if the
509 ** user pressed the cancel button)
510 ** char* defaultName - default name to be pre-entered in filename
511 ** text field.
513 ** Returns: GFN_OK - file was selected and OK button pressed
514 ** GFN_CANCEL - Cancel button pressed and no returned file
517 int HandleCustomNewFileSB(Widget newFileSB, char *filename, char *defaultName)
519 Boolean done_with_dialog=False; /* ok to destroy dialog flag */
520 Widget help; /* help window form dialog */
521 XmString cFileString; /* compound string for file selected */
522 XmString cDir; /* compound directory selected */
523 XmString cPattern; /* compound filter pattern */
524 char *fileString; /* C string for file selected */
525 char *dirString; /* C string for dir of file selected */
526 #if XmVersion < 1002
527 int i;
528 #endif
530 XtAddCallback(newFileSB, XmNokCallback, (XtCallbackProc)newFileOKCB,
531 &done_with_dialog);
532 XtAddCallback(newFileSB, XmNcancelCallback, (XtCallbackProc)newFileCancelCB,
533 &done_with_dialog);
535 #ifndef SGI_CUSTOM
536 makeListTypeable(XmFileSelectionBoxGetChild(newFileSB,XmDIALOG_LIST));
537 makeListTypeable(XmFileSelectionBoxGetChild(newFileSB,XmDIALOG_DIR_LIST));
538 #endif
539 if (DefaultDirectory != NULL || DefaultPattern != NULL)
540 XtVaSetValues(newFileSB, XmNdirectory, DefaultDirectory,
541 XmNpattern, DefaultPattern, NULL);
542 help = createPanelHelp(newFileSB, HelpNew, "Saving a File");
543 createYesNoDialog(newFileSB);
544 createErrorDialog(newFileSB);
545 XtAddCallback(newFileSB, XmNhelpCallback, (XtCallbackProc)newHelpCB,
546 (char *)help);
547 if (defaultName != NULL) {
548 Widget nameField = XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT);
549 XmTextInsert(nameField, XmTextGetLastPosition(nameField), defaultName);
551 #if XmVersion >= 1002
552 #ifndef SGI_CUSTOM
553 XtVaSetValues(newFileSB, XmNinitialFocus,
554 XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT), NULL);
555 #else /* SGI_CUSTOM */
556 { Widget finder = XmFileSelectionBoxGetChild(newFileSB, SgDIALOG_FINDER);
557 if ( finder != NULL )
558 XtVaSetValues(newFileSB, XmNinitialFocus, finder, NULL);
560 #endif
561 #endif
562 ManageDialogCenteredOnPointer(newFileSB);
564 #ifndef SGI_CUSTOM
565 #if XmVersion < 1002
566 /* To give filename text initial focus, revoke default button status for
567 the "OK" button. Dynamic defaulting will restore it as the default
568 button after the keyboard focus is established. Note the voodoo
569 below: calling XmProcess traversal FOUR times (a recommendation from
570 OSF technical support) somehow succeedes in changing the focus */
571 XtVaSetValues(newFileSB, XmNdefaultButton, NULL, NULL);
572 for (i=1; i<30; i++)
573 XmProcessTraversal(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_TEXT),
574 XmTRAVERSE_CURRENT);
575 #endif
577 /* Typing in the directory list is dependent on the list being in the
578 same form of alphabetical order expected by the character processing
579 routines. As of about 1.2.3, some Motif libraries seem to have a
580 different idea of ordering than is usual for Unix directories.
581 To sort them properly, we have to patch the directory and file
582 searching routines to re-sort the lists when they change */
583 XtVaGetValues(newFileSB, XmNdirSearchProc, &OrigDirSearchProc,
584 XmNfileSearchProc, &OrigFileSearchProc, NULL);
585 XtVaSetValues(newFileSB, XmNdirSearchProc, replacementDirSearchProc,
586 XmNfileSearchProc, replacementFileSearchProc, NULL);
587 sortWidgetList(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_DIR_LIST));
588 sortWidgetList(XmFileSelectionBoxGetChild(newFileSB, XmDIALOG_LIST));
589 #endif /* SGI_CUSTOM */
590 while (!done_with_dialog)
591 XtAppProcessEvent (XtWidgetToApplicationContext(newFileSB), XtIMAll);
593 if (SelectResult == GFN_OK) {
594 /* See note in existing file routines about freeing the values
595 obtained in the following call */
596 XtVaGetValues(newFileSB, XmNdirSpec, &cFileString, XmNdirectory,
597 &cDir, XmNpattern, &cPattern, NULL);
598 if (DefaultDirectory != NULL) XmStringFree(DefaultDirectory);
599 if (DefaultPattern != NULL) XmStringFree(DefaultPattern);
600 DefaultDirectory = cDir;
601 DefaultPattern = cPattern;
602 XmStringGetLtoR(cFileString, XmSTRING_DEFAULT_CHARSET, &fileString);
603 /* See note in existing file routines about Motif 2.x bug. */
604 if (fileString[0] == '/') {
605 /* The directory name is already present in the file name or
606 the user entered a full path name. */
607 strcpy(filename, fileString);
608 } else {
609 /* Concatenate the directory name and the file name */
610 XmStringGetLtoR(cDir, XmSTRING_DEFAULT_CHARSET, &dirString);
611 strcpy(filename, dirString);
612 strcat(filename, fileString);
613 XtFree(dirString);
615 XmStringFree(cFileString);
616 XtFree(fileString);
618 XtDestroyWidget(newFileSB);
619 return SelectResult;
623 ** Return current default directory used by GetExistingFilename and
624 ** GetNewFilename. Can return NULL if no default directory has been set
625 ** (meaning use the application's current working directory) String must
626 ** be freed by the caller using XtFree.
628 char *GetFileDialogDefaultDirectory(void)
630 char *string;
632 if (DefaultDirectory == NULL)
633 return NULL;
634 XmStringGetLtoR(DefaultDirectory, XmSTRING_DEFAULT_CHARSET, &string);
635 return string;
639 ** Return current default match pattern used by GetExistingFilename and
640 ** GetNewFilename. Can return NULL if no default pattern has been set
641 ** (meaning use a pattern matching all files in the directory) String must
642 ** be freed by the caller using XtFree.
644 char *GetFileDialogDefaultPattern(void)
646 char *string;
648 if (DefaultPattern == NULL)
649 return NULL;
650 XmStringGetLtoR(DefaultPattern, XmSTRING_DEFAULT_CHARSET, &string);
651 return string;
655 ** Set the current default directory to be used by GetExistingFilename and
656 ** GetNewFilename. "dir" can be passed as NULL to clear the current default
657 ** directory and use the application's working directory instead.
659 void SetFileDialogDefaultDirectory(char *dir)
661 if (DefaultDirectory != NULL)
662 XmStringFree(DefaultDirectory);
663 DefaultDirectory = dir==NULL ? NULL : XmStringCreateSimple(dir);
667 ** Set the current default match pattern to be used by GetExistingFilename and
668 ** GetNewFilename. "pattern" can be passed as NULL as the equivalent a pattern
669 ** matching all files in the directory.
671 void SetFileDialogDefaultPattern(char *pattern)
673 if (DefaultPattern != NULL)
674 XmStringFree(DefaultPattern);
675 DefaultPattern = pattern==NULL ? NULL : XmStringCreateSimple(pattern);
679 ** Turn on or off the text fiend in the GetExistingFilename file selection
680 ** box, where users can enter the filename by typing. This is redundant
681 ** with typing in the list, and leads users who are new to nedit to miss
682 ** the more powerful feature in favor of changing the focus and typing
683 ** in the text field.
685 void SetGetEFTextFieldRemoval(int state)
687 RemoveRedundantTextField = state;
691 ** createYesNoDialog, createErrorDialog, doYesNoDialog, doErrorDialog
693 ** Error Messages and question dialogs to be used with the file selection
694 ** box. Due to a crash bug in Motif 1.1.1 thru (at least) 1.1.5
695 ** getfiles can not use DialogF. According to OSF, there is an error
696 ** in the creation of pushButtonGadgets involving the creation and
697 ** destruction of some sort of temporary object. These routines create
698 ** the dialogs along with the file selection dialog and manage them
699 ** to display messages. This somehow avoids the problem
701 static void createYesNoDialog(Widget parent)
703 XmString buttonString; /* compound string for dialog buttons */
704 int n; /* number of arguments */
705 Arg args[MAX_ARGS]; /* arg list */
707 n = 0;
708 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
709 XtSetArg(args[n], XmNtitle, " "); n++;
710 YesNoDialog = CreateQuestionDialog(parent, "yesNo", args, n);
711 XtAddCallback (YesNoDialog, XmNokCallback, (XtCallbackProc)yesNoOKCB, NULL);
712 XtAddCallback (YesNoDialog, XmNcancelCallback,
713 (XtCallbackProc)yesNoCancelCB, NULL);
714 XtUnmanageChild(XmMessageBoxGetChild (YesNoDialog, XmDIALOG_HELP_BUTTON));
715 buttonString = XmStringCreateSimple("Yes");
716 SET_ONE_RSRC(YesNoDialog, XmNokLabelString, buttonString);
717 XmStringFree(buttonString);
718 buttonString = XmStringCreateSimple("No");
719 SET_ONE_RSRC(YesNoDialog, XmNcancelLabelString, buttonString);
720 XmStringFree(buttonString);
723 static void createErrorDialog(Widget parent)
725 XmString buttonString; /* compound string for dialog button */
726 int n; /* number of arguments */
727 Arg args[MAX_ARGS]; /* arg list */
729 n = 0;
730 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
731 XtSetArg(args[n], XmNtitle, " "); n++;
732 ErrorDialog = CreateErrorDialog(parent, "error", args, n);
733 XtAddCallback(ErrorDialog, XmNcancelCallback, (XtCallbackProc)errorOKCB,
734 NULL);
735 XtUnmanageChild(XmMessageBoxGetChild(ErrorDialog, XmDIALOG_OK_BUTTON));
736 XtUnmanageChild(XmMessageBoxGetChild(ErrorDialog, XmDIALOG_HELP_BUTTON));
737 buttonString = XmStringCreateLtoR ("Dismiss", XmSTRING_DEFAULT_CHARSET);
738 XtVaSetValues(ErrorDialog, XmNcancelLabelString, buttonString, NULL);
739 XmStringFree(buttonString);
742 static int doYesNoDialog(const char *filename)
744 char string[255];
745 XmString mString;
747 YesNoResult = ynNone;
749 sprintf(string, "File %s already exists,\nOk to overwrite?", filename);
750 mString = XmStringCreateLtoR(string, XmSTRING_DEFAULT_CHARSET);
752 SET_ONE_RSRC(YesNoDialog, XmNmessageString, mString);
753 XmStringFree(mString);
754 ManageDialogCenteredOnPointer(YesNoDialog);
756 while (YesNoResult == ynNone)
757 XtAppProcessEvent(XtWidgetToApplicationContext(YesNoDialog), XtIMAll);
759 XtUnmanageChild(YesNoDialog);
761 /* Nasty motif bug here, patched around by waiting for a ReparentNotify
762 event (with timeout) before allowing file selection dialog to pop
763 down. If this routine returns too quickly, and the file selection
764 dialog (and thereby, this dialog as well) are destroyed while X
765 is still sorting through the events generated by the pop-down,
766 something bad happens and we get a crash */
767 if (YesNoResult == ynYes)
768 PopDownBugPatch(YesNoDialog);
770 return YesNoResult == ynYes;
773 static void doErrorDialog(const char *errorString, const char *filename)
775 char string[255];
776 XmString mString;
778 ErrorDone = False;
780 sprintf(string, errorString, filename);
781 mString = XmStringCreateLtoR(string, XmSTRING_DEFAULT_CHARSET);
783 SET_ONE_RSRC(ErrorDialog, XmNmessageString, mString);
784 XmStringFree(mString);
785 ManageDialogCenteredOnPointer(ErrorDialog);
787 while (!ErrorDone)
788 XtAppProcessEvent (XtWidgetToApplicationContext(ErrorDialog), XtIMAll);
790 XtUnmanageChild(ErrorDialog);
793 static void newFileOKCB(Widget w, Boolean *client_data,
794 XmFileSelectionBoxCallbackStruct *call_data)
797 char *filename; /* name of chosen file */
798 int fd; /* file descriptor */
799 int length; /* length of file name */
800 int response; /* response to dialog */
801 struct stat buf; /* status from fstat */
803 XmStringGetLtoR(call_data->value, XmSTRING_DEFAULT_CHARSET, &filename);
804 SelectResult = GFN_OK;
805 length = strlen(filename);
806 if (length == 0 || filename[length-1] == '/') {
807 doErrorDialog("Please supply a name for the file", NULL);
808 XtFree(filename);
809 return;
812 #ifdef VMS
813 if (strchr(filename,';') && (fd = open(filename, O_RDONLY, 0)) != -1) {
814 #else /* not VMS*/
815 if ((fd = open(filename, O_RDONLY, 0)) != -1) { /* exists */
816 #endif /*VMS*/
817 fstat(fd, &buf);
818 close(fd);
819 if (buf.st_mode & S_IFDIR) {
820 doErrorDialog("Error: %s is a directory", filename);
821 XtFree(filename);
822 return;
824 response = doYesNoDialog(filename);
825 #ifdef VMS
826 if (response) {
827 if (access(filename, 2) != 0) { /* have write/delete access? */
828 doErrorDialog("Error: can't overwrite %s ", filename);
829 XtFree(filename);
830 return;
832 } else {
833 #else
834 if (!response) {
835 #endif /*VMS*/
836 return;
838 } else {
839 if ((fd = creat(filename, PERMS)) == -1) {
840 doErrorDialog("Error: can't create %s ", filename);
841 XtFree(filename);
842 return;
843 } else {
844 close(fd);
845 remove(filename);
848 XtFree(filename);
849 *client_data = True; /* done with dialog */
853 static void newFileCancelCB(Widget w, Boolean *client_data, caddr_t call_data)
855 SelectResult = GFN_CANCEL;
856 *client_data = True;
859 static void newHelpCB(Widget w, Widget helpPanel, caddr_t call_data)
861 ManageDialogCenteredOnPointer(helpPanel);
864 static void existOkCB(Widget w, Boolean * client_data,
865 XmFileSelectionBoxCallbackStruct *call_data)
867 char *filename; /* name of chosen file */
868 int fd; /* file descriptor */
869 int length; /* length of file name */
871 XmStringGetLtoR(call_data->value, XmSTRING_DEFAULT_CHARSET, &filename);
872 SelectResult = GFN_OK;
873 length = strlen(filename);
874 if (length == 0 || filename[length-1] == '/') {
875 doErrorDialog("Please select a file to open", NULL);
876 XtFree(filename);
877 return;
878 } else if ((fd = open(filename, O_RDONLY,0)) == -1) {
879 doErrorDialog("Error: can't open %s ", filename);
880 XtFree(filename);
881 return;
882 } else
883 close(fd);
884 XtFree(filename);
886 *client_data = True; /* done with dialog */
890 static void existCancelCB(Widget w, Boolean * client_data, caddr_t call_data)
892 SelectResult = GFN_CANCEL;
893 *client_data = True; /* done with dialog */
896 static void yesNoOKCB(Widget w, caddr_t client_data, caddr_t call_data)
898 YesNoResult = ynYes;
901 static void existHelpCB(Widget w, Widget helpPanel, caddr_t call_data)
903 ManageDialogCenteredOnPointer(helpPanel);
906 static void errorOKCB(Widget w, caddr_t client_data, caddr_t call_data)
908 ErrorDone = True;
911 static void yesNoCancelCB(Widget w, caddr_t client_data, caddr_t call_data)
913 YesNoResult = ynNo;
916 static Widget createPanelHelp(Widget parent, const char *helpText, const char *title)
918 Arg al[20];
919 int ac;
920 Widget form, text, button;
921 XmString st1;
923 ac = 0;
924 form = CreateFormDialog(parent, "helpForm", al, ac);
926 ac = 0;
927 XtSetArg (al[ac], XmNbottomAttachment, XmATTACH_FORM); ac++;
928 XtSetArg (al[ac], XmNtopAttachment, XmATTACH_NONE); ac++;
929 XtSetArg(al[ac], XmNlabelString, st1=XmStringCreateLtoR ("Dismiss",
930 XmSTRING_DEFAULT_CHARSET)); ac++;
931 button = XmCreatePushButtonGadget(form, "dismiss", al, ac);
932 XtAddCallback(button, XmNactivateCallback, (XtCallbackProc)helpDismissCB,
933 (char *)form);
934 XmStringFree(st1);
935 XtManageChild(button);
936 SET_ONE_RSRC(form, XmNdefaultButton, button);
938 ac = 0;
939 XtSetArg(al[ac], XmNrows, 15); ac++;
940 XtSetArg(al[ac], XmNcolumns, 60); ac++;
941 XtSetArg(al[ac], XmNresizeHeight, False); ac++;
942 XtSetArg(al[ac], XmNtraversalOn, False); ac++;
943 XtSetArg(al[ac], XmNwordWrap, True); ac++;
944 XtSetArg(al[ac], XmNscrollHorizontal, False); ac++;
945 XtSetArg(al[ac], XmNeditMode, XmMULTI_LINE_EDIT); ac++;
946 XtSetArg(al[ac], XmNeditable, False); ac++;
947 XtSetArg(al[ac], XmNvalue, helpText); ac++;
948 XtSetArg(al[ac], XmNtopAttachment, XmATTACH_FORM); ac++;
949 XtSetArg(al[ac], XmNleftAttachment, XmATTACH_FORM); ac++;
950 XtSetArg(al[ac], XmNbottomAttachment, XmATTACH_WIDGET); ac++;
951 XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
952 XtSetArg(al[ac], XmNbottomWidget, button); ac++;
953 text = XmCreateScrolledText(form, "helpText", al, ac);
954 XtManageChild(text);
956 SET_ONE_RSRC(XtParent(form), XmNtitle, title);
958 return form;
961 static void helpDismissCB(Widget w, Widget helpPanel, caddr_t call_data)
963 XtUnmanageChild(helpPanel);
967 ** Add ability for user to type filenames to a list widget
969 static void makeListTypeable(Widget listW)
971 XtAddEventHandler(listW, KeyPressMask, False, listCharEH, NULL);
975 ** Action procedure for processing characters typed in a list, finds the
976 ** first item matching the characters typed so far.
978 static void listCharEH(Widget w, XtPointer callData, XEvent *event,
979 Boolean *continueDispatch)
981 char charString[5], c, *itemString;
982 int nChars, nItems, i, cmp, selectPos, topPos, nVisible;
983 XmString *items;
984 KeySym kSym;
985 char name[MAXPATHLEN], path[MAXPATHLEN];
986 static char keystrokes[MAX_LIST_KEYSTROKES];
987 static int nKeystrokes = 0;
988 static Time lastKeyTime = 0;
990 /* Get the ascii character code represented by the event */
991 nChars = XLookupString((XKeyEvent *)event, charString, sizeof(charString),
992 &kSym, NULL);
993 c = charString[0];
995 /* Process selected control keys, but otherwise ignore the keystroke
996 if it isn't a single printable ascii character */
997 *continueDispatch = False;
998 if (kSym==XK_BackSpace || kSym==XK_Delete) {
999 nKeystrokes = nKeystrokes > 0 ? nKeystrokes-1 : 0;
1000 return;
1001 } else if (kSym==XK_Clear || kSym==XK_Cancel || kSym==XK_Break) {
1002 nKeystrokes = 0;
1003 return;
1004 } else if (nChars!=1 || c<0x021 || c>0x07e) {
1005 *continueDispatch = True;
1006 return;
1009 /* Throw out keystrokes and start keystroke accumulation over from
1010 scratch if user waits more than MAX_LIST_KESTROKE_WAIT milliseconds */
1011 if (((XKeyEvent *)event)->time - lastKeyTime > MAX_LIST_KESTROKE_WAIT)
1012 nKeystrokes = 0;
1013 lastKeyTime = ((XKeyEvent *)event)->time;
1015 /* Accumulate the current keystroke, just beep if there are too many */
1016 if (nKeystrokes >= MAX_LIST_KEYSTROKES)
1017 XBell(XtDisplay(w), 0);
1018 else
1019 #ifdef VMS
1020 keystrokes[nKeystrokes++] = toupper(c);
1021 #else
1022 keystrokes[nKeystrokes++] = c;
1023 #endif
1025 /* Get the items (filenames) in the list widget */
1026 XtVaGetValues(w, XmNitems, &items, XmNitemCount, &nItems, NULL);
1028 /* compare them with the accumulated user keystrokes & decide the
1029 appropriate line in the list widget to select */
1030 selectPos = 0;
1031 for (i=0; i<nItems; i++) {
1032 XmStringGetLtoR(items[i], XmSTRING_DEFAULT_CHARSET, &itemString);
1033 ParseFilename(itemString, name, path);
1034 XtFree(itemString);
1035 cmp = strncmp(name, keystrokes, nKeystrokes);
1036 if (cmp == 0) {
1037 selectPos = i+1;
1038 break;
1039 } else if (cmp > 0) {
1040 selectPos = i;
1041 break;
1045 /* Make the selection, and make sure it will be visible */
1046 XmListSelectPos(w, selectPos, True);
1047 if (selectPos == 0) /* XmListSelectPos curiously returns 0 for last item */
1048 selectPos = nItems + 1;
1049 XtVaGetValues(w, XmNtopItemPosition, &topPos,
1050 XmNvisibleItemCount, &nVisible, NULL);
1051 if (selectPos < topPos)
1052 XmListSetPos(w, selectPos-2 > 1 ? selectPos-2 : 1);
1053 else if (selectPos > topPos+nVisible-1)
1054 XmListSetBottomPos(w, selectPos+2 <= nItems ? selectPos+2 : 0);
1055 /* For LessTif 0.89.9. Obsolete now? */
1056 XmListSelectPos(w, selectPos, True);
1060 ** Replacement directory and file search procedures for the file selection
1061 ** box to re-sort the items in a standard order. This is a patch, and not
1062 ** a very good one, for the problem that in some Motif versions, the directory
1063 ** list is sorted differently, such that typing of filenames fails because
1064 ** it expects strcmp alphabetical order, as opposed to strcasecmp. Most
1065 ** users prefer the old ordering, which is what this enforces, but if
1066 ** ifdefs can be found that will correctly predict the ordering and adjust
1067 ** listCharEH above, instead of resorting to re-sorting, it should be done.
1068 ** This obviously wastes valuable time as the selection box is popping up
1069 ** and should be removed. These routines also leak memory like a seive,
1070 ** because Motif's inconsistent treatment of memory in list widgets does
1071 ** not allow us to free lists that we pass in, and most Motif versions
1072 ** don't clean it up properly.
1074 static void replacementDirSearchProc(Widget w, XtPointer searchData)
1076 Boolean updated;
1078 /* Call the original search procedure to do the actual search */
1079 (*OrigDirSearchProc)(w, searchData);
1080 XtVaGetValues(w, XmNlistUpdated, &updated, NULL);
1081 if (!updated)
1082 return;
1084 /* Sort the items in the list */
1085 sortWidgetList(XmFileSelectionBoxGetChild(w, XmDIALOG_DIR_LIST));
1088 static void replacementFileSearchProc(Widget w, XtPointer searchData)
1090 Boolean updated;
1092 /* Call the original search procedure to do the actual search */
1093 (*OrigFileSearchProc)(w, searchData);
1094 XtVaGetValues(w, XmNlistUpdated, &updated, NULL);
1095 if (!updated)
1096 return;
1098 /* Sort the items in the list */
1099 sortWidgetList(XmFileSelectionBoxGetChild(w, XmDIALOG_LIST));
1103 ** Sort the items in a list widget "listWidget"
1105 static void sortWidgetList(Widget listWidget)
1107 XmString *items, *sortedItems;
1108 int nItems, i;
1110 XtVaGetValues(listWidget, XmNitems, &items, XmNitemCount, &nItems, NULL);
1111 sortedItems = (XmString *)XtMalloc(sizeof(XmString) * nItems);
1112 for (i=0; i<nItems; i++)
1113 sortedItems[i] = XmStringCopy(items[i]);
1114 qsort(sortedItems, nItems, sizeof(XmString), compareXmStrings);
1115 XmListReplaceItemsPos(listWidget, sortedItems, nItems, 1);
1116 for (i=0; i<nItems; i++)
1117 XmStringFree(sortedItems[i]);
1118 XtFree((char *)sortedItems);
1122 ** Compare procedure for qsort for sorting a list of XmStrings
1124 static int compareXmStrings(const void *string1, const void *string2)
1126 char *s1, *s2;
1127 int result;
1129 XmStringGetLtoR(*(XmString *)string1, XmSTRING_DEFAULT_CHARSET, &s1);
1130 XmStringGetLtoR(*(XmString *)string2, XmSTRING_DEFAULT_CHARSET, &s2);
1131 result = strcmp(s1, s2);
1132 XtFree(s1);
1133 XtFree(s2);
1134 return result;