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