8ba8a7ecbfe8768b7e5ba36bcb9c9095c0d0223c
[xboard.git] / xengineoutput.c
1 /*
2  * Engine output (PV)
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  *
6  * Copyright 2005 Alessandro Scotti
7  *
8  * Enhancements Copyright 2009 Free Software Foundation, Inc.
9  *
10  * ------------------------------------------------------------------------
11  *
12  * GNU XBoard is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or (at
15  * your option) any later version.
16  *
17  * GNU XBoard is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program. If not, see http://www.gnu.org/licenses/.
24  *
25  * ------------------------------------------------------------------------
26  ** See the file ChangeLog for a revision history.  */
27
28 #include "config.h"
29
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <sys/types.h>
34
35 #if STDC_HEADERS
36 # include <stdlib.h>
37 # include <string.h>
38 #else /* not STDC_HEADERS */
39 extern char *getenv();
40 # if HAVE_STRING_H
41 #  include <string.h>
42 # else /* not HAVE_STRING_H */
43 #  include <strings.h>
44 # endif /* not HAVE_STRING_H */
45 #endif /* not STDC_HEADERS */
46
47 #if HAVE_UNISTD_H
48 # include <unistd.h>
49 #endif
50
51 #include <X11/Intrinsic.h>
52 #include <X11/StringDefs.h>
53 #include <X11/Shell.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
69 #include "common.h"
70 #include "frontend.h"
71 #include "backend.h"
72 #include "xboard.h"
73 // Add xengineo.h later
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 #include <X11/xpm.h>
85
86 // [HGM] pixmaps of some ICONS used in the engine-outut window
87 #include "pixmaps/WHITE_14.xpm"
88 #include "pixmaps/BLACK_14.xpm"
89 #include "pixmaps/CLEAR_14.xpm"
90 #include "pixmaps/UNKNOWN_14.xpm"
91 #include "pixmaps/THINKING_14.xpm"
92 #include "pixmaps/PONDER_14.xpm"
93 #include "pixmaps/ANALYZING_14.xpm"
94
95 #ifdef SNAP
96 #include "wsnap.h"
97 #endif
98
99 #define _LL_ 100
100
101 // imports from xboard.c
102 extern Widget formWidget, shellWidget, boardWidget, menuBarWidget;
103 extern Display *xDisplay;
104 extern Window xBoardWindow;
105 extern int squareSize;
106 extern Pixmap xMarkPixmap, wIconPixmap, bIconPixmap;
107 extern char *layoutName;
108
109 // temporary kludge to avoid compile errors untill all Windows code has been replaced
110 #define HICON int *
111 #define HWND  int *
112
113 // [HGM] define numbers to indicate icons, for referring to them in platform-independent way
114 #define nColorBlack   1
115 #define nColorWhite   2
116 #define nColorUnknown 3
117 #define nClear        4
118 #define nPondering    5
119 #define nThinking     6
120 #define nAnalyzing    7
121
122 Pixmap icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle
123
124 // [HGM] same for output fields (note that there are two of each type, one per color)
125 #define nColorIcon 1
126 #define nStateIcon 2
127 #define nLabel     3
128 #define nStateData 4
129 #define nLabelNPS  5
130 #define nMemo      6
131
132 Widget outputField[2][7]; // [HGM] front-end array to translate output field to window handle
133
134 void EngineOutputPopDown();
135 void engineOutputPopUp();
136 int  EngineOutputIsUp();
137 static void SetEngineColorIcon( int which );
138
139 #define SHOW_PONDERING
140
141 /* Imports from backend.c */
142 char * SavePart(char *str);
143 extern int opponentKibitzes;
144
145 /* Imports from winboard.c */
146 //extern HWND engineOutputDialog;
147 extern Arg layoutArgs[2], formArgs[2], messageArgs[4];
148
149 //extern WindowPlacement wpEngineOutput;
150
151 Position engineOutputX = -1, engineOutputY = -1;
152 Dimension engineOutputW, engineOutputH;
153 Widget engineOutputShell;
154 int engineOutputDialogUp;
155
156 /* Module variables */
157 #define H_MARGIN            2
158 #define V_MARGIN            2
159 #define LABEL_V_DISTANCE    1   /* Distance between label and memo */
160 #define SPLITTER_SIZE       4   /* Distance between first memo and second label */
161
162 #define ICON_SIZE           14
163
164 #define STATE_UNKNOWN   -1
165 #define STATE_THINKING   0
166 #define STATE_IDLE       1
167 #define STATE_PONDERING  2
168 #define STATE_ANALYZING  3
169
170 static int  windowMode = 1;
171
172 static int  needInit = TRUE;
173
174 static int  lastDepth[2] = { -1, -1 };
175 static int  lastForwardMostMove[2] = { -1, -1 };
176 static int  engineState[2] = { -1, -1 };
177
178 typedef struct {
179     char * name;
180     int which;
181     int depth;
182     u64 nodes;
183     int score;
184     int time;
185     char * pv;
186     char * hint;
187     int an_move_index;
188     int an_move_count;
189 } EngineOutputData;
190
191 static void VerifyDisplayMode();
192 static void UpdateControls( EngineOutputData * ed );
193 static void SetEngineState( int which, int state, char * state_data );
194
195 void ReadIcon(char *pixData[], int iconNr)
196 {
197     int r;
198
199         if ((r=XpmCreatePixmapFromData(xDisplay, XtWindow(outputField[0][nColorIcon]),
200                                        pixData,
201                                        &(icons[iconNr]),
202                                        NULL, NULL /*&attr*/)) != 0) {
203           fprintf(stderr, _("Error %d loading icon image\n"), r);
204           exit(1); 
205         }       
206 }
207
208 static void InitializeEngineOutput()
209 { int i;
210
211         ReadIcon(WHITE_14,   nColorWhite);
212         ReadIcon(BLACK_14,   nColorBlack);
213         ReadIcon(UNKNOWN_14, nColorUnknown);
214
215         ReadIcon(CLEAR_14,   nClear);
216         ReadIcon(PONDER_14,  nPondering);
217         ReadIcon(THINK_14,   nThinking);
218         ReadIcon(ANALYZE_14, nAnalyzing);
219 }
220
221 void DoSetWindowText(int which, int field, char *s_label)
222
223         Arg arg;
224
225         XtSetArg(arg, XtNlabel, (XtArgVal) s_label);
226         XtSetValues(outputField[which][field], &arg, 1);
227 }
228
229 static void InsertIntoMemo( int which, char * text, int where )
230 {
231         Arg arg; XawTextBlock t; Widget edit;
232
233         t.ptr = text; t.firstPos = 0; t.length = strlen(text); t.format = XawFmt8Bit;
234         edit = XtNameToWidget(engineOutputShell, which ? "*form2.text" : "*form.text");
235         XawTextReplace(edit, where, where, &t);
236 //      XtSetArg(arg, XtNstring, (XtArgVal) text);
237 //      XtSetValues(outputField[which][nMemo], &arg, 1);
238 }
239
240 static void SetIcon( int which, int field, int nIcon )
241 {
242     Arg arg;
243
244     if( nIcon != 0 ) {
245         XtSetArg(arg, XtNleftBitmap, (XtArgVal) icons[nIcon]);
246         XtSetValues(outputField[which][field], &arg, 1);
247     }
248 }
249
250 void DoClearMemo(int which)
251
252     Arg args[16];
253     int j;
254     Widget edit;
255
256         edit = XtNameToWidget(engineOutputShell, which ? "*form2.text" : "*form.text");
257         XtCallActionProc(edit, "select-all", NULL, NULL, 0);
258         XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
259 }
260
261 // The following routines are mutated clones of the commentPopUp routines
262
263 void PositionControlSet(which, form, bw_width)
264      int which;
265      Widget form;
266      Dimension bw_width;
267 {
268     Arg args[16];
269     Widget edit, NameWidget, ColorWidget, ModeWidget, MoveWidget, NodesWidget;
270     int j, mutable=1;
271     j = 0;
272     XtSetArg(args[j], XtNborderWidth, (XtArgVal) 0); j++;
273     XtSetArg(args[j], XtNlabel,     (XtArgVal) ""); j++;
274     XtSetArg(args[j], XtNtop,       XtChainTop); j++;
275     XtSetArg(args[j], XtNbottom,    XtChainTop); j++;
276     XtSetArg(args[j], XtNleft,      XtChainLeft); j++;
277     XtSetArg(args[j], XtNright,     XtChainLeft); j++;
278     XtSetArg(args[j], XtNheight,    (XtArgVal) 16); j++;
279     XtSetArg(args[j], XtNwidth,     (XtArgVal) 17); j++;
280     outputField[which][nColorIcon] = ColorWidget =
281       XtCreateManagedWidget("Color", labelWidgetClass,
282                      form, args, j);
283
284     j = 0;
285     XtSetArg(args[j], XtNborderWidth, (XtArgVal) 0); j++;
286     XtSetArg(args[j], XtNjustify,   (XtArgVal) XtJustifyLeft); j++;
287     XtSetArg(args[j], XtNfromHoriz, (XtArgVal) ColorWidget); j++;
288     XtSetArg(args[j], XtNtop,       XtChainTop); j++;
289     XtSetArg(args[j], XtNbottom,    XtChainTop); j++;
290     XtSetArg(args[j], XtNleft,      XtChainLeft); j++;
291     XtSetArg(args[j], XtNheight,    (XtArgVal) 16); j++;
292     XtSetArg(args[j], XtNwidth,     (XtArgVal) bw_width/2 - 57); j++;
293     outputField[which][nLabel] = NameWidget =
294       XtCreateManagedWidget("Engine", labelWidgetClass,
295                      form, args, j);
296
297     j = 0;
298     XtSetArg(args[j], XtNborderWidth, (XtArgVal) 0); j++;
299     XtSetArg(args[j], XtNlabel,     (XtArgVal) ""); j++;
300     XtSetArg(args[j], XtNfromHoriz, (XtArgVal) NameWidget); j++;
301     XtSetArg(args[j], XtNtop,       XtChainTop); j++;
302     XtSetArg(args[j], XtNbottom,    XtChainTop); j++;
303     XtSetArg(args[j], XtNheight,    (XtArgVal) 16); j++;
304     XtSetArg(args[j], XtNwidth,     (XtArgVal) 20); j++;
305     outputField[which][nStateIcon] = ModeWidget =
306       XtCreateManagedWidget("Mode", labelWidgetClass,
307                      form, args, j);
308
309     j = 0;
310     XtSetArg(args[j], XtNborderWidth, (XtArgVal) 0); j++;
311     XtSetArg(args[j], XtNjustify,   (XtArgVal) XtJustifyLeft); j++;
312     XtSetArg(args[j], XtNlabel,     (XtArgVal) ""); j++;
313     XtSetArg(args[j], XtNfromHoriz, (XtArgVal) ModeWidget); j++;
314     XtSetArg(args[j], XtNtop,       XtChainTop); j++;
315     XtSetArg(args[j], XtNbottom,    XtChainTop); j++;
316     XtSetArg(args[j], XtNright,     XtChainRight); j++;
317     XtSetArg(args[j], XtNheight,    (XtArgVal) 16); j++;
318     XtSetArg(args[j], XtNwidth,     (XtArgVal) bw_width/2 - 102); j++;
319     outputField[which][nStateData] = MoveWidget =
320       XtCreateManagedWidget("Move", labelWidgetClass,
321                      form, args, j);
322
323     j = 0;
324     XtSetArg(args[j], XtNborderWidth, (XtArgVal) 0); j++;
325     XtSetArg(args[j], XtNjustify,   (XtArgVal) XtJustifyRight); j++;
326     XtSetArg(args[j], XtNlabel,     (XtArgVal) _("NPS")); j++;
327     XtSetArg(args[j], XtNfromHoriz, (XtArgVal) MoveWidget); j++;
328     XtSetArg(args[j], XtNtop,       XtChainTop); j++;
329     XtSetArg(args[j], XtNbottom,    XtChainTop); j++;
330     XtSetArg(args[j], XtNleft,      XtChainRight); j++;
331     XtSetArg(args[j], XtNright,     XtChainRight); j++;
332     XtSetArg(args[j], XtNheight,    (XtArgVal) 16); j++;
333     XtSetArg(args[j], XtNwidth,     (XtArgVal) 100); j++;
334     outputField[which][nLabelNPS] = NodesWidget =
335       XtCreateManagedWidget("Nodes", labelWidgetClass,
336                      form, args, j);
337
338     // create "text" within "form"
339     j = 0;
340     if (mutable) {
341         XtSetArg(args[j], XtNeditType, XawtextEdit);  j++;
342         XtSetArg(args[j], XtNuseStringInPlace, False);  j++;
343     }
344     XtSetArg(args[j], XtNstring, "");  j++;
345     XtSetArg(args[j], XtNdisplayCaret, False);  j++;
346     XtSetArg(args[j], XtNtop, XtChainTop);  j++;
347     XtSetArg(args[j], XtNbottom, XtChainBottom);  j++;
348     XtSetArg(args[j], XtNleft, XtChainLeft);  j++;
349     XtSetArg(args[j], XtNright, XtChainRight);  j++;
350     XtSetArg(args[j], XtNresizable, True);  j++;
351     XtSetArg(args[j], XtNwidth, bw_width);  j++; /*force wider than buttons*/
352     /* !!Work around an apparent bug in XFree86 4.0.1 (X11R6.4.3) */
353     XtSetArg(args[j], XtNscrollVertical, XawtextScrollAlways);  j++;
354     XtSetArg(args[j], XtNscrollHorizontal, XawtextScrollWhenNeeded);  j++;
355 //    XtSetArg(args[j], XtNautoFill, True);  j++;
356 //    XtSetArg(args[j], XtNwrap, XawtextWrapWord); j++;
357     outputField[which][nMemo] = edit =
358       XtCreateManagedWidget("text", asciiTextWidgetClass, form, args, j);
359
360     j = 0;
361     XtSetArg(args[j], XtNfromVert, ColorWidget); j++;
362 //    XtSetArg(args[j], XtNresizable, (XtArgVal) True); j++;
363     XtSetValues(edit, args, j);
364 }
365
366 Widget EngineOutputCreate(name, text)
367      char *name, *text;
368 {
369     Arg args[16];
370     Widget shell, layout, form, form2, edit;
371     Dimension bw_width, bw_height;
372     int j;
373
374     // get board width
375     j = 0;
376     XtSetArg(args[j], XtNwidth,  &bw_width);  j++;
377     XtSetArg(args[j], XtNheight, &bw_height);  j++;
378     XtGetValues(boardWidget, args, j);
379
380     // define form within layout within shell.
381     j = 0;
382     XtSetArg(args[j], XtNresizable, True);  j++;
383     shell =
384 #if TOPLEVEL 
385      XtCreatePopupShell(name, topLevelShellWidgetClass,
386 #else
387       XtCreatePopupShell(name, transientShellWidgetClass,
388 #endif
389                          shellWidget, args, j);
390     layout =
391       XtCreateManagedWidget(layoutName, formWidgetClass, shell,
392                             layoutArgs, XtNumber(layoutArgs));
393     // divide window vertically into two equal parts, by creating two forms
394     form =
395       XtCreateManagedWidget("form", formWidgetClass, layout,
396                             formArgs, XtNumber(formArgs));
397     form2 =
398       XtCreateManagedWidget("form2", formWidgetClass, layout,
399                             formArgs, XtNumber(formArgs));
400     j = 0;
401     XtSetArg(args[j], XtNfromVert,  (XtArgVal) form); j++;
402     XtSetValues(form2, args, j);
403     // make sure width is known in advance, for better placement of child widgets
404     j = 0;
405     XtSetArg(args[j], XtNwidth,     (XtArgVal) bw_width-16); j++;
406     XtSetArg(args[j], XtNheight,    (XtArgVal) bw_height/2); j++;
407     XtSetValues(shell, args, j);
408
409     // fill up both forms with control elements
410     PositionControlSet(0, form,  bw_width);
411     PositionControlSet(1, form2, bw_width);
412
413     XtRealizeWidget(shell);
414
415     if (engineOutputX == -1) {
416         int xx, yy;
417         Window junk;
418         Dimension pw_height;
419         Dimension ew_height;
420         engineOutputH = bw_height/2;
421         engineOutputW = bw_width-16;
422
423         XSync(xDisplay, False);
424 #ifdef NOTDEF
425         /* This code seems to tickle an X bug if it is executed too soon
426            after xboard starts up.  The coordinates get transformed as if
427            the main window was positioned at (0, 0).
428            */
429         XtTranslateCoords(shellWidget,
430                           (bw_width - engineOutputW) / 2, 0 - engineOutputH / 2,
431                           &engineOutputX, &engineOutputY);
432 #else  /*!NOTDEF*/
433         XTranslateCoordinates(xDisplay, XtWindow(shellWidget),
434                               RootWindowOfScreen(XtScreen(shellWidget)),
435                               (bw_width - engineOutputW) / 2, 0 - engineOutputH / 2,
436                               &xx, &yy, &junk);
437         engineOutputX = xx;
438         engineOutputY = yy;
439 #endif /*!NOTDEF*/
440         if (engineOutputY < 0) engineOutputY = 0; /*avoid positioning top offscreen*/
441     }
442     j = 0;
443     XtSetArg(args[j], XtNheight, engineOutputH);  j++;
444     XtSetArg(args[j], XtNwidth, engineOutputW);  j++;
445     XtSetArg(args[j], XtNx, engineOutputX);  j++;
446     XtSetArg(args[j], XtNy, engineOutputY);  j++;
447     XtSetValues(shell, args, j);
448 //    XtSetKeyboardFocus(shell, edit);
449
450     return shell;
451 }
452
453 void ResizeWindowControls(shell, mode)
454         Widget shell;
455         int mode;
456 {
457     Widget form1, form2;
458     Arg args[16];
459     int j;
460     Dimension ew_height, tmp;
461
462     form1 = XtNameToWidget(shell, "*form");
463     form2 = XtNameToWidget(shell, "*form2");
464
465     j = 0;
466     XtSetArg(args[j], XtNheight, (XtArgVal) &ew_height); j++;
467     XtGetValues(form1, args, j);
468     j = 0;
469     XtSetArg(args[j], XtNheight, (XtArgVal) &tmp); j++;
470     XtGetValues(form2, args, j);
471     ew_height += tmp; // total height
472
473     if(mode==0) {
474         j = 0;
475         XtSetArg(args[j], XtNheight, (XtArgVal) 5); j++;
476         XtSetValues(form2, args, j);
477         j = 0;
478         XtSetArg(args[j], XtNheight, (XtArgVal) (ew_height-5)); j++;
479         XtSetValues(form1, args, j);
480     } else {
481         j = 0;
482         XtSetArg(args[j], XtNheight, (XtArgVal) (ew_height/2)); j++;
483         XtSetValues(form1, args, j);
484         j = 0;
485         XtSetArg(args[j], XtNheight, (XtArgVal) (ew_height/2)); j++;
486         XtSetValues(form2, args, j);
487     }
488 }
489
490 void 
491 EngineOutputPopUp()
492 {
493     Arg args[16];
494     int j;
495     Widget edit;
496     static char *title = _("Engine output"), *text = _("This feature is experimental");
497
498     if (engineOutputShell == NULL) {
499         engineOutputShell =
500           EngineOutputCreate(title, text);
501         XtRealizeWidget(engineOutputShell);
502         CatchDeleteWindow(engineOutputShell, "EngineOutputPopDown");
503         if( needInit ) {
504             InitializeEngineOutput();
505             needInit = FALSE;
506         }
507         SetEngineColorIcon( 0 );
508         SetEngineColorIcon( 1 );
509         SetEngineState( 0, STATE_IDLE, "" );
510         SetEngineState( 1, STATE_IDLE, "" );
511     } else {
512         edit = XtNameToWidget(engineOutputShell, "*form.text");
513         j = 0;
514         XtSetArg(args[j], XtNstring, text); j++;
515         XtSetValues(edit, args, j);
516         j = 0;
517         XtSetArg(args[j], XtNiconName, (XtArgVal) title);   j++;
518         XtSetArg(args[j], XtNtitle, (XtArgVal) title);      j++;
519         XtSetValues(engineOutputShell, args, j);
520     }
521
522     XtPopup(engineOutputShell, XtGrabNone);
523     XSync(xDisplay, False);
524
525     j=0;
526     XtSetArg(args[j], XtNleftBitmap, xMarkPixmap); j++;
527     XtSetValues(XtNameToWidget(menuBarWidget, "menuMode.Show Engine Output"),
528                 args, j);
529
530     engineOutputDialogUp = True;
531     ShowThinkingEvent(); // [HGM] thinking: might need to prompt engine for thinking output
532 }
533
534 void EngineOutputPopDown()
535 {
536     Arg args[16];
537     int j;
538
539     if (!engineOutputDialogUp) return;
540     DoClearMemo(1);
541     j = 0;
542     XtSetArg(args[j], XtNx, &engineOutputX); j++;
543     XtSetArg(args[j], XtNy, &engineOutputY); j++;
544     XtSetArg(args[j], XtNwidth, &engineOutputW); j++;
545     XtSetArg(args[j], XtNheight, &engineOutputH); j++;
546     XtGetValues(engineOutputShell, args, j);
547     XtPopdown(engineOutputShell);
548     XSync(xDisplay, False);
549     j=0;
550     XtSetArg(args[j], XtNleftBitmap, None); j++;
551     XtSetValues(XtNameToWidget(menuBarWidget, "menuMode.Show Engine Output"),
552                 args, j);
553
554     engineOutputDialogUp = False;
555     ShowThinkingEvent(); // [HGM] thinking: might need to shut off thinking output
556 }
557
558 //------------------------ pure back-end routines -------------------------------
559
560
561 #define MAX_VAR 400
562 static int scores[MAX_VAR], textEnd[MAX_VAR], curDepth[2], nrVariations[2];
563
564 // back end, due to front-end wrapper for SetWindowText, and new SetIcon arguments
565 static void SetEngineState( int which, int state, char * state_data )
566 {
567     int x_which = 1 - which;
568
569     if( engineState[ which ] != state ) {
570         engineState[ which ] = state;
571
572         switch( state ) {
573         case STATE_THINKING:
574             SetIcon( which, nStateIcon, nThinking );
575             if( engineState[ x_which ] == STATE_THINKING ) {
576                 SetEngineState( x_which, STATE_IDLE, "" );
577             }
578             break;
579         case STATE_PONDERING:
580             SetIcon( which, nStateIcon, nPondering );
581             break;
582         case STATE_ANALYZING:
583             SetIcon( which, nStateIcon, nAnalyzing );
584             break;
585         default:
586             SetIcon( which, nStateIcon, nClear );
587             break;
588         }
589     }
590
591     if( state_data != 0 ) {
592         DoSetWindowText( which, nStateData, state_data );
593     }
594 }
595
596 // back end, now the front-end wrapper ClearMemo is used, and ed no longer contains handles.
597 void EngineOutputUpdate( FrontEndProgramStats * stats )
598 {
599     EngineOutputData ed;
600     int clearMemo = FALSE;
601     int which;
602     int depth;
603
604     if( stats == 0 ) {
605         SetEngineState( 0, STATE_IDLE, "" );
606         SetEngineState( 1, STATE_IDLE, "" );
607         return;
608     }
609
610     if(gameMode == IcsObserving && !appData.icsEngineAnalyze)
611         return; // [HGM] kibitz: shut up engine if we are observing an ICS game
612
613     which = stats->which;
614     depth = stats->depth;
615
616     if( which < 0 || which > 1 || depth < 0 || stats->time < 0 || stats->pv == 0 ) {
617         return;
618     }
619
620     if( engineOutputShell == NULL ) {
621         return;
622     }
623
624     VerifyDisplayMode();
625
626     ed.which = which;
627     ed.depth = depth;
628     ed.nodes = stats->nodes;
629     ed.score = stats->score;
630     ed.time = stats->time;
631     ed.pv = stats->pv;
632     ed.hint = stats->hint;
633     ed.an_move_index = stats->an_move_index;
634     ed.an_move_count = stats->an_move_count;
635
636     /* Get target control. [HGM] this is moved to front end, which get them from a table */
637     if( which == 0 ) {
638         ed.name = first.tidy;
639     }
640     else {
641         ed.name = second.tidy;
642     }
643
644     /* Clear memo if needed */
645     if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1) ) {
646         clearMemo = TRUE;
647     }
648
649     if( lastForwardMostMove[which] != forwardMostMove ) {
650         clearMemo = TRUE;
651     }
652
653     if( clearMemo ) DoClearMemo(which);
654
655     /* Update */
656     lastDepth[which] = depth == 1 && ed.nodes == 0 ? 0 : depth; // [HGM] info-line kudge
657     lastForwardMostMove[which] = forwardMostMove;
658
659     if( ed.pv != 0 && ed.pv[0] == ' ' ) {
660         if( strncmp( ed.pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */
661             ed.pv = "";
662         }
663     }
664
665     UpdateControls( &ed );
666 }
667
668 #define ENGINE_COLOR_WHITE      'w'
669 #define ENGINE_COLOR_BLACK      'b'
670 #define ENGINE_COLOR_UNKNOWN    ' '
671
672 // pure back end
673 char GetEngineColor( int which )
674 {
675     char result = ENGINE_COLOR_UNKNOWN;
676
677     if( which == 0 || which == 1 ) {
678         ChessProgramState * cps;
679
680         switch (gameMode) {
681         case MachinePlaysBlack:
682         case IcsPlayingBlack:
683             result = ENGINE_COLOR_BLACK;
684             break;
685         case MachinePlaysWhite:
686         case IcsPlayingWhite:
687             result = ENGINE_COLOR_WHITE;
688             break;
689         case AnalyzeMode:
690         case AnalyzeFile:
691             result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
692             break;
693         case TwoMachinesPlay:
694             cps = (which == 0) ? &first : &second;
695             result = cps->twoMachinesColor[0];
696             result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
697             break;
698         }
699     }
700
701     return result;
702 }
703
704 // pure back end
705 char GetActiveEngineColor()
706 {
707     char result = ENGINE_COLOR_UNKNOWN;
708
709     if( gameMode == TwoMachinesPlay ) {
710         result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
711     }
712
713     return result;
714 }
715
716 // pure back end
717 static int IsEnginePondering( int which )
718 {
719     int result = FALSE;
720
721     switch (gameMode) {
722     case MachinePlaysBlack:
723     case IcsPlayingBlack:
724         if( WhiteOnMove(forwardMostMove) ) result = TRUE;
725         break;
726     case MachinePlaysWhite:
727     case IcsPlayingWhite:
728         if( ! WhiteOnMove(forwardMostMove) ) result = TRUE;
729         break;
730     case TwoMachinesPlay:
731         if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {
732             if( GetEngineColor( which ) != GetActiveEngineColor() ) result = TRUE;
733         }
734         break;
735     }
736
737     return result;
738 }
739
740 // back end
741 static void SetDisplayMode( int mode )
742 {
743     if( windowMode != mode ) {
744         windowMode = mode;
745
746         ResizeWindowControls( engineOutputShell, mode );
747     }
748 }
749
750 // pure back end
751 void VerifyDisplayMode()
752 {
753     int mode;
754
755     /* Get proper mode for current game */
756     switch( gameMode ) {
757     case IcsObserving:    // [HGM] ICS analyze
758         if(!appData.icsEngineAnalyze) return;
759     case AnalyzeMode:
760     case AnalyzeFile:
761     case MachinePlaysWhite:
762     case MachinePlaysBlack:
763         mode = 0;
764         break;
765     case IcsPlayingWhite:
766     case IcsPlayingBlack:
767         mode = appData.zippyPlay && opponentKibitzes; // [HGM] kibitz
768         break;
769     case TwoMachinesPlay:
770         mode = 1;
771         break;
772     default:
773         /* Do not change */
774         return;
775     }
776
777     SetDisplayMode( mode );
778 }
779
780 // back end. Determine what icon to se in the color-icon field, and print it
781 static void SetEngineColorIcon( int which )
782 {
783     char color = GetEngineColor(which);
784     int nicon = 0;
785
786     if( color == ENGINE_COLOR_BLACK )
787         nicon = nColorBlack;
788     else if( color == ENGINE_COLOR_WHITE )
789         nicon = nColorWhite;
790     else
791         nicon = nColorUnknown;
792
793     SetIcon( which, nColorIcon, nicon );
794 }
795
796 #define MAX_NAME_LENGTH 32
797
798 // [HGM] multivar: sort Thinking Output within one depth on score
799
800 static int InsertionPoint( int len, EngineOutputData * ed )
801 {
802         int i, offs = 0, newScore = ed->score, n = ed->which;
803
804         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0)
805                 newScore = 1e6; // info lines inserted on top
806         if(ed->depth != curDepth[n]) { // depth has changed
807                 curDepth[n] = ed->depth;
808                 nrVariations[n] = 0; // throw away everything we had
809         }
810         // loop through all lines. Note even / odd used for different panes
811         for(i=nrVariations[n]-2; i>=0; i-=2) {
812                 // put new item behind those we haven't looked at
813                 offs = textEnd[i+n];
814                 textEnd[i+n+2] = offs + len;
815                 scores[i+n+2] = newScore;
816                 if(newScore < scores[i+n]) break;
817                 // if it had higher score as previous, move previous in stead
818                 scores[i+n+2] = scores[i+n];
819                 textEnd[i+n+2] = textEnd[i+n] + len;
820         }
821         if(i<0) {
822                 offs = 0;
823                 textEnd[n] = offs + len;
824                 scores[n] = newScore;
825         }
826         nrVariations[n] += 2;
827       return offs;
828 }
829
830 // pure back end, now SetWindowText is called via wrapper DoSetWindowText
831 static void UpdateControls( EngineOutputData * ed )
832 {
833     int isPondering = FALSE;
834
835     char s_label[MAX_NAME_LENGTH + 32];
836     
837     char * name = ed->name;
838
839     /* Label */
840     if( name == 0 || *name == '\0' ) {
841         name = "?";
842     }
843
844     strncpy( s_label, name, MAX_NAME_LENGTH );
845     s_label[ MAX_NAME_LENGTH-1 ] = '\0';
846
847 #ifdef SHOW_PONDERING
848     if( IsEnginePondering( ed->which ) ) {
849         char buf[8];
850
851         buf[0] = '\0';
852
853         if( ed->hint != 0 && *ed->hint != '\0' ) {
854             strncpy( buf, ed->hint, sizeof(buf) );
855             buf[sizeof(buf)-1] = '\0';
856         }
857         else if( ed->pv != 0 && *ed->pv != '\0' ) {
858             char * sep = strchr( ed->pv, ' ' );
859             int buflen = sizeof(buf);
860
861             if( sep != NULL ) {
862                 buflen = sep - ed->pv + 1;
863                 if( buflen > sizeof(buf) ) buflen = sizeof(buf);
864             }
865
866             strncpy( buf, ed->pv, buflen );
867             buf[ buflen-1 ] = '\0';
868         }
869
870         SetEngineState( ed->which, STATE_PONDERING, buf );
871     }
872     else if( gameMode == TwoMachinesPlay ) {
873         SetEngineState( ed->which, STATE_THINKING, "" );
874     }
875     else if( gameMode == AnalyzeMode || gameMode == AnalyzeFile 
876           || gameMode == IcsObserving && appData.icsEngineAnalyze) { // [HGM] ICS-analyze
877         char buf[64];
878         int time_secs = ed->time / 100;
879         int time_mins = time_secs / 60;
880
881         buf[0] = '\0';
882
883         if( ed->an_move_index != 0 && ed->an_move_count != 0 && *ed->hint != '\0' ) {
884             char mov[16];
885
886             strncpy( mov, ed->hint, sizeof(mov) );
887             mov[ sizeof(mov)-1 ] = '\0';
888
889             sprintf( buf, "[%d] %d/%d: %s [%02d:%02d:%02d]", ed->depth, ed->an_move_index,
890                         ed->an_move_count, mov, time_mins / 60, time_mins % 60, time_secs % 60 );
891         }
892
893         SetEngineState( ed->which, STATE_ANALYZING, buf );
894     }
895     else {
896         SetEngineState( ed->which, STATE_IDLE, "" );
897     }
898 #endif
899
900     DoSetWindowText( ed->which, nLabel, s_label );
901
902     s_label[0] = '\0';
903
904     if( ed->time > 0 && ed->nodes > 0 ) {
905         unsigned long nps_100 = ed->nodes / ed->time;
906
907         if( nps_100 < 100000 ) {
908             sprintf( s_label, _("NPS: %lu"), nps_100 * 100 );
909         }
910         else {
911             sprintf( s_label, _("NPS: %.1fk"), nps_100 / 10.0 );
912         }
913     }
914
915     DoSetWindowText( ed->which, nLabelNPS, s_label );
916
917     /* Memo */
918     if( ed->pv != 0 && *ed->pv != '\0' ) {
919         char s_nodes[24];
920         char s_score[16];
921         char s_time[24];
922         char buf[256];
923         int buflen;
924         int time_secs = ed->time / 100;
925         int time_cent = ed->time % 100;
926
927         /* Nodes */
928         if( ed->nodes < 1000000 ) {
929             sprintf( s_nodes, u64Display, ed->nodes );
930         }
931         else {
932             sprintf( s_nodes, "%.1fM", u64ToDouble(ed->nodes) / 1000000.0 );
933         }
934
935         /* Score */
936         if( ed->score > 0 ) {
937             sprintf( s_score, "+%.2f", ed->score / 100.0 );
938         } else
939             sprintf( s_score, "%.2f", ed->score / 100.0 );
940
941         /* Time */
942         sprintf( s_time, "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );
943
944         /* Put all together... */
945         if(ed->nodes == 0 && ed->score == 0 && ed->time == 0) sprintf( buf, "%3d\t", ed->depth ); else 
946         sprintf( buf, "%3d  %s  %s\t%s\t", ed->depth, s_score, s_nodes, s_time );
947
948         /* Add PV */
949         buflen = strlen(buf);
950
951         strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );
952
953         buf[ sizeof(buf) - 3 ] = '\0';
954
955         strcat( buf + buflen, "\n" );
956
957         /* Update memo */
958         InsertIntoMemo( ed->which, buf, InsertionPoint(strlen(buf), ed) );
959     }
960
961     /* Colors */
962     SetEngineColorIcon( ed->which );
963 }
964
965 // back end
966 int EngineOutputIsUp()
967 {
968     return engineOutputDialogUp;
969 }
970
971 void
972 EngineOutputProc(w, event, prms, nprms)
973      Widget w;
974      XEvent *event;
975      String *prms;
976      Cardinal *nprms;
977 {
978   if (engineOutputDialogUp) {
979     EngineOutputPopDown();
980   } else {
981     EngineOutputPopUp();
982   }
983 //  ToNrEvent(currentMove);
984 }
985
986 // [HGM] kibitz: write kibitz line; split window for it if necessary
987 void OutputKibitz(int window, char *text)
988 {
989         if(!EngineOutputIsUp()) return;
990         if(!opponentKibitzes) { // on first kibitz of game, clear memos
991             DoClearMemo(1);
992             if(gameMode == IcsObserving) DoClearMemo(0);
993         }
994         opponentKibitzes = TRUE; // this causes split window DisplayMode in ICS modes.
995         VerifyDisplayMode();
996         if(gameMode == IcsObserving) {
997             DoSetWindowText(0, nLabel, gameInfo.white);
998             SetIcon( 0, nColorIcon,  nColorWhite);
999             SetIcon( 0, nStateIcon,  nClear);
1000         }
1001         DoSetWindowText(1, nLabel, gameMode == IcsPlayingBlack ? gameInfo.white : gameInfo.black); // opponent name
1002         SetIcon( 1, nColorIcon,  gameMode == IcsPlayingBlack ? nColorWhite : nColorBlack);
1003         SetIcon( 1, nStateIcon,  nClear);
1004         InsertIntoMemo(window-1, text, 0);
1005 }