Fix recent-engines menu
[xboard.git] / ngamelist.c
1 /*
2  * ngamelist.c -- Game list window, Xt-independent front-end code for XBoard
3  *
4  * Copyright 1995, 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 #include "config.h"
24
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <sys/types.h>
29
30 #if STDC_HEADERS
31 # include <stdlib.h>
32 # include <string.h>
33 #else /* not STDC_HEADERS */
34 extern char *getenv();
35 # if HAVE_STRING_H
36 #  include <string.h>
37 # else /* not HAVE_STRING_H */
38 #  include <strings.h>
39 # endif /* not HAVE_STRING_H */
40 #endif /* not STDC_HEADERS */
41
42 #if HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45
46 #include "common.h"
47 #include "frontend.h"
48 #include "backend.h"
49 #include "dialogs.h"
50 #include "menus.h"
51 #include "gettext.h"
52
53 #ifdef ENABLE_NLS
54 # define  _(s) gettext (s)
55 # define N_(s) gettext_noop (s)
56 #else
57 # define  _(s) (s)
58 # define N_(s)  s
59 #endif
60
61
62 static char filterString[MSG_SIZ];
63 static int listLength, wins, losses, draws, page;
64
65
66 typedef struct {
67     short int x, y;
68     short int w, h;
69     FILE *fp;
70     char *filename;
71     char **strings;
72 } GameListClosure;
73 static GameListClosure *glc = NULL;
74
75 static char *filterPtr;
76 static char *list[1003];
77 static int listEnd;
78
79 static int GameListPrepare P((int byPos));
80 static void GameListReplace P((int page));
81 static void GL_Button P((int n));
82
83 static Option gamesOptions[] = {
84 { 200,  LR|TB,     400, NULL, (void*) list,       "", NULL, ListBox, "" },
85 {   0,  0,         100, NULL, (void*) &filterPtr, "", NULL, TextBox, "" },
86 {   2,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("(filter)") }, // buttons referred to by ID in value (=first) field!
87 {   3,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("thresholds") },
88 {   9,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("tags") },
89 {   4,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("find position") },
90 {   5,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("next") },
91 {   6,  SAME_ROW,    0, NULL, (void*) &GL_Button, NULL, NULL, Button, N_("close") },
92 {   0,  SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
93 };
94
95 static void
96 GL_Button (int n)
97 {
98     int index, j;
99     n = gamesOptions[n].value; // use marker in option rather than n itself, for more easy adding/deletng of buttons
100     if (n == 6) { // close
101         PopDown(GameListDlg);
102         return;
103     }
104     if (n == 3) { // thresholds
105         LoadOptionsPopUp(GameListDlg);
106         return;
107     }
108     if (n == 9) { // tags
109         GameListOptionsPopUp(GameListDlg);
110         return;
111     }
112     index = SelectedListBoxItem(&gamesOptions[0]);
113     if (n == 7) { // load
114         if (index < 0) {
115             DisplayError(_("No game selected"), 0);
116             return;
117         }
118     } else if (n == 5) { // next
119         index++;
120         if (index >= listLength || !list[index]) {
121             DisplayError(_("Can't go forward any further"), 0);
122             return;
123         }
124         HighlightWithScroll(&gamesOptions[0], index, listEnd);
125     } else if (n == 8) { // prev
126         index--;
127         if (index < 0) {
128             DisplayError(_("Can't back up any further"), 0);
129             return;
130         }
131         HighlightWithScroll(&gamesOptions[0], index, listEnd);
132     } else if (n == 2 || // filter
133                n == 4) { // find position
134         char *text;
135         GetWidgetText(&gamesOptions[1], &text);
136         safeStrCpy(filterString, text, sizeof(filterString)/sizeof(filterString[0]));
137         GameListPrepare(n == 4); GameListReplace(0);
138         return;
139     }
140
141     index = atoi(list[index])-1; // [HGM] filter: read true index from sequence nr of line
142     if (cmailMsgLoaded) {
143         CmailLoadGame(glc->fp, index + 1, glc->filename, True);
144     } else {
145         LoadGame(glc->fp, index + 1, glc->filename, True);
146     }
147 }
148
149 static int
150 GameListCreate (char *name)
151 {
152     int new;
153     if(new = GenericPopUp(gamesOptions, name, GameListDlg, BoardWindow, NONMODAL, 1))
154         AddHandler(&gamesOptions[1], 4),
155         AddHandler(&gamesOptions[0], 5);
156     FocusOnWidget(&gamesOptions[0], GameListDlg);
157     return new;
158 }
159
160 static int
161 GameListPrepare (int byPos)
162 {   // [HGM] filter: put in separate routine, to make callable from call-back
163     int nstrings;
164     ListGame *lg;
165     char **st, *line;
166     TimeMark t, t2;
167
168     GetTimeMark(&t);
169     if(st = glc->strings) while(*st) free(*st++);
170     nstrings = ((ListGame *) gameList.tailPred)->number;
171     glc->strings = (char **) malloc((nstrings + 1) * sizeof(char *));
172     st = glc->strings;
173     lg = (ListGame *) gameList.head;
174     listLength = wins = losses = draws = 0;
175     if(byPos) InitSearch();
176     while (nstrings--) {
177         int pos = -1;
178         line = GameListLine(lg->number, &lg->gameInfo);
179         if((filterString[0] == NULLCHAR || SearchPattern( line, filterString )) && (!byPos || (pos=GameContainsPosition(glc->fp, lg)) >= 0) ) {
180             *st++ = line; // [HGM] filter: make adding line conditional.
181             listLength++;
182             if( lg->gameInfo.result == WhiteWins ) wins++; else
183             if( lg->gameInfo.result == BlackWins ) losses++; else
184             if( lg->gameInfo.result == GameIsDrawn ) draws++;
185         }
186         if(lg->number % 2000 == 0) {
187             char buf[MSG_SIZ];
188             snprintf(buf, MSG_SIZ, _("Scanning through games (%d)"), lg->number);
189             DisplayTitle(buf);
190         }
191         lg->position = pos;
192         lg = (ListGame *) lg->node.succ;
193      }
194 GetTimeMark(&t2);printf("GameListPrepare %ld msec\n", SubtractTimeMarks(&t2,&t));
195      DisplayTitle("XBoard");
196     *st = NULL;
197     return listLength;
198 }
199
200 static void
201 GameListReplace (int page)
202 {
203   // filter: put in separate routine, to make callable from call-back
204   char buf[MSG_SIZ], *p, **st=list;
205   int i;
206
207   if(page) *st++ = _("previous page"); else if(listLength > 1000) *st++ = "";
208   for(i=0; i<1000; i++) if( !(*st++ = glc->strings[page+i]) ) { st--; break; }
209   listEnd = st - list;
210   if(page + 1000 <= listLength) *st++ = _("next page");
211   *st = NULL;
212
213   LoadListBox(&gamesOptions[0], _("no games matched your request"));
214   HighlightWithScroll(&gamesOptions[0], listEnd > 1000, listEnd);
215   snprintf(buf, MSG_SIZ, _("%s - %d/%d games (%d-%d-%d)"), glc->filename, listLength, ((ListGame *) gameList.tailPred)->number, wins, losses, draws);
216   SetDialogTitle(GameListDlg, buf);
217 }
218
219 void
220 GameListPopUp (FILE *fp, char *filename)
221 {
222     char **st;
223
224     if (glc == NULL) {
225         glc = (GameListClosure *) calloc(1, sizeof(GameListClosure));
226         glc->x = glc->y = -1;
227         glc->filename = NULL;
228     }
229
230     GameListPrepare(False); // [HGM] filter: code put in separate routine
231
232     glc->fp = fp;
233
234     if (glc->filename != NULL) free(glc->filename);
235     glc->filename = StrSave(filename);
236
237     if (!GameListCreate(filename))
238         SetIconName(GameListDlg, filename);
239
240     page = 0;
241     GameListReplace(0); // [HGM] filter: code put in separate routine, and also called to set title
242     MarkMenu("View.GameList", GameListDlg);
243 }
244
245 void
246 GameListDestroy ()
247 {
248     if (glc == NULL) return;
249     PopDown(GameListDlg);
250     if (glc->strings != NULL) {
251         char **st;
252         st = glc->strings;
253         while (*st) {
254             free(*st++);
255         }
256         free(glc->strings);
257     }
258     free(glc);
259     glc = NULL;
260 }
261
262 void
263 ShowGameListProc ()
264 {
265     Arg args[16];
266     int j;
267
268     if (glc == NULL) {
269         DisplayError(_("There is no game list"), 0);
270         return;
271     }
272     if (shellUp[GameListDlg]) {
273         PopDown(GameListDlg);
274         return;
275     }
276     GenericPopUp(NULL, NULL, GameListDlg, BoardWindow, NONMODAL, 1); // first two args ignored when shell exists!
277     MarkMenu("View.GameList", GameListDlg);
278     GameListHighlight(lastLoadGameNumber);
279 }
280
281 int
282 GameListClicks (int direction)
283 {
284     int index;
285
286     if (glc == NULL || listLength == 0) return 0;
287     if(direction == 100) { FocusOnWidget(&gamesOptions[0], GameListDlg); return 1; }
288     index = SelectedListBoxItem(&gamesOptions[0]);
289
290     if (index < 0) return;
291     if(page && (index == 0 && direction < 1 || direction == -4)) {
292         page -= 1000;
293         if(page < 0) page = 0; // safety
294         GameListReplace(page);
295         return 1;
296     }
297     if(index == 1001 && direction >= 0 || listEnd == 1001 && direction == 4) {
298         page += 1000;
299         GameListReplace(page);
300         return 1;
301     }
302
303     if(direction != 0) {
304         int doLoad = abs(direction) == 3;
305         if(doLoad) direction /= 3;
306         index += direction;
307         if(direction < -1) index = 0;
308         if(direction >  1) index = listEnd-1;
309         if(index < 0 || index >= listEnd) return;
310         HighlightWithScroll(&gamesOptions[0], index, listEnd);
311         if(!doLoad) return 1;
312     }
313     index = atoi(list[index])-1; // [HGM] filter: read true index from sequence nr of line
314     if (cmailMsgLoaded) {
315         CmailLoadGame(glc->fp, index + 1, glc->filename, True);
316     } else {
317         LoadGame(glc->fp, index + 1, glc->filename, True);
318     }
319     return 0;
320 }
321
322 void
323 SetFilter ()
324 {
325         char *name;
326         GetWidgetText(&gamesOptions[1], &name);
327         safeStrCpy(filterString, name, sizeof(filterString)/sizeof(filterString[0]));
328         GameListPrepare(False); GameListReplace(0);
329         UnCaret(); // filter text-edit
330         FocusOnWidget(&gamesOptions[0], GameListDlg); // listbox
331 }
332
333 void
334 GameListHighlight (int index)
335 {
336     int i=0; char **st;
337     if (!shellUp[GameListDlg]) return;
338     st = list;
339     while(*st && atoi(*st)<index) st++,i++;
340     HighlightWithScroll(&gamesOptions[0], i, listEnd);
341 }
342
343 int
344 SaveGameListAsText (FILE *f)
345 {
346     ListGame * lg = (ListGame *) gameList.head;
347     int nItem;
348
349     if( !glc || ((ListGame *) gameList.tailPred)->number <= 0 ) {
350       DisplayError(_("Game list not loaded or empty"), 0);
351         return False;
352     }
353
354     /* Copy the list into the global memory block */
355     if( f != NULL ) {
356  
357         lg = (ListGame *) gameList.head;
358
359         for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){
360             char * st = GameListLineFull(lg->number, &lg->gameInfo);
361             char *line = GameListLine(lg->number, &lg->gameInfo);
362             if(filterString[0] == NULLCHAR || SearchPattern( line, filterString ) )
363                     fprintf( f, "%s\n", st );
364             free(st); free(line);
365             lg = (ListGame *) lg->node.succ;
366         }
367
368         fclose(f);
369         return True;
370     }
371     return False;
372 }
373