Fix multi-leg promotions
[xboard.git] / xaw / xoptions.c
index 7bbff9b..1c0a897 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * xoptions.c -- Move list window, part of X front end for XBoard
  *
- * Copyright 2000, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * Copyright 2000, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
  * ------------------------------------------------------------------------
  *
  * GNU XBoard is free software: you can redistribute it and/or modify
@@ -181,6 +181,11 @@ SetWidgetState (Option *opt, int state)
 }
 
 void
+WidgetEcho (Option *opt, int state)
+{
+}
+
+void
 SetWidgetLabel (Option *opt, char *buf)
 {
     Arg arg;
@@ -189,6 +194,12 @@ SetWidgetLabel (Option *opt, char *buf)
 }
 
 void
+SetComboChoice (Option *opt, int n)
+{
+    SetWidgetText(opt, opt->choice[n], MasterDlg);
+}
+
+void
 SetDialogTitle (DialogClass dlg, char *title)
 {
     Arg args[16];
@@ -197,11 +208,12 @@ SetDialogTitle (DialogClass dlg, char *title)
 }
 
 void
-LoadListBox (Option *opt, char *emptyText)
+LoadListBox (Option *opt, char *emptyText, int n1, int n2)
 {
     static char *dummyList[2];
     dummyList[0] = emptyText; // empty listboxes tend to crash X, so display user-supplied warning string instead
-    XawListChange(opt->handle, *(char*)opt->target ? opt->target : dummyList, 0, 0, True);
+    XawListChange(opt->handle, *(char**)opt->target ? opt->target : dummyList, 0, 0, True);
+//printf("listbox data = %x\n", opt->target);
 }
 
 int
@@ -265,8 +277,50 @@ SelectedListBoxItem (Option *opt)
 }
 
 void
+SetTextColor (char **cnames, int fg, int bg, int attr)
+{ // this is not possible in Xaw
+}
+
+void
+AppendColorized (Option *opt, char *message, int count)
+{
+  if(!opt->handle) return;
+  AppendText(opt, message);
+}
+
+void
+ApplyFont (Option *opt, char *font)
+{ // dummy
+}
+
+void
+Show (Option *opt, int hide)
+{
+    static Dimension h;
+    Arg args[16];
+    Dimension v;
+    int j=0;
+return; // FIXME: it would be nice if the Chat window did have an ICS pane we could hide behind
+//printf("Show(%d) %x\n", hide, opt->handle);
+    if(!opt->handle) return;
+    if(hide) { // make sure original size is saved
+      XtSetArg(args[j], XtNheight, &v); j++;
+      XtGetValues(opt->handle, args, j);
+      if(v != 1) h = v;
+    }
+printf("h = %d\n",h);
+    j = 0;
+    XtSetArg(args[j], XtNheight, hide ? 1 : h); j++;
+    XtSetValues(opt->handle, args, j);
+}
+
+void
 HighlightText (Option *opt, int start, int end, Boolean on)
 {
+    if(on)
+       XawTextSetSelection( opt->handle, start, end ); // for lack of a better method, use selection for highighting
+    else
+       XawTextSetSelection( opt->handle, 0, 0 );
 }
 
 void
@@ -287,13 +341,21 @@ SetIconName (DialogClass dlg, char *name)
 }
 
 static void
+LabelCallback (Widget ww, XtPointer client_data, XEvent *event, Boolean *b)
+{   // called on ButtonPress in label widgets with attached user handler (clocks!)
+    int s, data = (intptr_t) client_data;
+    Option *opt = dialogOptions[data >> 8] + (s = data & 255);
+
+    if(((XButtonEvent*)event)->button != Button1) s = -s;
+    ((ButtonCallback*) opt->target) (s);
+}
+
+static void
 CheckCallback (Widget ww, XtPointer client_data, XEvent *event, Boolean *b)
 {
     int s, data = (intptr_t) client_data;
     Option *opt = dialogOptions[data >> 8] + (data & 255);
 
-    if(opt->type == Label) { ((ButtonCallback*) opt->target)(data&255); return; }
-
     GetWidgetState(opt, &s);
     SetWidgetState(opt, !s);
 }
@@ -328,7 +390,7 @@ SpinCallback (Widget w, XtPointer client_data, XtPointer call_data)
        if(--j < opt->min) return;
     } else return;
     snprintf(buf, MSG_SIZ,  "%d", j);
-    SetWidgetText(opt, buf, TransientDlg);
+    SetWidgetText(opt, buf, shellUp[TransientDlg] ? TransientDlg : MasterDlg);
 }
 
 static void
