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