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