@@ -369,6 +431,100 @@ CreateMenuItem (Widget menu, char *msg, XtCallbackProc CB, int n)
     return entry;
 }
 
+char *
+format_accel (char *input)
+{
+  char *output;
+  char *key,*test;
+
+  output = strdup("");
+
+  if( strstr(input, "<Ctrl>") )
+    {
+      output = realloc(output, strlen(output) + strlen(_("Ctrl"))+2);
+      strncat(output, _("Ctrl"), strlen(_("Ctrl")) +1);
+      strncat(output, "+", 1);
+    };
+  if( strstr(input, "<Alt>") )
+    {
+      output = realloc(output, strlen(output) + strlen(_("Alt"))+2);
+      strncat(output, _("Alt"), strlen(_("Alt")) +1);
+      strncat(output, "+", 1);
+    };
+  if( strstr(input, "<Shift>") )
+    {
+      output = realloc(output, strlen(output) + strlen(_("Shift"))+2);
+      strncat(output, _("Shift"), strlen(_("Shift")) +1);
+      strncat(output, "+", 1);
+    };
+
+  test = strrchr(input, '>');
+  if ( test==NULL )
+    key = strdup(input);
+  else
+    key = strdup(++test); // remove ">"
+  if(strlen(key) == 1) key[0] = ToUpper(key[0]);
+
+  output = realloc(output, strlen(output) + strlen(_(key))+2);
+  strncat(output, _(key), strlen(_(key)) +1);
+
+  free(key);
+  return output;
+}
+
+int
+pixlen (char *s)
+{
+#if 0
+    int dummy;
+    XCharStruct overall;
+    XTextExtents(messageFontStruct, s, strlen(s), &dummy, &dummy, &dummy, &overall);
+    return overall.width;
+#else
+    float tot = 0;
+    while(*s) switch(*s++) {
+       case '.': tot += 0.45; break;
+       case ' ': tot += 0.55; break;
+       case 'i': tot += 0.45; break;
+       case 'l': tot += 0.45; break;
+       case 'j': tot += 0.45; break;
+       case 'f': tot += 0.45; break;
+       case 'I': tot += 0.45; break;
+       case 't': tot += 0.45; break;
+       case 'k': tot += 0.83; break;
+       case 's': tot += 0.83; break;
+       case 'x': tot += 0.83; break;
+       case 'z': tot += 0.83; break;
+       case 'r': tot += 0.55; break;
+       case 'w': tot += 1.3; break;
+       case 'm': tot += 1.3; break;
+       case 'A': tot += 1.3; break;
+       case 'C': tot += 1.3; break;
+       case 'D': tot += 1.3; break;
+       case 'G': tot += 1.3; break;
+       case 'H': tot += 1.3; break;
+       case 'N': tot += 1.3; break;
+       case 'V': tot += 1.3; break;
+       case 'X': tot += 1.3; break;
+       case 'Y': tot += 1.3; break;
+       case 'Z': tot += 1.3; break;
+       case 'M': tot += 1.6; break;
+       case 'W': tot += 1.6; break;
+       case 'B': tot += 1.1; break;
+       case 'E': tot += 1.1; break;
+       case 'F': tot += 1.1; break;
+       case 'K': tot += 1.1; break;
+       case 'P': tot += 1.1; break;
+       case 'R': tot += 1.1; break;
+       case 'S': tot += 1.1; break;
+       case 'O': tot += 1.4; break;
+       case 'Q': tot += 1.4; break;
+       default:  tot++;
+    }
+    return tot;
+#endif
+}
+
 static Widget
 CreateComboPopup (Widget parent, Option *opt, int n, int fromList, int def)
 {   // fromList determines if the item texts are taken from a list of strings, or from a menu table
@@ -377,20 +533,51 @@ CreateComboPopup (Widget parent, Option *opt, int n, int fromList, int def)
     Arg arg;
     MenuItem *mb = (MenuItem *) opt->choice;
     char **list = (char **) opt->choice;
+    int maxlength=0, menuLen[1000];
+
 
     if(list[0] == NULL) return NULL; // avoid empty menus, as they cause crash
     menu = XtCreatePopupShell(opt->name, simpleMenuWidgetClass, parent, NULL, 0);
 
-    for (i=0; 1; i++) 
+    if(!fromList)
+      for (i=0; mb[i].string; i++) if(mb[i].accel) {
+       int len = pixlen(_(mb[i].string));
+       menuLen[i] = len;
+       if (maxlength < len )
+         maxlength = len;
+      }
+
+    for (i=0; 1; i++)
       {
        char *msg = fromList ? list[i] : mb[i].string;
+       char *label=NULL;
+
        if(!msg) break;
-       entry = CreateMenuItem(menu, opt->min & NO_GETTEXT ? msg : _(msg), (XtCallbackProc) ComboSelect, (n<<16)+i);
+
+       if(!fromList && mb[i].accel)
+         {
+           char *menuname = opt->min & NO_GETTEXT ? msg : _(msg);
+           char *accel = format_accel(mb[i].accel);
+           size_t len;
+//         int fill = maxlength - strlen(menuname) +2+strlen(accel);
+           int fill = (maxlength - menuLen[i] + 3)*1.8;
+
+           len = strlen(menuname)+fill+strlen(accel)+1;
+           label = malloc(len);
+
+           snprintf(label,len,"%s%*s%s",menuname,fill," ",accel);
+           free(accel);
+         }
+       else
+         label = strdup(opt->min & NO_GETTEXT ? msg : _(msg));
+
+       entry = CreateMenuItem(menu, label, (XtCallbackProc) ComboSelect, (n<<16)+i);
        if(!fromList) mb[i].handle = (void*) entry; // save item ID, for enabling / checkmarking
        if(i==def) {
            XtSetArg(arg, XtNpopupOnEntry, entry);
            XtSetValues(menu, &arg, 1);
        }
+       free(label);
       }
       return menu;
 }
