Let file browser filter on extension
[xboard.git] / filebrowser / selfile.c
1 /*
2  * Copyright 1989 Software Research Associates, Inc., Tokyo, Japan
3  *
4  * Permission to use, copy, modify, and distribute this software and its
5  * documentation for any purpose and without fee is hereby granted, provided
6  * that the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation, and that the name of Software Research Associates not be used
9  * in advertising or publicity pertaining to distribution of the software
10  * without specific, written prior permission.  Software Research Associates
11  * makes no representations about the suitability of this software for any
12  * purpose.  It is provided "as is" without express or implied warranty.
13  *
14  * SOFTWARE RESEARCH ASSOCIATES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
15  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
16  * IN NO EVENT SHALL SOFTWARE RESEARCH ASSOCIATES BE LIABLE FOR ANY SPECIAL,
17  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
18  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
19  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20  * PERFORMANCE OF THIS SOFTWARE.
21  *
22  * Author: Erik M. van der Poel
23  *         Software Research Associates, Inc., Tokyo, Japan
24  *         erik@sra.co.jp
25  */
26
27 /*
28  * Author's address:
29  *
30  *     erik@sra.co.jp
31  *                                            OR
32  *     erik%sra.co.jp@uunet.uu.net
33  *                                            OR
34  *     erik%sra.co.jp@mcvax.uucp
35  *                                            OR
36  *     try junet instead of co.jp
37  *                                            OR
38  *     Erik M. van der Poel
39  *     Software Research Associates, Inc.
40  *     1-1-1 Hirakawa-cho, Chiyoda-ku
41  *     Tokyo 102 Japan. TEL +81-3-234-2692
42  */
43
44 #include <stdio.h>
45 #include <errno.h>
46 /* BSD 4.3 errno.h does not declare errno */
47 extern int errno;
48 //extern int sys_nerr;
49 //extern char *sys_errlist[]; // [HGM] this produced a compile error in Ubuntu 8.04
50
51 #include <sys/param.h>
52 #include <X11/cursorfont.h>
53 #include <X11/Intrinsic.h>
54 #include <X11/StringDefs.h>
55 #include <X11/Composite.h>
56 #include <X11/Shell.h>
57 #include <X11/Xaw/Form.h>
58 #include <X11/Xaw/Command.h>
59 #include <X11/Xaw/Scrollbar.h>
60 #include <X11/Xaw/Label.h>
61 #include <X11/Xaw/Cardinals.h>
62
63 #include "selfile.h"
64 #include "xstat.h"
65
66 /* added missing prototypes */
67 extern void SFdrawList(int,int);
68 extern void SFinitFont();
69 extern void SFcreateGC();
70 extern int SFchdir(char *);
71 extern void SFupdatePath();
72 extern void SFsetText(char *);
73 extern char SFstatChar(struct stat*);
74
75 #ifndef MAXPATHLEN
76 #define MAXPATHLEN 1024
77 #endif /* ndef MAXPATHLEN */
78
79 #if !defined(SVR4) && !defined(SYSV) && !defined(USG)
80 extern char *getwd();
81 #endif /* !defined(SVR4) && !defined(SYSV) && !defined(USG) */
82
83 int SFstatus = SEL_FILE_NULL;
84
85 char
86         SFstartDir[MAXPATHLEN],
87         SFcurrentPath[MAXPATHLEN],
88         SFlastPath[MAXPATHLEN],
89         SFcurrentDir[MAXPATHLEN];
90
91 Widget
92         selFile,
93         selFileCancel,
94         selFileField,
95         selFileMess,
96         filterField,
97         selFileForm,
98         selFileHScroll,
99         selFileHScrolls[3],
100         selFileLists[3],
101         selFileOK,
102         selFilePrompt,
103         selFileVScrolls[3];
104
105 Display *SFdisplay;
106
107 Pixel SFfore, SFback;
108
109 Atom    SFwmDeleteWindow;
110
111 XSegment SFsegs[2], SFcompletionSegs[2];
112
113 XawTextPosition SFtextPos;
114
115 int SFupperX, SFlowerY, SFupperY;
116
117 int SFtextX, SFtextYoffset;
118
119 int SFentryWidth, SFentryHeight;
120
121 int SFlineToTextH = 3;
122
123 int SFlineToTextV = 3;
124
125 int SFbesideText = 3;
126
127 int SFaboveAndBelowText = 2;
128
129 int SFcharsPerEntry = 15;
130
131 int SFlistSize = 10;
132
133 int SFworkProcAdded = 0;
134
135 XtAppContext SFapp;
136
137 int SFpathScrollWidth, SFvScrollHeight, SFhScrollWidth;
138
139 char SFtextBuffer[MAXPATHLEN];
140
141 char SFfilterBuffer[MAXPATHLEN];
142
143 XtIntervalId SFdirModTimerId;
144
145 int (*SFfunc)();
146
147 Boolean SFpathFlag; // [HGM]
148
149 static char *oneLineTextEditTranslations = "\
150         <Key>Return:    redraw-display()\n\
151         Ctrl<Key>M:     redraw-display()\n\
152 ";
153
154 /* ARGSUSED */
155 static void
156 SFexposeList(w, n, event, cont)
157         Widget          w;
158         XtPointer       n;
159         XEvent          *event;
160         Boolean         *cont;
161 {
162         if ((event->type == NoExpose) || event->xexpose.count) {
163                 return;
164         }
165
166         SFdrawList((int)(intptr_t)n, SF_DO_NOT_SCROLL);
167 }
168
169 void
170 SFpurge()
171 {
172         if(SFdirs) XtFree((XtPointer) SFdirs);
173         SFdirs = NULL; // kludge to throw away all cached info
174 }
175
176 /* ARGSUSED */
177 static void
178 SFmodVerifyCallback(w, client_data, event, cont)
179         Widget                  w;
180         XtPointer               client_data;
181         XEvent                  *event;
182         Boolean                 *cont;
183 {
184         char    buf[2];
185
186         if (
187                 (XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1) &&
188                 ((*buf) == '\r' || *buf == 033)
189         ) {
190                 if(client_data) {
191                     Arg args[10]; char *p;
192                     if(*buf == 033) { // [HGM] esc in filter: restore and give focus to path
193                         XtSetArg(args[0], XtNstring, SFfilterBuffer);
194                         XtSetValues(filterField, args, 1);
195                         XtSetKeyboardFocus(selFileForm, selFileField);
196                         SFstatus = SEL_FILE_TEXT;
197                         return;
198                     } else
199                     if(!SFpathFlag) // [HGM] cr: fetch current extenson filter
200                     {   
201                         XtSetArg(args[0], XtNstring, &p);
202                         XtGetValues(filterField, args, 1);
203                         if(strcmp(SFfilterBuffer, p)) SFpurge();
204                         strncpy(SFfilterBuffer, p, 40);
205                         SFstatus = SEL_FILE_TEXT;
206                     }
207                     return;
208                 }
209                 SFstatus = (*buf == 033 ? SEL_FILE_CANCEL : SEL_FILE_OK);
210         } else {
211                 if(!client_data) SFstatus = SEL_FILE_TEXT;
212         }
213 }
214
215 /* ARGSUSED */
216 static void
217 SFokCallback(w, cl, cd)
218         Widget  w;
219         XtPointer cl, cd;
220 {
221         SFstatus = SEL_FILE_OK;
222 }
223
224 static XtCallbackRec SFokSelect[] = {
225         { SFokCallback, (XtPointer) NULL },
226         { NULL, (XtPointer) NULL },
227 };
228
229 /* ARGSUSED */
230 static void
231 SFcancelCallback(w, cl, cd)
232         Widget  w;
233         XtPointer cl, cd;
234 {
235         SFstatus = SEL_FILE_CANCEL;
236 }
237
238 static XtCallbackRec SFcancelSelect[] = {
239         { SFcancelCallback, (XtPointer) NULL },
240         { NULL, (XtPointer) NULL },
241 };
242
243 /* ARGSUSED */
244 static void
245 SFdismissAction(w, event, params, num_params)
246         Widget  w;
247         XEvent *event;
248         String *params;
249         Cardinal *num_params;
250 {
251         if (event->type == ClientMessage &&
252             event->xclient.data.l[0] != SFwmDeleteWindow) return;
253
254         SFstatus = SEL_FILE_CANCEL;
255 }
256
257 static char *wmDeleteWindowTranslation = "\
258         <Message>WM_PROTOCOLS:  SelFileDismiss()\n\
259 ";
260
261 static XtActionsRec actions[] = {
262         {"SelFileDismiss",      SFdismissAction},
263 };
264
265 void SFsetFocus(Widget w, XtPointer data, XEvent *event, Boolean *b)
266 {
267     XtSetKeyboardFocus((Widget) data, w);
268 }
269
270 static void
271 SFcreateWidgets(toplevel, prompt, ok, cancel)
272         Widget  toplevel;
273         char    *prompt;
274         char    *ok;
275         char    *cancel;
276 {
277         Cardinal        i, n;
278         int             listWidth, listHeight;
279         int             listSpacing = 10;
280         int             scrollThickness = 15;
281         int             hScrollX, hScrollY;
282         int             vScrollX, vScrollY;
283         Cursor
284                         xtermCursor,
285                         sbRightArrowCursor,
286                         dotCursor;
287         Arg             arglist[20];
288
289         i = 0;
290         XtSetArg(arglist[i], XtNtransientFor, toplevel);                i++;
291
292         selFile = XtAppCreateShell("Browse", "SelFile",
293                 transientShellWidgetClass, SFdisplay, arglist, i);
294
295         /* Add WM_DELETE_WINDOW protocol */
296         XtAppAddActions(XtWidgetToApplicationContext(selFile),
297                 actions, XtNumber(actions));
298         XtOverrideTranslations(selFile,
299                 XtParseTranslationTable(wmDeleteWindowTranslation));
300
301         i = 0;
302         XtSetArg(arglist[i], XtNdefaultDistance, 30);                   i++;
303         selFileForm = XtCreateManagedWidget("selFileForm",
304                 formWidgetClass, selFile, arglist, i);
305
306         i = 0;
307         XtSetArg(arglist[i], XtNlabel, prompt);                         i++;
308         XtSetArg(arglist[i], XtNresizable, True);                       i++;
309         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
310         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
311         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
312         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
313         XtSetArg(arglist[i], XtNborderWidth, 0);                        i++;
314         selFilePrompt = XtCreateManagedWidget("selFilePrompt",
315                 labelWidgetClass, selFileForm, arglist, i);
316
317         i = 0;
318         XtSetArg(arglist[i], XtNforeground, &SFfore);                   i++;
319         XtSetArg(arglist[i], XtNbackground, &SFback);                   i++;
320         XtGetValues(selFilePrompt, arglist, i);
321
322         SFinitFont();
323
324         SFentryWidth = SFbesideText + SFcharsPerEntry * SFcharWidth +
325                         SFbesideText;
326         SFentryHeight = SFaboveAndBelowText + SFcharHeight +
327                         SFaboveAndBelowText;
328
329         listWidth = SFlineToTextH + SFentryWidth + SFlineToTextH + 1 +
330                         scrollThickness;
331         listHeight = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
332                         SFlineToTextV + SFlistSize * SFentryHeight +
333                         SFlineToTextV + 1 + scrollThickness;
334
335         SFpathScrollWidth = NR * listWidth + (NR-1) * listSpacing + 4;
336
337         hScrollX = -1;
338         hScrollY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
339                         SFlineToTextV + SFlistSize * SFentryHeight +
340                         SFlineToTextV;
341         SFhScrollWidth = SFlineToTextH + SFentryWidth + SFlineToTextH;
342
343         vScrollX = SFlineToTextH + SFentryWidth + SFlineToTextH;
344         vScrollY = SFlineToTextV + SFentryHeight + SFlineToTextV;
345         SFvScrollHeight = SFlineToTextV + SFlistSize * SFentryHeight +
346                         SFlineToTextV;
347
348         SFupperX = SFlineToTextH + SFentryWidth + SFlineToTextH - 1;
349         SFlowerY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
350                         SFlineToTextV;
351         SFupperY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
352                         SFlineToTextV + SFlistSize * SFentryHeight - 1;
353
354         SFtextX = SFlineToTextH + SFbesideText;
355         SFtextYoffset = SFlowerY + SFaboveAndBelowText + SFcharAscent;
356
357         SFsegs[0].x1 = 0;
358         SFsegs[0].y1 = vScrollY;
359         SFsegs[0].x2 = vScrollX - 1;
360         SFsegs[0].y2 = vScrollY;
361         SFsegs[1].x1 = vScrollX;
362         SFsegs[1].y1 = 0;
363         SFsegs[1].x2 = vScrollX;
364         SFsegs[1].y2 = vScrollY - 1;
365
366         SFcompletionSegs[0].x1 = SFcompletionSegs[0].x2 = SFlineToTextH;
367         SFcompletionSegs[1].x1 = SFcompletionSegs[1].x2 =
368                 SFlineToTextH + SFentryWidth - 1;
369
370         i = 0;
371         XtSetArg(arglist[i], XtNwidth, NR * listWidth + (NR - 1) * listSpacing + 4);
372                                                                         i++;
373         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
374
375         XtSetArg(arglist[i], XtNfromVert, selFilePrompt);               i++;
376         XtSetArg(arglist[i], XtNvertDistance, 5);                       i++;
377         XtSetArg(arglist[i], XtNresizable, True);                       i++;
378         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
379         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
380         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
381         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
382         XtSetArg(arglist[i], XtNstring, SFtextBuffer);                  i++;
383         XtSetArg(arglist[i], XtNlength, MAXPATHLEN);                    i++;
384         XtSetArg(arglist[i], XtNeditType, XawtextEdit);                 i++;
385         XtSetArg(arglist[i], XtNwrap, XawtextWrapWord);                 i++;
386         XtSetArg(arglist[i], XtNresize, XawtextResizeHeight);           i++;
387         XtSetArg(arglist[i], XtNuseStringInPlace, True);                i++;
388         selFileField = XtCreateManagedWidget("selFileField",
389                 asciiTextWidgetClass, selFileForm, arglist, i);
390
391         XtOverrideTranslations(selFileField,
392                 XtParseTranslationTable(oneLineTextEditTranslations));
393         XtAddEventHandler(selFileField, ButtonPressMask, False, SFsetFocus, (XtPointer) selFileForm);
394
395         i = 0;
396         XtSetArg(arglist[i], XtNlabel, "Filter on extensions:");        i++;
397         XtSetArg(arglist[i], XtNvertDistance, 5);                       i++;
398         XtSetArg(arglist[i], XtNfromVert, selFileField);                i++;
399         XtSetArg(arglist[i], XtNresizable, True);                       i++;
400         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
401         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
402         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
403         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
404         XtSetArg(arglist[i], XtNborderWidth, 0);                        i++;
405         selFileMess = XtCreateManagedWidget("selFileMess",
406                 labelWidgetClass, selFileForm, arglist, i);
407
408         i = 0;
409         XtSetArg(arglist[i], XtNwidth, NR * listWidth + (NR - 1) * listSpacing + 4);
410                                                                         i++;
411         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
412         XtSetArg(arglist[i], XtNvertDistance, 5);                       i++;
413         XtSetArg(arglist[i], XtNfromVert, selFileMess);                 i++;
414         XtSetArg(arglist[i], XtNresizable, True);                       i++;
415         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
416         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
417         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
418         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
419         XtSetArg(arglist[i], XtNlength, MAXPATHLEN);                    i++;
420         XtSetArg(arglist[i], XtNeditType, XawtextEdit);                 i++;
421         XtSetArg(arglist[i], XtNwrap, XawtextWrapWord);                 i++;
422         XtSetArg(arglist[i], XtNresize, XawtextResizeHeight);           i++;
423         XtSetArg(arglist[i], XtNuseStringInPlace, False);               i++;
424         filterField = XtCreateManagedWidget("filterField",
425                 asciiTextWidgetClass, selFileForm, arglist, i);
426
427         XtOverrideTranslations(filterField,
428                 XtParseTranslationTable(oneLineTextEditTranslations));
429         XtAddEventHandler(filterField, ButtonPressMask, False, SFsetFocus, (XtPointer) selFileForm);
430
431         i = 0;
432         XtSetArg(arglist[i], XtNorientation, XtorientHorizontal);       i++;
433         XtSetArg(arglist[i], XtNwidth, SFpathScrollWidth);              i++;
434         XtSetArg(arglist[i], XtNheight, scrollThickness);               i++;
435         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
436         XtSetArg(arglist[i], XtNfromVert, filterField);                 i++;
437         XtSetArg(arglist[i], XtNvertDistance, 10);                      i++;
438         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
439         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
440         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
441         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
442         selFileHScroll = XtCreateManagedWidget("selFileHScroll",
443                 scrollbarWidgetClass, selFileForm, arglist, i);
444
445         XtAddCallback(selFileHScroll, XtNjumpProc,
446                 SFpathSliderMovedCallback, (XtPointer) NULL);
447         XtAddCallback(selFileHScroll, XtNscrollProc,
448                 SFpathAreaSelectedCallback, (XtPointer) NULL);
449
450         i = 0;
451         XtSetArg(arglist[i], XtNwidth, listWidth);                      i++;
452         XtSetArg(arglist[i], XtNheight, listHeight);                    i++;
453         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
454         XtSetArg(arglist[i], XtNfromVert, selFileHScroll);              i++;
455         XtSetArg(arglist[i], XtNvertDistance, 10);                      i++;
456         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
457         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
458         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
459         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
460         selFileLists[0] = XtCreateManagedWidget("selFileList1",
461                 compositeWidgetClass, selFileForm, arglist, i);
462 #if (NR == 3)
463         i = 0;
464         XtSetArg(arglist[i], XtNwidth, listWidth);                      i++;
465         XtSetArg(arglist[i], XtNheight, listHeight);                    i++;
466         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
467         XtSetArg(arglist[i], XtNfromHoriz, selFileLists[0]);            i++;
468         XtSetArg(arglist[i], XtNfromVert, selFileHScroll);              i++;
469         XtSetArg(arglist[i], XtNhorizDistance, listSpacing);            i++;
470         XtSetArg(arglist[i], XtNvertDistance, 10);                      i++;
471         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
472         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
473         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
474         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
475         selFileLists[1] = XtCreateManagedWidget("selFileList2",
476                 compositeWidgetClass, selFileForm, arglist, i);
477
478         i = 0;
479         XtSetArg(arglist[i], XtNwidth, listWidth);                      i++;
480         XtSetArg(arglist[i], XtNheight, listHeight);                    i++;
481         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
482         XtSetArg(arglist[i], XtNfromHoriz, selFileLists[1]);            i++;
483         XtSetArg(arglist[i], XtNfromVert, selFileHScroll);              i++;
484         XtSetArg(arglist[i], XtNhorizDistance, listSpacing);            i++;
485         XtSetArg(arglist[i], XtNvertDistance, 10);                      i++;
486         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
487         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
488         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
489         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
490         selFileLists[2] = XtCreateManagedWidget("selFileList3",
491                 compositeWidgetClass, selFileForm, arglist, i);
492 #endif
493         for (n = 0; n < NR; n++) {
494
495                 i = 0;
496                 XtSetArg(arglist[i], XtNx, vScrollX);                   i++;
497                 XtSetArg(arglist[i], XtNy, vScrollY);                   i++;
498                 XtSetArg(arglist[i], XtNwidth, scrollThickness);        i++;
499                 XtSetArg(arglist[i], XtNheight, SFvScrollHeight);       i++;
500                 XtSetArg(arglist[i], XtNborderColor, SFfore);           i++;
501                 selFileVScrolls[n] = XtCreateManagedWidget("selFileVScroll",
502                         scrollbarWidgetClass, selFileLists[n], arglist, i);
503
504                 XtAddCallback(selFileVScrolls[n], XtNjumpProc,
505                         SFvFloatSliderMovedCallback, (XtPointer)(intptr_t) n);
506                 XtAddCallback(selFileVScrolls[n], XtNscrollProc,
507                         SFvAreaSelectedCallback, (XtPointer)(intptr_t) n);
508
509                 i = 0;
510
511                 XtSetArg(arglist[i], XtNorientation, XtorientHorizontal);
512                                                                         i++;
513                 XtSetArg(arglist[i], XtNx, hScrollX);                   i++;
514                 XtSetArg(arglist[i], XtNy, hScrollY);                   i++;
515                 XtSetArg(arglist[i], XtNwidth, SFhScrollWidth);         i++;
516                 XtSetArg(arglist[i], XtNheight, scrollThickness);       i++;
517                 XtSetArg(arglist[i], XtNborderColor, SFfore);           i++;
518                 selFileHScrolls[n] = XtCreateManagedWidget("selFileHScroll",
519                         scrollbarWidgetClass, selFileLists[n], arglist, i);
520
521                 XtAddCallback(selFileHScrolls[n], XtNjumpProc,
522                         SFhSliderMovedCallback, (XtPointer)(intptr_t) n);
523                 XtAddCallback(selFileHScrolls[n], XtNscrollProc,
524                         SFhAreaSelectedCallback, (XtPointer)(intptr_t) n);
525         }
526
527         i = 0;
528         XtSetArg(arglist[i], XtNlabel, ok);                             i++;
529         XtSetArg(arglist[i], XtNresizable, True);                       i++;
530         XtSetArg(arglist[i], XtNcallback, SFokSelect);                  i++;
531         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
532         XtSetArg(arglist[i], XtNfromVert, selFileLists[0]);             i++;
533         XtSetArg(arglist[i], XtNvertDistance, 30);                      i++;
534         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
535         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
536         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
537         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
538         selFileOK = XtCreateManagedWidget("selFileOK", commandWidgetClass,
539                 selFileForm, arglist, i);
540
541         i = 0;
542         XtSetArg(arglist[i], XtNlabel, cancel);                         i++;
543         XtSetArg(arglist[i], XtNresizable, True);                       i++;
544         XtSetArg(arglist[i], XtNcallback, SFcancelSelect);              i++;
545         XtSetArg(arglist[i], XtNborderColor, SFfore);                   i++;
546         XtSetArg(arglist[i], XtNfromHoriz, selFileOK);                  i++;
547         XtSetArg(arglist[i], XtNfromVert, selFileLists[0]);             i++;
548         XtSetArg(arglist[i], XtNhorizDistance, 30);                     i++;
549         XtSetArg(arglist[i], XtNvertDistance, 30);                      i++;
550         XtSetArg(arglist[i], XtNtop, XtChainTop);                       i++;
551         XtSetArg(arglist[i], XtNbottom, XtChainTop);                    i++;
552         XtSetArg(arglist[i], XtNleft, XtChainLeft);                     i++;
553         XtSetArg(arglist[i], XtNright, XtChainLeft);                    i++;
554         selFileCancel = XtCreateManagedWidget("selFileCancel",
555                 commandWidgetClass, selFileForm, arglist, i);
556
557         XtSetMappedWhenManaged(selFile, False);
558         XtRealizeWidget(selFile);
559
560         /* Add WM_DELETE_WINDOW protocol */
561         SFwmDeleteWindow = XInternAtom(SFdisplay, "WM_DELETE_WINDOW", False);
562         XSetWMProtocols(SFdisplay, XtWindow(selFile), &SFwmDeleteWindow, 1);
563
564         SFcreateGC();
565
566         xtermCursor = XCreateFontCursor(SFdisplay, XC_xterm);
567
568         sbRightArrowCursor = XCreateFontCursor(SFdisplay, XC_sb_right_arrow);
569         dotCursor = XCreateFontCursor(SFdisplay, XC_dot);
570
571         XDefineCursor(SFdisplay, XtWindow(selFileForm), xtermCursor);
572         XDefineCursor(SFdisplay, XtWindow(selFileField), xtermCursor);
573         XDefineCursor(SFdisplay, XtWindow(filterField), xtermCursor);
574
575         for (n = 0; n < NR; n++) {
576                 XDefineCursor(SFdisplay, XtWindow(selFileLists[n]),
577                         sbRightArrowCursor);
578         }
579         XDefineCursor(SFdisplay, XtWindow(selFileOK), dotCursor);
580         XDefineCursor(SFdisplay, XtWindow(selFileCancel), dotCursor);
581
582         for (n = 0; n < NR; n++) {
583                 XtAddEventHandler(selFileLists[n], ExposureMask, True,
584                         SFexposeList, (XtPointer)(intptr_t) n);
585                 XtAddEventHandler(selFileLists[n], EnterWindowMask, False,
586                         SFenterList, (XtPointer)(intptr_t) n);
587                 XtAddEventHandler(selFileLists[n], LeaveWindowMask, False,
588                         SFleaveList, (XtPointer)(intptr_t) n);
589                 XtAddEventHandler(selFileLists[n], PointerMotionMask, False,
590                         SFmotionList, (XtPointer)(intptr_t) n);
591                 XtAddEventHandler(selFileLists[n], ButtonPressMask, False,
592                         SFbuttonPressList, (XtPointer)(intptr_t) n);
593                 XtAddEventHandler(selFileLists[n], ButtonReleaseMask, False,
594                         SFbuttonReleaseList, (XtPointer)(intptr_t) n);
595         }
596
597         XtAddEventHandler(selFileField, KeyPressMask, False,
598                 SFmodVerifyCallback, (XtPointer) NULL);
599         XtAddEventHandler(filterField, KeyReleaseMask, False,
600                 SFmodVerifyCallback, (XtPointer) 1);
601         XtSetKeyboardFocus(selFileForm, selFileField);
602
603         SFapp = XtWidgetToApplicationContext(selFile);
604
605 }
606
607 /* position widget under the cursor */
608 void
609 SFpositionWidget(w)
610     Widget w;
611 {
612     Arg args[3];
613     Cardinal num_args;
614     Dimension width, height, b_width;
615     int x, y, max_x, max_y;
616     Window root, child;
617     int dummyx, dummyy;
618     unsigned int dummymask;
619     
620     XQueryPointer(XtDisplay(w), XtWindow(w), &root, &child, &x, &y,
621                   &dummyx, &dummyy, &dummymask);
622     num_args = 0;
623     XtSetArg(args[num_args], XtNwidth, &width); num_args++;
624     XtSetArg(args[num_args], XtNheight, &height); num_args++;
625     XtSetArg(args[num_args], XtNborderWidth, &b_width); num_args++;
626     XtGetValues(w, args, num_args);
627
628     width += 2 * b_width;
629     height += 2 * b_width;
630
631     x -= ( (Position) width/2 );
632     if (x < 0) x = 0;
633     if ( x > (max_x = (Position) (XtScreen(w)->width - width)) ) x = max_x;
634
635     y -= ( (Position) height/2 );
636     if (y < 0) y = 0;
637     if ( y > (max_y = (Position) (XtScreen(w)->height - height)) ) y = max_y;
638     
639     num_args = 0;
640     XtSetArg(args[num_args], XtNx, x); num_args++;
641     XtSetArg(args[num_args], XtNy, y); num_args++;
642     XtSetValues(w, args, num_args);
643 }
644
645 FILE *
646 SFopenFile(name, mode, prompt, failed)
647     char *name;
648     char *mode;
649     char *prompt;
650     char *failed;
651 {
652     Arg args[1];
653     FILE *fp;
654
655     SFchdir(SFstartDir);
656     if ((fp = fopen(name, mode)) == NULL) {
657         char *buf;
658         if (1) { // [HGM] always use strerror
659             buf = XtMalloc(strlen(failed) + strlen(strerror(errno)) + 
660                            strlen(prompt) + 2);
661             strcpy(buf, failed);
662             strcat(buf, strerror(errno));
663             strcat(buf, "\n");
664             strcat(buf, prompt);
665         } else {
666             buf = XtMalloc(strlen(failed) + strlen(prompt) + 2);
667             strcpy(buf, failed);
668             strcat(buf, "\n");
669             strcat(buf, prompt);
670         }
671         XtSetArg(args[0], XtNlabel, buf);
672         XtSetValues(selFilePrompt, args, ONE);
673         XtFree(buf);
674         return NULL;
675     }
676     return fp;
677 }
678
679 void
680 SFtextChanged()
681 {
682
683         if ((SFtextBuffer[0] == '/') || (SFtextBuffer[0] == '~')) {
684           (void) strncpy(SFcurrentPath, SFtextBuffer, MAXPATHLEN);
685
686                 SFtextPos = XawTextGetInsertionPoint(selFileField);
687         } else {
688           (void) strcat(strncpy(SFcurrentPath, SFstartDir, MAXPATHLEN), SFtextBuffer);
689
690                 SFtextPos = XawTextGetInsertionPoint(selFileField) +
691                         strlen(SFstartDir);
692         }
693
694         if (!SFworkProcAdded) {
695                 (void) XtAppAddWorkProc(SFapp, SFworkProc, NULL);
696                 SFworkProcAdded = 1;
697         }
698
699         SFupdatePath();
700         return;
701 }
702
703 static char *
704 SFgetText()
705 {
706         return strcpy(XtMalloc((unsigned) (strlen(SFtextBuffer) + 1)),
707                 SFtextBuffer);
708 }
709
710 static void
711 SFprepareToReturn()
712 {
713         SFstatus = SEL_FILE_NULL;
714         XtRemoveGrab(selFile);
715         XtUnmapWidget(selFile);
716         XtRemoveTimeOut(SFdirModTimerId);
717         if (SFchdir(SFstartDir)) {
718                 XtAppError(
719                         SFapp,
720                         "XsraSelFile: can't return to current directory"
721                 );
722         }
723         return;
724 }
725
726 FILE *
727 XsraSelFile(toplevel, prompt, ok, cancel, failed,
728             init_path, filter, mode, show_entry, name_return)
729         Widget          toplevel;
730         char            *prompt;
731         char            *ok;
732         char            *cancel;
733         char            *failed;
734         char            *init_path;
735         char            *filter;
736         char            *mode;
737         int             (*show_entry)();
738         char            **name_return;
739 {
740         static int      firstTime = 1;
741         Cardinal        i;
742         Arg             arglist[20];
743         XEvent          event;
744         FILE            *fp;
745
746         if (!prompt) {
747                 prompt = "Pathname:";
748         }
749
750         if (!ok) {
751                 ok = "OK";
752         }
753
754         if (!cancel) {
755                 cancel = "Cancel";
756         }
757
758         if(SFpathFlag != (mode && mode[0] == 'p') || strcmp(SFfilterBuffer, filter)) {
759                 SFpurge();
760                 SFpathFlag = (mode && mode[0] == 'p'); // [HGM] ignore everything that is not a directory
761         }
762
763         if (firstTime) {
764                 firstTime = 0;
765                 SFdisplay = XtDisplay(toplevel);
766                 SFcreateWidgets(toplevel, prompt, ok, cancel);
767         } else {
768                 i = 0;
769
770                 XtSetArg(arglist[i], XtNlabel, prompt);                 i++;
771                 XtSetValues(selFilePrompt, arglist, i);
772
773                 i = 0;
774                 XtSetArg(arglist[i], XtNlabel, ok);                     i++;
775                 XtSetValues(selFileOK, arglist, i);
776
777                 i = 0;
778                 XtSetArg(arglist[i], XtNlabel, cancel);                 i++;
779                 XtSetValues(selFileCancel, arglist, i);
780         }
781
782         i = 0;
783         XtSetArg(arglist[i], XtNstring, filter);                        i++;
784         XtSetValues(filterField, arglist, i);
785
786         safeStrCpy(SFfilterBuffer, filter, MAXPATHLEN);
787         safeStrCpy(SFlastPath, SFtextBuffer, MAXPATHLEN); // remember for cancel
788
789         SFpositionWidget(selFile);
790         XtMapWidget(selFile);
791
792 #if defined(SVR4) || defined(SYSV) || defined(USG) || 1
793         if (!getcwd(SFstartDir, MAXPATHLEN)) { // [HGM] always do this, as I do not know when exactly to do it
794 #else /* defined(SVR4) || defined(SYSV) || defined(USG) */
795         if (!getwd(SFstartDir)) {
796 #endif /* defined(SVR4) || defined(SYSV) || defined(USG) */
797
798                 XtAppError(SFapp, "XsraSelFile: can't get current directory");
799         }
800         (void) strcat(SFstartDir, "/");
801         (void) strncpy(SFcurrentDir, SFstartDir, MAXPATHLEN);
802
803         if (init_path) {
804                 if (init_path[0] == '/') {
805                   (void) strncpy(SFcurrentPath, init_path, MAXPATHLEN);
806                         if (strncmp(
807                                 SFcurrentPath,
808                                 SFstartDir,
809                                 strlen(SFstartDir)
810                         )) {
811                                 SFsetText(SFcurrentPath);
812                         } else {
813                                 SFsetText(&(SFcurrentPath[strlen(SFstartDir)]));
814                         }
815                 } else {
816                   (void) strcat(strncpy(SFcurrentPath, SFstartDir, MAXPATHLEN),
817                                 init_path);
818                         SFsetText(&(SFcurrentPath[strlen(SFstartDir)]));
819                 }
820         } else {
821           (void) strncpy(SFcurrentPath, SFstartDir, MAXPATHLEN);
822         }
823
824         SFfunc = show_entry;
825
826         SFtextChanged();
827
828         XtAddGrab(selFile, True, True);
829
830         SFdirModTimerId = XtAppAddTimeOut(SFapp, (unsigned long) 1000,
831                 SFdirModTimer, (XtPointer) NULL);
832
833         while (1) {
834                 XtAppNextEvent(SFapp, &event);
835                 XtDispatchEvent(&event);
836                 switch (SFstatus) {
837                 case SEL_FILE_TEXT:
838                         SFstatus = SEL_FILE_NULL;
839                         SFtextChanged();
840                         break;
841                 case SEL_FILE_OK:
842                         *name_return = SFgetText();
843                         if(mode && (mode[0] == 'p' || mode[0] == 'f')) { // [HGM] for use in file-option browse button
844                                 SFprepareToReturn();
845                                 return stderr;
846                         }
847                         if (fp = SFopenFile(*name_return, mode,
848                                             prompt, failed)) {
849                                 SFprepareToReturn();
850                                 return fp;
851                         }
852                         SFstatus = SEL_FILE_NULL;
853                         break;
854                 case SEL_FILE_CANCEL:
855                         SFprepareToReturn();
856                         SFsetText(SFlastPath);
857                         return NULL;
858                 case SEL_FILE_NULL:
859                         break;
860                 }
861         }
862 }