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