@@ -409,7 +596,7 @@ char *translationTable[] = { // beware: order is essential!
 };
 
 void
-AddHandler (Option *opt, int nr)
+AddHandler (Option *opt, DialogClass dlg, int nr)
 {
     XtOverrideTranslations(opt->handle, XtParseTranslationTable(translationTable[nr]));
 }
@@ -453,7 +640,7 @@ RaiseWindow (DialogClass dlg)
           SubstructureRedirectMask | SubstructureNotifyMask,
           &xev);
 
-    XFlush(xDisplay); 
+    XFlush(xDisplay);
     XSync(xDisplay, False);
 }
 
@@ -496,7 +683,8 @@ GenericPopDown (Widget w, XEvent *event, String *prms, Cardinal *nprms)
 {   // to cause popdown through a translation (Delete Window button!)
     int dlg = atoi(prms[0]);
     Widget sh = shells[dlg];
-    if(shellUp[BrowserDlg] && dlg != BrowserDlg || dialogError) return; // prevent closing dialog when it has an open file-browse daughter
+    if(shellUp[BrowserDlg] && dlg != BrowserDlg || dialogError || dlg == MasterDlg && shellUp[TransientDlg])
+       return; // prevent closing dialog when it has an open file-browse or transient daughter
     shells[dlg] = w;
     PopDown(dlg);
     shells[dlg] = sh; // restore
@@ -578,8 +766,15 @@ GraphEventProc(Widget widget, caddr_t client_data, XEvent *event)
                         // to give drawing routines opportunity to use it before first expose event
                         // (which are only processed when main gets to the event loop, so after all init!)
                         // so only change when size is no longer good
+               cairo_t *cr;
                if(graph->choice) cairo_surface_destroy((cairo_surface_t *) graph->choice);
                graph->choice = (char**) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
+               // paint white, to prevent weirdness when people maximize window and drag pieces over space next to board
+               cr = cairo_create ((cairo_surface_t *) graph->choice);
+               cairo_rectangle (cr, 0, 0, w, h);
+               cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
+               cairo_fill(cr);
+               cairo_destroy (cr);
                break;
            }
            w = ((XExposeEvent*)event)->width;
@@ -634,7 +829,7 @@ GenericCallback (Widget w, XtPointer client_data, XtPointer call_data)
     currentOption = dialogOptions[dlg=data>>16]; data &= 0xFFFF;
     oldSh = shells[dlg]; shells[dlg] = sh; // bow to reality
     if (data == 30000) { // cancel
-        PopDown(dlg); 
+        PopDown(dlg);
     } else
     if (data == 30001) { // save buttons imply OK
         if(GenericReadout(currentOption, -1)) PopDown(dlg); // calls OK-proc after full readout, but no popdown if it returns false
@@ -783,6 +978,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
        shellUp[dlgNr] = True;
        return 0;
     }
+    if(dlgNr == TransientDlg && parent == BoardWindow && shellUp[MasterDlg]) parent = MasterDlg; // MasterDlg can always take role of main window
 
     dialogOptions[dlgNr] = option; // make available to callback
     // post currentOption globally, so Spin and Combo callbacks can already use it
