e591d742f657c21444adc89441a07b4df7c07c84
[xboard.git] / xoptions.c
1 /*
2  * xoptions.c -- Move list window, part of X front end for XBoard
3  *
4  * Copyright 2000, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
5  * ------------------------------------------------------------------------
6  *
7  * GNU XBoard is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or (at
10  * your option) any later version.
11  *
12  * GNU XBoard is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see http://www.gnu.org/licenses/.  *
19  *
20  *------------------------------------------------------------------------
21  ** See the file ChangeLog for a revision history.  */
22
23 // [HGM] this file is the counterpart of woptions.c, containing xboard popup menus
24 // similar to those of WinBoard, to set the most common options interactively.
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <sys/types.h>
32
33 #if STDC_HEADERS
34 # include <stdlib.h>
35 # include <string.h>
36 #else /* not STDC_HEADERS */
37 extern char *getenv();
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #if HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include <stdint.h>
49
50 #include <X11/Intrinsic.h>
51 #include <X11/StringDefs.h>
52 #include <X11/Shell.h>
53 #include <X11/Xatom.h>
54 #include <X11/Xaw/Dialog.h>
55 #include <X11/Xaw/Form.h>
56 #include <X11/Xaw/List.h>
57 #include <X11/Xaw/Label.h>
58 #include <X11/Xaw/SimpleMenu.h>
59 #include <X11/Xaw/SmeBSB.h>
60 #include <X11/Xaw/SmeLine.h>
61 #include <X11/Xaw/Box.h>
62 #include <X11/Xaw/Paned.h>
63 #include <X11/Xaw/MenuButton.h>
64 #include <X11/cursorfont.h>
65 #include <X11/Xaw/Text.h>
66 #include <X11/Xaw/AsciiText.h>
67 #include <X11/Xaw/Viewport.h>
68 #include <X11/Xaw/Toggle.h>
69
70 #include "common.h"
71 #include "backend.h"
72 #include "xboard.h"
73 #include "dialogs.h"
74 #include "gettext.h"
75
76 #ifdef ENABLE_NLS
77 # define  _(s) gettext (s)
78 # define N_(s) gettext_noop (s)
79 #else
80 # define  _(s) (s)
81 # define N_(s)  s
82 #endif
83
84 // [HGM] the following code for makng menu popups was cloned from the FileNamePopUp routines
85
86 static Widget previous = NULL;
87 static Option *currentOption;
88
89 void
90 SetFocus (Widget w, XtPointer data, XEvent *event, Boolean *b)
91 {
92     Arg args[2];
93     char *s;
94     int j;
95
96     if(previous) {
97         XtSetArg(args[0], XtNdisplayCaret, False);
98         XtSetValues(previous, args, 1);
99     }
100     XtSetArg(args[0], XtNstring, &s);
101     XtGetValues(w, args, 1);
102     j = 1;
103     XtSetArg(args[0], XtNdisplayCaret, True);
104     if(!strchr(s, '\n') && strlen(s) < 80) XtSetArg(args[1], XtNinsertPosition, strlen(s)), j++;
105     XtSetValues(w, args, j);
106     XtSetKeyboardFocus((Widget) data, w);
107     previous = w;
108 }
109
110 //--------------------------- Engine-specific options menu ----------------------------------
111
112 int dialogError;
113 static Boolean browserUp;
114
115 void
116 GetWidgetText (Option *opt, char **buf)
117 {
118     Arg arg;
119     XtSetArg(arg, XtNstring, buf);
120     XtGetValues(opt->handle, &arg, 1);
121 }
122
123 void
124 SetWidgetText (Option *opt, char *buf, int n)
125 {
126     Arg arg;
127     XtSetArg(arg, XtNstring, buf);
128     XtSetValues(opt->handle, &arg, 1);
129     if(n >= 0) SetFocus(opt->handle, shells[n], NULL, False);
130 }
131
132 void
133 GetWidgetState (Option *opt, int *state)
134 {
135     Arg arg;
136     XtSetArg(arg, XtNstate, state);
137     XtGetValues(opt->handle, &arg, 1);
138 }
139
140 void
141 SetWidgetState (Option *opt, int state)
142 {
143     Arg arg;
144     XtSetArg(arg, XtNstate, state);
145     XtSetValues(opt->handle, &arg, 1);
146 }
147
148 void
149 SetDialogTitle (DialogClass dlg, char *title)
150 {
151     Arg args[16];
152     XtSetArg(args[0], XtNtitle, title);
153     XtSetValues(shells[dlg], args, 1);
154 }
155
156 static void
157 CheckCallback (Widget ww, XtPointer data, XEvent *event, Boolean *b)
158 {
159     Widget w = currentOption[(int)(intptr_t)data].handle;
160     Boolean s;
161     Arg args[16];
162
163     XtSetArg(args[0], XtNstate, &s);
164     XtGetValues(w, args, 1);
165     SetWidgetState(&currentOption[(int)(intptr_t)data], !s);
166 }
167
168 static void
169 SpinCallback (Widget w, XtPointer client_data, XtPointer call_data)
170 {
171     String name, val;
172     Arg args[16];
173     char buf[MSG_SIZ], *p;
174     int j = 0; // Initialiasation is necessary because the text value may be non-numeric causing the scanf conversion to fail
175     int data = (intptr_t) client_data;
176
177     XtSetArg(args[0], XtNlabel, &name);
178     XtGetValues(w, args, 1);
179
180     GetWidgetText(&currentOption[data], &val);
181     sscanf(val, "%d", &j);
182     if (strcmp(name, _("browse")) == 0) {
183         char *q=val, *r;
184         for(r = ""; *q; q++) if(*q == '.') r = q; else if(*q == '/') r = ""; // last dot after last slash
185         if(!strcmp(r, "") && !currentCps && currentOption[data].type == FileName && currentOption[data].textValue)
186                 r = currentOption[data].textValue;
187         browserUp = True;
188         if(XsraSelFile(shells[TransientDlg], currentOption[data].name, NULL, NULL, "", "", r,
189                                   currentOption[data].type == PathName ? "p" : "f", NULL, &p)) {
190                 int len = strlen(p);
191                 if(len && p[len-1] == '/') p[len-1] = NULLCHAR;
192                 XtSetArg(args[0], XtNstring, p);
193                 XtSetValues(currentOption[data].handle, args, 1);
194         }
195         browserUp = False;
196         SetFocus(currentOption[data].handle, shells[TransientDlg], (XEvent*) NULL, False);
197         return;
198     } else
199     if (strcmp(name, "+") == 0) {
200         if(++j > currentOption[data].max) return;
201     } else
202     if (strcmp(name, "-") == 0) {
203         if(--j < currentOption[data].min) return;
204     } else return;
205     snprintf(buf, MSG_SIZ,  "%d", j);
206     SetWidgetText(&currentOption[data], buf, TransientDlg);
207 }
208
209 static void
210 ComboSelect (Widget w, caddr_t addr, caddr_t index) // callback for all combo items
211 {
212     Arg args[16];
213     int i = ((intptr_t)addr)>>8;
214     int j = 255 & (intptr_t) addr;
215
216     values[i] = j; // store in temporary, for transfer at OK
217
218     if(currentOption[i].min & NO_GETTEXT)
219       XtSetArg(args[0], XtNlabel, ((char**)currentOption[i].textValue)[j]);
220     else
221       XtSetArg(args[0], XtNlabel, _(((char**)currentOption[i].textValue)[j]));
222
223     XtSetValues(currentOption[i].handle, args, 1);
224
225     if(currentOption[i].min & COMBO_CALLBACK && !currentCps && comboCallback) (comboCallback)(i);
226 }
227
228 static void
229 CreateComboPopup (Widget parent, Option *option, int n)
230 {
231     int i=0, j;
232     Widget menu, entry;
233     Arg args[16];
234     char **mb = (char **) option->textValue;
235
236     if(mb[0] == NULL) return; // avoid empty menus, as they cause crash
237     menu = XtCreatePopupShell(option->name, simpleMenuWidgetClass,
238                               parent, NULL, 0);
239     j = 0;
240     XtSetArg(args[j], XtNwidth, 100);  j++;
241     while (mb[i] != NULL) 
242       {
243         if (option->min & NO_GETTEXT)
244           XtSetArg(args[j], XtNlabel, mb[i]);
245         else
246           XtSetArg(args[j], XtNlabel, _(mb[i]));
247         entry = XtCreateManagedWidget((String) mb[i], smeBSBObjectClass,
248                                       menu, args, j+1);
249         XtAddCallback(entry, XtNcallback,
250                       (XtCallbackProc) ComboSelect,
251                       (caddr_t)(intptr_t) (256*n+i));
252         i++;
253       }
254 }
255
256 char moveTypeInTranslations[] =
257     "<Key>Return: TypeInProc(1) \n"
258     "<Key>Escape: TypeInProc(0) \n";
259
260
261 char *translationTable[] = {
262    historyTranslations, commentTranslations, moveTypeInTranslations, ICSInputTranslations,
263 };
264
265 void
266 AddHandler (Option *opt, int nr)
267 {
268     XtOverrideTranslations(opt->handle, XtParseTranslationTable(translationTable[nr]));
269 }
270
271 //----------------------------Generic dialog --------------------------------------------
272
273 // cloned from Engine Settings dialog (and later merged with it)
274
275 Widget shells[NrOfDialogs];
276 WindowPlacement *wp[NrOfDialogs] = { NULL, &wpComment, &wpTags, NULL, NULL, NULL, NULL, &wpMoveHistory };
277 static Option *dialogOptions[NrOfDialogs];
278
279 int
280 DialogExists (DialogClass n)
281 {
282     return shells[n] != NULL;
283 }
284
285 int
286 PopDown (DialogClass n)
287 {
288     int j;
289     Arg args[10];
290     Dimension windowH, windowW; Position windowX, windowY;
291     if (!shellUp[n]) return 0;
292     if(n && wp[n]) { // remember position
293         j = 0;
294         XtSetArg(args[j], XtNx, &windowX); j++;
295         XtSetArg(args[j], XtNy, &windowY); j++;
296         XtSetArg(args[j], XtNheight, &windowH); j++;
297         XtSetArg(args[j], XtNwidth, &windowW); j++;
298         XtGetValues(shells[n], args, j);
299         wp[n]->x = windowX;
300         wp[n]->x = windowY;
301         wp[n]->width  = windowW;
302         wp[n]->height = windowH;
303     }
304     previous = NULL;
305     XtPopdown(shells[n]);
306     if(n == 0) XtDestroyWidget(shells[n]);
307     shellUp[n] = False;
308     if(marked[n]) {
309         MarkMenuItem(marked[n], False);
310         marked[n] = NULL;
311     }
312     if(!n) currentCps = NULL; // if an Engine Settings dialog was up, we must be popping it down now
313     return 1;
314 }
315
316 void
317 GenericPopDown (Widget w, XEvent *event, String *prms, Cardinal *nprms)
318 {
319     if(browserUp || dialogError) return; // prevent closing dialog when it has an open file-browse daughter
320     PopDown(prms[0][0] - '0');
321 }
322
323 int
324 AppendText (Option *opt, char *s)
325 {
326     XawTextBlock t;
327     char *v;
328     int len;
329     GetWidgetText(opt, &v);
330     len = strlen(v);
331     t.ptr = s; t.firstPos = 0; t.length = strlen(s); t.format = XawFmt8Bit;
332     XawTextReplace(opt->handle, len, len, &t);
333     return len;
334 }
335
336 void
337 SetColor (char *colorName, Option *box)
338 {
339         Arg args[5];
340         Pixel buttonColor;
341         XrmValue vFrom, vTo;
342         if (!appData.monoMode) {
343             vFrom.addr = (caddr_t) colorName;
344             vFrom.size = strlen(colorName);
345             XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
346             if (vTo.addr == NULL) {
347                 buttonColor = (Pixel) -1;
348             } else {
349                 buttonColor = *(Pixel *) vTo.addr;
350             }
351         } else buttonColor = timerBackgroundPixel;
352         XtSetArg(args[0], XtNbackground, buttonColor);;
353         XtSetValues(box->handle, args, 1);
354 }
355
356 void
357 ColorChanged (Widget w, XtPointer data, XEvent *event, Boolean *b)
358 {
359     char buf[10];
360     if ( (XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1) && *buf == '\r' )
361         RefreshColor((int)(intptr_t) data, 0);
362 }
363
364 void
365 GenericCallback (Widget w, XtPointer client_data, XtPointer call_data)
366 {
367     String name;
368     Arg args[16];
369     char buf[MSG_SIZ];
370     int data = (intptr_t) client_data;
371
372     currentOption = dialogOptions[data>>16]; data &= 0xFFFF;
373
374     XtSetArg(args[0], XtNlabel, &name);
375     XtGetValues(w, args, 1);
376
377     if (strcmp(name, _("cancel")) == 0) {
378         PopDown(data);
379         return;
380     }
381     if (strcmp(name, _("OK")) == 0) { // save buttons imply OK
382         if(GenericReadout(currentOption, -1)) PopDown(data);
383         return;
384     }
385     if(currentCps) {
386         if(currentOption[data].type == SaveButton) GenericReadout(currentOption, -1);
387         snprintf(buf, MSG_SIZ,  "option %s\n", name);
388         SendToProgram(buf, currentCps);
389     } else ((ButtonCallback*) currentOption[data].target)(data);
390 }
391
392 static char *oneLiner  = "<Key>Return:  redraw-display()\n";
393
394 int
395 GenericPopUp (Option *option, char *title, DialogClass dlgNr)
396 {
397     Arg args[16];
398     Widget popup, layout, dialog=NULL, edit=NULL, form,  last, b_ok, b_cancel, leftMargin = NULL, textField = NULL;
399     Window root, child;
400     int x, y, i, j, height=999, width=1, h, c, w, shrink=FALSE;
401     int win_x, win_y, maxWidth, maxTextWidth;
402     unsigned int mask;
403     char def[MSG_SIZ], *msg;
404     static char pane[6] = "paneX";
405     Widget texts[100], forelast = NULL, anchor, widest, lastrow = NULL, browse = NULL;
406     Dimension bWidth = 50;
407
408     if(shellUp[dlgNr]) return 0; // already up          
409     if(dlgNr && shells[dlgNr]) {
410         XtPopup(shells[dlgNr], XtGrabNone);
411         shellUp[dlgNr] = True;
412         return 0;
413     }
414
415     dialogOptions[dlgNr] = option; // make available to callback
416     // post currentOption globally, so Spin and Combo callbacks can already use it
417     // WARNING: this kludge does not work for persistent dialogs, so that these cannot have spin or combo controls!
418     currentOption = option;
419
420     if(currentCps) { // Settings popup for engine: format through heuristic
421         int n = currentCps->nrOptions;
422         if(!n) { DisplayNote(_("Engine has no options")); currentCps = NULL; return 0; }
423         if(n > 50) width = 4; else if(n>24) width = 2; else width = 1;
424         height = n / width + 1;
425         if(n && (currentOption[n-1].type == Button || currentOption[n-1].type == SaveButton)) currentOption[n].min = SAME_ROW; // OK on same line
426         currentOption[n].type = EndMark; currentOption[n].target = NULL; // delimit list by callback-less end mark
427     }
428      i = 0;
429     XtSetArg(args[i], XtNresizable, True); i++;
430     popup = shells[dlgNr] =
431       XtCreatePopupShell(title, transientShellWidgetClass,
432                          shellWidget, args, i);
433
434     layout =
435       XtCreateManagedWidget(layoutName, formWidgetClass, popup,
436                             layoutArgs, XtNumber(layoutArgs));
437   for(c=0; c<width; c++) {
438     pane[4] = 'A'+c;
439     form =
440       XtCreateManagedWidget(pane, formWidgetClass, layout,
441                             formArgs, XtNumber(formArgs));
442     j=0;
443     XtSetArg(args[j], XtNfromHoriz, leftMargin);  j++;
444     XtSetValues(form, args, j);
445     leftMargin = form;
446
447     last = widest = NULL; anchor = lastrow;
448     for(h=0; h<height; h++) {
449         i = h + c*height;
450         if(option[i].type == EndMark) break;
451         lastrow = forelast;
452         forelast = last;
453         switch(option[i].type) {
454           case Fractional:
455             snprintf(def, MSG_SIZ,  "%.2f", *(float*)option[i].target);
456             option[i].value = *(float*)option[i].target;
457             goto tBox;
458           case Spin:
459             if(!currentCps) option[i].value = *(int*)option[i].target;
460             snprintf(def, MSG_SIZ,  "%d", option[i].value);
461           case TextBox:
462           case FileName:
463           case PathName:
464           tBox:
465             if(option[i].name[0]) {
466             j=0;
467             XtSetArg(args[j], XtNfromVert, last);  j++;
468             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
469             XtSetArg(args[j], XtNright, XtChainLeft); j++;
470             XtSetArg(args[j], XtNheight, textHeight),  j++;
471             XtSetArg(args[j], XtNborderWidth, 0);  j++;
472             XtSetArg(args[j], XtNjustify, XtJustifyLeft);  j++;
473             XtSetArg(args[j], XtNlabel, _(option[i].name));  j++;
474             texts[h] =
475             dialog = XtCreateManagedWidget(option[i].name, labelWidgetClass, form, args, j);
476             } else texts[h] = dialog = NULL;
477             w = option[i].type == Spin || option[i].type == Fractional ? 70 : option[i].max ? option[i].max : 205;
478             if(option[i].type == FileName || option[i].type == PathName) w -= 55;
479             j=0;
480             XtSetArg(args[j], XtNfromVert, last);  j++;
481             XtSetArg(args[j], XtNfromHoriz, dialog);  j++;
482             XtSetArg(args[j], XtNborderWidth, 1); j++;
483             XtSetArg(args[j], XtNwidth, w); j++;
484             if(option[i].type == TextBox && option[i].min) {
485                 XtSetArg(args[j], XtNheight, option[i].min); j++;
486                 if(option[i].value & 1) { XtSetArg(args[j], XtNscrollVertical, XawtextScrollAlways);  j++; }
487                 if(option[i].value & 2) { XtSetArg(args[j], XtNscrollHorizontal, XawtextScrollAlways);  j++; }
488                 if(option[i].value & 4) { XtSetArg(args[j], XtNautoFill, True);  j++; }
489                 if(option[i].value & 8) { XtSetArg(args[j], XtNwrap, XawtextWrapWord); j++; }
490             } else shrink = TRUE;
491             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
492             XtSetArg(args[j], XtNeditType, XawtextEdit);  j++;
493             XtSetArg(args[j], XtNuseStringInPlace, False);  j++;
494             XtSetArg(args[j], XtNdisplayCaret, False);  j++;
495             XtSetArg(args[j], XtNright, XtChainRight);  j++;
496             XtSetArg(args[j], XtNresizable, True);  j++;
497             XtSetArg(args[j], XtNinsertPosition, 9999);  j++;
498             XtSetArg(args[j], XtNstring, option[i].type==Spin || option[i].type==Fractional ? def : 
499                                 currentCps ? option[i].textValue : *(char**)option[i].target);  j++;
500             edit = last;
501             option[i].handle = (void*)
502                 (textField = last = XtCreateManagedWidget("text", asciiTextWidgetClass, form, args, j));
503             XtAddEventHandler(last, ButtonPressMask, False, SetFocus, (XtPointer) popup);
504             if(option[i].min == 0 || option[i].type != TextBox)
505                 XtOverrideTranslations(last, XtParseTranslationTable(oneLiner));
506
507             if(option[i].type == TextBox || option[i].type == Fractional) break;
508
509             // add increment and decrement controls for spin
510             j=0;
511             XtSetArg(args[j], XtNfromVert, edit);  j++;
512             XtSetArg(args[j], XtNfromHoriz, last);  j++;
513             XtSetArg(args[j], XtNleft, XtChainRight); j++;
514             XtSetArg(args[j], XtNright, XtChainRight); j++;
515             if(option[i].type == FileName || option[i].type == PathName) {
516                 msg = _("browse"); w = 0;
517                 /* automatically scale to width of text */
518                 XtSetArg(args[j], XtNwidth, (XtArgVal) NULL );  j++;
519                 if(textHeight) XtSetArg(args[j], XtNheight, textHeight),  j++;
520             } else {
521                 w = 20; msg = "+";
522                 XtSetArg(args[j], XtNheight, textHeight/2);  j++;
523                 XtSetArg(args[j], XtNwidth,   w);  j++;
524             }
525             edit = XtCreateManagedWidget(msg, commandWidgetClass, form, args, j);
526             XtAddCallback(edit, XtNcallback, SpinCallback, (XtPointer)(intptr_t) i);
527             if(w == 0) browse = edit;
528
529             if(option[i].type != Spin) break;
530
531             j=0;
532             XtSetArg(args[j], XtNfromVert, edit);  j++;
533             XtSetArg(args[j], XtNfromHoriz, last);  j++;
534             XtSetArg(args[j], XtNvertDistance, -1);  j++;
535             XtSetArg(args[j], XtNheight, textHeight/2);  j++;
536             XtSetArg(args[j], XtNwidth, 20);  j++;
537             XtSetArg(args[j], XtNleft, XtChainRight); j++;
538             XtSetArg(args[j], XtNright, XtChainRight); j++;
539             last = XtCreateManagedWidget("-", commandWidgetClass, form, args, j);
540             XtAddCallback(last, XtNcallback, SpinCallback, (XtPointer)(intptr_t) i);
541             break;
542           case CheckBox:
543             if(!currentCps) option[i].value = *(Boolean*)option[i].target;
544             j=0;
545             XtSetArg(args[j], XtNfromVert, last);  j++;
546             XtSetArg(args[j], XtNvertDistance, (textHeight+2)/4 + 3);  j++;
547             XtSetArg(args[j], XtNwidth, textHeight/2);  j++;
548             XtSetArg(args[j], XtNheight, textHeight/2);  j++;
549             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
550             XtSetArg(args[j], XtNright, XtChainLeft); j++;
551             XtSetArg(args[j], XtNstate, option[i].value);  j++;
552             option[i].handle = (void*)
553                 (dialog = XtCreateManagedWidget(" ", toggleWidgetClass, form, args, j));
554           case Label:
555             msg = option[i].name;
556             if(*msg == NULLCHAR) msg = option[i].textValue;
557             if(!msg) break;
558             j=0;
559             XtSetArg(args[j], XtNfromVert, last);  j++;
560             XtSetArg(args[j], XtNfromHoriz, option[i].type != Label ? dialog : NULL);  j++;
561             if(option[i].type != Label) XtSetArg(args[j], XtNheight, textHeight),  j++, shrink = TRUE;
562             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
563             XtSetArg(args[j], XtNborderWidth, 0);  j++;
564             XtSetArg(args[j], XtNjustify, XtJustifyLeft);  j++;
565             XtSetArg(args[j], XtNlabel, _(msg));  j++;
566             last = XtCreateManagedWidget(msg, labelWidgetClass, form, args, j);
567             if(option[i].type == CheckBox)
568                 XtAddEventHandler(last, ButtonPressMask, False, CheckCallback, (XtPointer)(intptr_t) i);
569             break;
570           case SaveButton:
571           case Button:
572             j=0;
573             if(option[i].min & SAME_ROW) {
574                 XtSetArg(args[j], XtNfromVert, lastrow);  j++;
575                 XtSetArg(args[j], XtNfromHoriz, last);  j++;
576                 XtSetArg(args[j], XtNleft, XtChainRight); j++;
577                 XtSetArg(args[j], XtNright, XtChainRight); j++;
578                 if(shrink) XtSetArg(args[j], XtNheight, textHeight),  j++;
579             } else {
580                 XtSetArg(args[j], XtNfromVert, last);  j++;
581                 XtSetArg(args[j], XtNfromHoriz, NULL);  j++; lastrow = forelast;
582                 shrink = FALSE;
583             }
584             XtSetArg(args[j], XtNlabel, _(option[i].name));  j++;
585             if(option[i].max) { XtSetArg(args[j], XtNwidth, option[i].max);  j++; }
586             if(option[i].textValue) { // special for buttons of New Variant dialog
587                 XtSetArg(args[j], XtNsensitive, appData.noChessProgram || option[i].value < 0
588                                          || strstr(first.variants, VariantName(option[i].value))); j++;
589                 XtSetArg(args[j], XtNborderWidth, (gameInfo.variant == option[i].value)+1); j++;
590             }
591             option[i].handle = (void*)
592                 (dialog = last = XtCreateManagedWidget(option[i].name, commandWidgetClass, form, args, j));
593             if(option[i].choice && ((char*)option[i].choice)[0] == '#' && !currentCps) {
594                 SetColor( *(char**) option[i-1].target, &option[i]);
595                 XtAddEventHandler(option[i-1].handle, KeyReleaseMask, False, ColorChanged, (XtPointer)(intptr_t) i-1);
596             }
597             XtAddCallback(last, XtNcallback, GenericCallback,
598                           (XtPointer)(intptr_t) i + (dlgNr<<16));
599             if(option[i].textValue) SetColor( option[i].textValue, &option[i]);
600             forelast = lastrow; // next button can go on same row
601             break;
602           case ComboBox:
603             j=0;
604             XtSetArg(args[j], XtNfromVert, last);  j++;
605             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
606             XtSetArg(args[j], XtNright, XtChainLeft); j++;
607             XtSetArg(args[j], XtNheight, textHeight),  j++;
608             XtSetArg(args[j], XtNborderWidth, 0);  j++;
609             XtSetArg(args[j], XtNjustify, XtJustifyLeft);  j++;
610             XtSetArg(args[j], XtNlabel, _(option[i].name));  j++;
611             texts[h] = dialog = XtCreateManagedWidget(option[i].name, labelWidgetClass, form, args, j);
612
613             if(currentCps) option[i].choice = (char**) option[i].textValue; else {
614               for(j=0; option[i].choice[j]; j++)
615                 if(*(char**)option[i].target && !strcmp(*(char**)option[i].target, option[i].choice[j])) break;
616               option[i].value = j + (option[i].choice[j] == NULL);
617             }
618
619             j=0;
620             XtSetArg(args[j], XtNfromVert, last);  j++;
621             XtSetArg(args[j], XtNfromHoriz, dialog);  j++;
622             XtSetArg(args[j], XtNwidth, option[i].max && !currentCps ? option[i].max : 100);  j++;
623             XtSetArg(args[j], XtNleft, XtChainLeft); j++;
624             XtSetArg(args[j], XtNmenuName, XtNewString(option[i].name));  j++;
625             XtSetArg(args[j], XtNlabel, _(((char**)option[i].textValue)[option[i].value]));  j++;
626             XtSetArg(args[j], XtNheight, textHeight),  j++;
627             shrink = TRUE;
628             option[i].handle = (void*)
629                 (last = XtCreateManagedWidget(" ", menuButtonWidgetClass, form, args, j));
630             CreateComboPopup(last, option + i, i);
631             values[i] = option[i].value;
632             break;
633           case Break:
634             width++;
635             height = i+1;
636             break;
637         default:
638             printf("GenericPopUp: unexpected case in switch.\n");
639             break;
640         }
641     }
642
643     // make an attempt to align all spins and textbox controls
644     maxWidth = maxTextWidth = 0;
645     if(browse != NULL) {
646         j=0;
647         XtSetArg(args[j], XtNwidth, &bWidth);  j++;
648         XtGetValues(browse, args, j);
649     }
650     for(h=0; h<height; h++) {
651         i = h + c*height;
652         if(option[i].type == EndMark) break;
653         if(option[i].type == Spin || option[i].type == TextBox || option[i].type == ComboBox
654                                   || option[i].type == PathName || option[i].type == FileName) {
655             Dimension w;
656             if(!texts[h]) continue;
657             j=0;
658             XtSetArg(args[j], XtNwidth, &w);  j++;
659             XtGetValues(texts[h], args, j);
660             if(option[i].type == Spin) {
661                 if(w > maxWidth) maxWidth = w;
662                 widest = texts[h];
663             } else {
664                 if(w > maxTextWidth) maxTextWidth = w;
665                 if(!widest) widest = texts[h];
666             }
667         }
668     }
669     if(maxTextWidth + 110 < maxWidth)
670          maxTextWidth = maxWidth - 110;
671     else maxWidth = maxTextWidth + 110;
672     for(h=0; h<height; h++) {
673         i = h + c*height;
674         if(option[i].type == EndMark) break;
675         if(!texts[h]) continue; // Note: texts[h] can be undefined (giving errors in valgrind), but then both if's below will be false.
676         j=0;
677         if(option[i].type == Spin) {
678             XtSetArg(args[j], XtNwidth, maxWidth);  j++;
679             XtSetValues(texts[h], args, j);
680         } else
681         if(option[i].type == TextBox || option[i].type == ComboBox || option[i].type == PathName || option[i].type == FileName) {
682             XtSetArg(args[j], XtNwidth, maxTextWidth);  j++;
683             XtSetValues(texts[h], args, j);
684             if(bWidth != 50 && (option[i].type == FileName || option[i].type == PathName)) {
685                 int tWidth = (option[i].max ? option[i].max : 205) - 5 - bWidth;
686                 j = 0;
687                 XtSetArg(args[j], XtNwidth, tWidth);  j++;
688                 XtSetValues(option[i].handle, args, j);
689             }
690         }
691     }
692   }
693
694   if(!(option[i].min & NO_OK)) {
695     j=0;
696     if(option[i].min & SAME_ROW) {
697         for(j=i-1; option[j+1].min & SAME_ROW && option[j].type == Button; j--) {
698             XtSetArg(args[0], XtNtop, XtChainBottom);
699             XtSetArg(args[1], XtNbottom, XtChainBottom);
700             XtSetValues(option[j].handle, args, 2);
701         }
702         if(option[j].type == TextBox && option[j].name[0] == NULLCHAR) {
703             XtSetArg(args[0], XtNbottom, XtChainBottom);
704             XtSetValues(option[j].handle, args, 1);
705         }
706         j = 0;
707         XtSetArg(args[j], XtNfromHoriz, last); last = forelast;
708     } else shrink = FALSE,
709     XtSetArg(args[j], XtNfromHoriz, widest ? widest : dialog);  j++;
710     XtSetArg(args[j], XtNfromVert, anchor ? anchor : last);  j++;
711     XtSetArg(args[j], XtNbottom, XtChainBottom);  j++;
712     XtSetArg(args[j], XtNtop, XtChainBottom);  j++;
713     XtSetArg(args[j], XtNleft, XtChainRight);  j++;
714     XtSetArg(args[j], XtNright, XtChainRight);  j++;
715     if(shrink) XtSetArg(args[j], XtNheight, textHeight),  j++;
716     b_ok = XtCreateManagedWidget(_("OK"), commandWidgetClass, form, args, j);
717     XtAddCallback(b_ok, XtNcallback, GenericCallback, (XtPointer)(intptr_t) dlgNr + (dlgNr<<16));
718
719     XtSetArg(args[0], XtNfromHoriz, b_ok);
720     b_cancel = XtCreateManagedWidget(_("cancel"), commandWidgetClass, form, args, j);
721     XtAddCallback(b_cancel, XtNcallback, GenericCallback, (XtPointer)(intptr_t) dlgNr);
722   }
723
724     XtRealizeWidget(popup);
725     XSetWMProtocols(xDisplay, XtWindow(popup), &wm_delete_window, 1);
726     snprintf(def, MSG_SIZ, "<Message>WM_PROTOCOLS: GenericPopDown(\"%d\") \n", dlgNr);
727     XtAugmentTranslations(popup, XtParseTranslationTable(def));
728     XQueryPointer(xDisplay, xBoardWindow, &root, &child,
729                   &x, &y, &win_x, &win_y, &mask);
730
731     XtSetArg(args[0], XtNx, x - 10);
732     XtSetArg(args[1], XtNy, y - 30);
733     XtSetValues(popup, args, 2);
734
735     XtPopup(popup, dlgNr ? XtGrabNone : XtGrabExclusive);
736     shellUp[dlgNr] = True;
737     previous = NULL;
738     if(textField)SetFocus(textField, popup, (XEvent*) NULL, False);
739     if(dlgNr && wp[dlgNr] && wp[dlgNr]->width > 0) { // if persistent window-info available, reposition
740         j = 0;
741         XtSetArg(args[j], XtNheight, (Dimension) (wp[dlgNr]->height));  j++;
742         XtSetArg(args[j], XtNwidth,  (Dimension) (wp[dlgNr]->width));  j++;
743         XtSetArg(args[j], XtNx, (Position) (wp[dlgNr]->x));  j++;
744         XtSetArg(args[j], XtNy, (Position) (wp[dlgNr]->y));  j++;
745         XtSetValues(popup, args, j);
746     }
747     return 1;
748 }
749
750
751 /* function called when the data to Paste is ready */
752 static void
753 SendTextCB (Widget w, XtPointer client_data, Atom *selection,
754             Atom *type, XtPointer value, unsigned long *len, int *format)
755 {
756   char buf[MSG_SIZ], *p = (char*) textOptions[(int)(intptr_t) client_data].choice, *name = (char*) value, *q;
757   if (value==NULL || *len==0) return; /* nothing selected, abort */
758   name[*len]='\0';
759   strncpy(buf, p, MSG_SIZ);
760   q = strstr(p, "$name");
761   snprintf(buf + (q-p), MSG_SIZ -(q-p), "%s%s", name, q+5);
762   SendString(buf);
763   XtFree(value);
764 }
765
766 void
767 SendText (int n)
768 {
769     char *p = (char*) textOptions[n].choice;
770     if(strstr(p, "$name")) {
771         XtGetSelectionValue(menuBarWidget,
772           XA_PRIMARY, XA_STRING,
773           /* (XtSelectionCallbackProc) */ SendTextCB,
774           (XtPointer) (intptr_t) n, /* client_data passed to PastePositionCB */
775           CurrentTime
776         );
777     } else SendString(p);
778 }
779
780 void
781 SetInsertPos (Option *opt, int pos)
782 {
783     Arg args[16];
784     XtSetArg(args[0], XtNinsertPosition, pos);
785     XtSetValues(opt->handle, args, 1);
786 //    SetFocus(opt->handle, shells[InputBoxDlg], NULL, False); // No idea why this does not work, and the following is needed:
787     XSetInputFocus(xDisplay, XtWindow(opt->handle), RevertToPointerRoot, CurrentTime);
788 }
789
790 void
791 TypeInProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
792 {
793     char *val;
794
795     if(prms[0][0] == '1') {
796         GetWidgetText(&boxOptions[0], &val);
797         TypeInDoneEvent(val);
798     }
799     PopDown(TransientDlg);
800 }
801
802 void
803 HardSetFocus (Option *opt)
804 {
805     XSetInputFocus(xDisplay, XtWindow(opt->handle), RevertToPointerRoot, CurrentTime);
806 }
807
808