@@ -827,7 +1023,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
     for(h=0; h<height || c == width-1; h++) {
        i = h + c*height;
        if(option[i].type == EndMark) break;
-       if(option[i].type == -1) continue;
+       if(option[i].type == Skip) continue;
        lastrow = forelast;
        forelast = last;
        switch(option[i].type) {
@@ -851,6 +1047,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
            } else texts[h] = dialog = NULL; // kludge to position from left margin
            w = option[i].type == Spin || option[i].type == Fractional ? 70 : option[i].max ? option[i].max : 205;
            if(option[i].type == FileName || option[i].type == PathName) w -= 55;
+           if(squareSize > 33) w += (squareSize - 33)/2;
            j = SetPositionAndSize(args, dialog, last, 1 /* border */,
                                   w /* w */, option[i].type == TextBox ? option[i].value : 0 /* h */, 0x91 /* chain full width */);
            if(option[i].type == TextBox) { // decorations for multi-line text-edits
@@ -869,7 +1066,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
            XtSetArg(args[j], XtNdisplayCaret, False);  j++;
            XtSetArg(args[j], XtNresizable, True);  j++;
            XtSetArg(args[j], XtNinsertPosition, 9999);  j++;
-           XtSetArg(args[j], XtNstring, option[i].type==Spin || option[i].type==Fractional ? def : 
+           XtSetArg(args[j], XtNstring, option[i].type==Spin || option[i].type==Fractional ? def :
                                engineDlg ? option[i].textValue : *(char**)option[i].target);  j++;
            edit = last;
            option[i].handle = (void*)
@@ -937,7 +1134,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
            XtSetArg(args[j], XtNlabel, _(msg));  j++;
            option[i].handle = (void*) (last = XtCreateManagedWidget("label", labelWidgetClass, form, args, j));
            if(option[i].target) // allow user to specify event handler for button presses
-               XtAddEventHandler(last, ButtonPressMask, False, CheckCallback, (XtPointer)(intptr_t) i + 256*dlgNr);
+               XtAddEventHandler(last, ButtonPressMask, False, LabelCallback, (XtPointer)(intptr_t) i + 256*dlgNr);
            break;
          case SaveButton:
          case Button:
@@ -948,9 +1145,11 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
            j = SetPositionAndSize(args, last, lastrow, 3 /* border */,
                                   option[i].max /* w */, shrink ? textHeight : 0 /* h */, option[i].min & 0xE | chain /* chain */);
            XtSetArg(args[j], XtNlabel, _(option[i].name));  j++;
-           if(option[i].textValue) { // special for buttons of New Variant dialog
-               XtSetArg(args[j], XtNsensitive, appData.noChessProgram || option[i].value < 0
-                                        || strstr(first.variants, VariantName(option[i].value))); j++;
+           if(option[i].textValue && *option[i].textValue == '#') { // special for buttons of New Variant dialog
+               char *p = NULL, *v, n = option[i].value;
+               if(n >= 0) v = VariantName(n), p = strstr(first.variants, v);
+               XtSetArg(args[j], XtNsensitive, option[i].value >= 0 && (appData.noChessProgram
+                                        || p && (!*v || strlen(p) == strlen(v) || p[strlen(v)] == ','))); j++;
                XtSetArg(args[j], XtNborderWidth, (gameInfo.variant == option[i].value)+1); j++;
            }
            option[i].handle = (void*)
@@ -960,7 +1159,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
                XtAddEventHandler(option[i-1].handle, KeyReleaseMask, False, ColorChanged, (XtPointer)(intptr_t) i-1);
            }
            XtAddCallback(last, XtNcallback, GenericCallback, (XtPointer)(intptr_t) i + (dlgNr<<16)); // invokes user callback
-           if(option[i].textValue) SetColor( option[i].textValue, &option[i]); // for new-variant buttons
+           if(option[i].textValue && *option[i].textValue == '#') SetColor( option[i].textValue, &option[i]); // for new-variant buttons
            break;
          case ComboBox:
            j = SetPositionAndSize(args, last, lastrow, 0 /* border */,
@@ -1036,6 +1235,11 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
                                   0 /* w */, 0 /* h */, 1 /* chain (always on same row) */);
            forelast = lastrow;
            msg = _(option[i].name); // write name on the menu button
+           if(msg) { if(*msg == '_') msg++; else if(msg[1] == '_') { // kludge to remove GTK shortkut-key indicators
+               static char buf[MSG_SIZ];
+               strncpy(buf, msg, MSG_SIZ); msg = buf + 1;
+               *msg = *buf;
+           }}
            XtSetArg(args[j], XtNmenuName, XtNewString(option[i].name));  j++;
            XtSetArg(args[j], XtNlabel, msg);  j++;
            option[i].handle = (void*)
@@ -1050,6 +1254,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
            last = form; lastrow = oldLastRow; form = oldForm; forelast = oldForeLast;
            break;
          case Break:
+           if(c) break;
            width++;
            height = i+1;
            stack = !(option[i].min & SAME_ROW);
@@ -1133,7 +1338,7 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
     XtAddCallback(b_ok, XtNcallback, GenericCallback, (XtPointer)(intptr_t) (30001 + (dlgNr<<16)));
     if(!(option[i].min & NO_CANCEL)) {
       XtSetArg(args[1], XtNfromHoriz, b_ok); // overwrites!
-      b_cancel = XtCreateManagedWidget(_("cancel"), commandWidgetClass, form, args, j);
+      b_cancel = XtCreateManagedWidget(_("Cancel"), commandWidgetClass, form, args, j);
       XtAddCallback(b_cancel, XtNcallback, GenericCallback, (XtPointer)(intptr_t) (30000 + (dlgNr<<16)));
     }
   }
@@ -1154,13 +1359,17 @@ GenericPopUp (Option *option, char *title, DialogClass dlgNr, DialogClass parent
     shellUp[dlgNr]++; // count rather than flag
     previous = NULL;
     if(textField) SetFocus(textField, popup, (XEvent*) NULL, False);
-    if(dlgNr && wp[dlgNr] && wp[dlgNr]->width > 0) { // if persistent window-info available, reposition
+    if(dlgNr && wp[dlgNr]) { // if persistent window-info available, reposition
        j = 0;
-       XtSetArg(args[j], XtNheight, (Dimension) (wp[dlgNr]->height));  j++;
-       XtSetArg(args[j], XtNwidth,  (Dimension) (wp[dlgNr]->width));  j++;
-       XtSetArg(args[j], XtNx, (Position) (wp[dlgNr]->x));  j++;
-       XtSetArg(args[j], XtNy, (Position) (wp[dlgNr]->y));  j++;
-       XtSetValues(popup, args, j);
+       if(wp[dlgNr]->width > 0 && wp[dlgNr]->height > 0) {
+         XtSetArg(args[j], XtNheight, (Dimension) (wp[dlgNr]->height));  j++;
+         XtSetArg(args[j], XtNwidth,  (Dimension) (wp[dlgNr]->width));  j++;
+       }
+       if(wp[dlgNr]->x > 0 && wp[dlgNr]->y > 0) {
+         XtSetArg(args[j], XtNx, (Position) (wp[dlgNr]->x));  j++;
+         XtSetArg(args[j], XtNy, (Position) (wp[dlgNr]->y));  j++;
+       }
+       if(j) XtSetValues(popup, args, j);
     }
     RaiseWindow(dlgNr);
     return 1; // tells caller he must do initialization (e.g. add specific event handlers)
@@ -1200,6 +1409,11 @@ void
 SetInsertPos (Option *opt, int pos)
 {
     Arg args[16];
+    if(pos == 999999) { // this kludge to indicate end in GTK is fatal in Xaw
+      char *s;
+      GetWidgetText(opt, &s);
+      pos = strlen(s) - 1;
+    }
     XtSetArg(args[0], XtNinsertPosition, pos);
     XtSetValues(opt->handle, args, 1);
 //    SetFocus(opt->handle, shells[InputBoxDlg], NULL, False); // No idea why this does not work, and the following is needed:
@@ -1211,6 +1425,8 @@ TypeInProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
 {   // can be used as handler for any text edit in any dialog (from GenericPopUp, that is)
     int n = prms[0][0] - '0';
     Widget sh = XtParent(XtParent(XtParent(w))); // popup shell
+    extern int hidden;
+    hidden = 0;
 
     if(n<2) { // Enter or Esc typed from primed text widget: treat as if dialog OK or cancel button hit.
        int dlgNr; // figure out what the dialog number is by comparing shells (because we must pass it :( )
@@ -1220,14 +1436,18 @@ TypeInProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
 }
 
 void
-HardSetFocus (Option *opt)
+HardSetFocus (Option *opt, DialogClass dlg)
 {
     XSetInputFocus(xDisplay, XtWindow(opt->handle), RevertToPointerRoot, CurrentTime);
 }
 
 void
-FileNamePopUpGTK(char *label, char *def, char *filter, FileProc proc, Boolean pathFlag, char *openMode, char **openName, FILE **openFP)
+FileNamePopUpWrapper (char *label, char *def, char *filter, FileProc proc, Boolean pathFlag, char *openMode, char **openName, FILE **openFP)
 {
     Browse(BoardWindow, label, (def[0] ? def : NULL), filter, False, openMode, openName, openFP);
 }
 
+void
+LockBoardSize (int after)
+{
+}