Fix multi-leg promotions
[xboard.git] / winboard / wgamelist.c
1 /*\r
2  * wgamelist.c -- Game list window for WinBoard\r
3  *\r
4  * Copyright 1995, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free\r
5  * Software Foundation, Inc.\r
6  *\r
7  * Enhancements Copyright 2005 Alessandro Scotti\r
8  *\r
9  * ------------------------------------------------------------------------\r
10  *\r
11  * GNU XBoard is free software: you can redistribute it and/or modify\r
12  * it under the terms of the GNU General Public License as published by\r
13  * the Free Software Foundation, either version 3 of the License, or (at\r
14  * your option) any later version.\r
15  *\r
16  * GNU XBoard is distributed in the hope that it will be useful, but\r
17  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
19  * General Public License for more details.\r
20  *\r
21  * You should have received a copy of the GNU General Public License\r
22  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
23  *\r
24  *------------------------------------------------------------------------\r
25  ** See the file ChangeLog for a revision history.  */\r
26 \r
27 #include "config.h"\r
28 \r
29 #include <windows.h> /* required for all Windows applications */\r
30 #include <stdio.h>\r
31 #include <stdlib.h>\r
32 #include <malloc.h>\r
33 #include <fcntl.h>\r
34 #include <math.h>\r
35 #include <commdlg.h>\r
36 #include <dlgs.h>\r
37 \r
38 #include "common.h"\r
39 #include "frontend.h"\r
40 #include "backend.h"\r
41 #include "winboard.h"\r
42 \r
43 #include "wsnap.h"\r
44 \r
45 #define _(s) T_(s)\r
46 \r
47 /* Module globals */\r
48 static BOOLEAN gameListUp = FALSE;\r
49 static FILE* gameFile;\r
50 static char* gameFileName = NULL;\r
51 \r
52 /* [AS] Setup the game list according to the specified filter */\r
53 int GameListToListBox( HWND hDlg, BOOL boReset, char * pszFilter, struct GameListStats * stats, BOOL byPos, BOOL narrow )\r
54 {\r
55     ListGame * lg = (ListGame *) gameList.head;\r
56     int nItem;\r
57     char buf[MSG_SIZ];\r
58     BOOL hasFilter = FALSE;\r
59     int count = 0;\r
60     struct GameListStats dummy;\r
61 \r
62     if(!hDlg) hDlg = gameListDialog; // [HGM] to allow calling from Game List Options dialog\r
63     if(!hDlg) return 0;\r
64 \r
65     /* Initialize stats (use a dummy variable if caller not interested in them) */\r
66     if( stats == NULL ) {\r
67         stats = &dummy;\r
68     }\r
69 \r
70     stats->white_wins = 0;\r
71     stats->black_wins = 0;\r
72     stats->drawn = 0;\r
73     stats->unfinished = 0;\r
74 \r
75     if( boReset ) {\r
76         SendDlgItemMessage(hDlg, OPT_GameListText, LB_RESETCONTENT, 0, 0);\r
77     }\r
78 \r
79     if( pszFilter != NULL ) {\r
80         if( strlen( pszFilter ) > 0 ) {\r
81             hasFilter = TRUE;\r
82         }\r
83     }\r
84 \r
85     if(byPos) InitSearch();\r
86 \r
87     for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
88         char * st = NULL;\r
89         BOOL skip = FALSE;\r
90         int pos = -1;\r
91 \r
92         if(nItem % 2000 == 0) {\r
93           snprintf(buf, MSG_SIZ, _("Scanning through games (%d)"), nItem);\r
94           SetWindowText(hwndMain, buf); DoEvents();\r
95         }\r
96 \r
97       if(!narrow || lg->position >= 0) {\r
98         if( hasFilter ) {\r
99             st = GameListLine(lg->number, &lg->gameInfo);\r
100             if( !SearchPattern( st, pszFilter) ) skip = TRUE;\r
101         }\r
102 \r
103         if( !skip && byPos) {\r
104             if( (pos = GameContainsPosition(gameFile, lg)) < 0) skip = TRUE;\r
105         }\r
106 \r
107         if( ! skip ) {\r
108             if(!st) st = GameListLine(lg->number, &lg->gameInfo);\r
109             SendDlgItemMessage(hDlg, OPT_GameListText, LB_ADDSTRING, 0, (LPARAM) st);\r
110             count++;\r
111 \r
112             /* Update stats */\r
113             if( lg->gameInfo.result == WhiteWins )\r
114                 stats->white_wins++;\r
115             else if( lg->gameInfo.result == BlackWins )\r
116                 stats->black_wins++;\r
117             else if( lg->gameInfo.result == GameIsDrawn )\r
118                 stats->drawn++;\r
119             else\r
120                 stats->unfinished++;\r
121             if(!byPos) pos = 0;\r
122         }\r
123       }\r
124 \r
125         lg->position = pos;\r
126 \r
127         if(st) free(st);\r
128         lg = (ListGame *) lg->node.succ;\r
129     }\r
130 \r
131     SendDlgItemMessage(hDlg, OPT_GameListText, LB_SETCURSEL, 0, 0);\r
132     SetWindowText(hwndMain, "WinBoard");\r
133 \r
134     return count;\r
135 }\r
136 \r
137 /* [AS] Show number of visible (filtered) games and total on window caption */\r
138 static int GameListUpdateTitle( HWND hDlg, char * pszTitle, int item_count, int item_total, struct GameListStats * stats )\r
139 {\r
140     char buf[256];\r
141 \r
142     snprintf( buf, sizeof(buf)/sizeof(buf[0]),_("%s - %d/%d games"), pszTitle, item_count, item_total );\r
143 \r
144     if( stats != 0 ) {\r
145         sprintf( buf+strlen(buf), " (%d-%d-%d)", stats->white_wins, stats->black_wins, stats->drawn );\r
146     }\r
147 \r
148     SetWindowText( hDlg, buf );\r
149 \r
150     return 0;\r
151 }\r
152 \r
153 #define MAX_FILTER_LENGTH   128\r
154 \r
155 LRESULT CALLBACK\r
156 GameListDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)\r
157 {\r
158   static char szDlgTitle[64];\r
159   static HANDLE hwndText;\r
160   int nItem;\r
161   RECT rect;\r
162   static int sizeX, sizeY;\r
163   int newSizeX, newSizeY;\r
164   MINMAXINFO *mmi;\r
165   static BOOL filterHasFocus = FALSE;\r
166   int count;\r
167   struct GameListStats stats;\r
168   static SnapData sd;\r
169 \r
170   switch (message) {\r
171   case WM_INITDIALOG:\r
172     Translate(hDlg, DLG_GameList);\r
173     GetWindowText( hDlg, szDlgTitle, sizeof(szDlgTitle) );\r
174     szDlgTitle[ sizeof(szDlgTitle)-1 ] = '\0';\r
175 \r
176     if (gameListDialog) {\r
177       SendDlgItemMessage(hDlg, OPT_GameListText, LB_RESETCONTENT, 0, 0);\r
178     }\r
179 \r
180     /* Initialize the dialog items */\r
181     hwndText = GetDlgItem(hDlg, OPT_TagsText);\r
182 \r
183     /* Set font */\r
184     SendDlgItemMessage( hDlg, OPT_GameListText, WM_SETFONT, (WPARAM)font[boardSize][GAMELIST_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
185 \r
186     count = GameListToListBox( hDlg, gameListDialog ? TRUE : FALSE, NULL, &stats, FALSE, FALSE );\r
187 \r
188     SendDlgItemMessage( hDlg, IDC_GameListFilter, WM_SETTEXT, 0, (LPARAM) "" );\r
189     SendDlgItemMessage( hDlg, IDC_GameListFilter, EM_SETLIMITTEXT, MAX_FILTER_LENGTH, 0 );\r
190 \r
191     filterHasFocus = FALSE;\r
192 \r
193     /* Size and position the dialog */\r
194     if (!gameListDialog) {\r
195       gameListDialog = hDlg;\r
196       GetClientRect(hDlg, &rect);\r
197       sizeX = rect.right;\r
198       sizeY = rect.bottom;\r
199       if (wpGameList.x != CW_USEDEFAULT && wpGameList.y != CW_USEDEFAULT &&\r
200           wpGameList.width != CW_USEDEFAULT && wpGameList.height != CW_USEDEFAULT) {\r
201         WINDOWPLACEMENT wp;\r
202         EnsureOnScreen(&wpGameList.x, &wpGameList.y, 0, 0);\r
203         wp.length = sizeof(WINDOWPLACEMENT);\r
204         wp.flags = 0;\r
205         wp.showCmd = SW_SHOW;\r
206         wp.ptMaxPosition.x = wp.ptMaxPosition.y = 0;\r
207         wp.rcNormalPosition.left = wpGameList.x;\r
208         wp.rcNormalPosition.right = wpGameList.x + wpGameList.width;\r
209         wp.rcNormalPosition.top = wpGameList.y;\r
210         wp.rcNormalPosition.bottom = wpGameList.y + wpGameList.height;\r
211         SetWindowPlacement(hDlg, &wp);\r
212 \r
213         GetClientRect(hDlg, &rect);\r
214         newSizeX = rect.right;\r
215         newSizeY = rect.bottom;\r
216         ResizeEditPlusButtons(hDlg, hwndText, sizeX, sizeY,\r
217                               newSizeX, newSizeY);\r
218         sizeX = newSizeX;\r
219         sizeY = newSizeY;\r
220       } else\r
221         GetActualPlacement( gameListDialog, &wpGameList );\r
222 \r
223     }\r
224       GameListUpdateTitle( hDlg, _("Game List"), count, ((ListGame *) gameList.tailPred)->number, &stats ); // [HGM] always update title\r
225     GameListHighlight(lastLoadGameNumber);\r
226     return FALSE;\r
227 \r
228   case WM_SIZE:\r
229     newSizeX = LOWORD(lParam);\r
230     newSizeY = HIWORD(lParam);\r
231     ResizeEditPlusButtons(hDlg, GetDlgItem(hDlg, OPT_GameListText),\r
232       sizeX, sizeY, newSizeX, newSizeY);\r
233     sizeX = newSizeX;\r
234     sizeY = newSizeY;\r
235     break;\r
236 \r
237   case WM_ENTERSIZEMOVE:\r
238     return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
239 \r
240   case WM_SIZING:\r
241     return OnSizing( &sd, hDlg, wParam, lParam );\r
242 \r
243   case WM_MOVING:\r
244     return OnMoving( &sd, hDlg, wParam, lParam );\r
245 \r
246   case WM_EXITSIZEMOVE:\r
247     return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
248 \r
249   case WM_GETMINMAXINFO:\r
250     /* Prevent resizing window too small */\r
251     mmi = (MINMAXINFO *) lParam;\r
252     mmi->ptMinTrackSize.x = 100;\r
253     mmi->ptMinTrackSize.y = 100;\r
254     break;\r
255 \r
256   case WM_COMMAND:\r
257       /*\r
258         [AS]\r
259         If <Enter> is pressed while editing the filter, it's better to apply\r
260         the filter rather than selecting the current game.\r
261       */\r
262       if( LOWORD(wParam) == IDC_GameListFilter ) {\r
263           switch( HIWORD(wParam) ) {\r
264           case EN_SETFOCUS:\r
265               filterHasFocus = TRUE;\r
266               break;\r
267           case EN_KILLFOCUS:\r
268               filterHasFocus = FALSE;\r
269               break;\r
270           }\r
271       }\r
272 \r
273       if( filterHasFocus && (LOWORD(wParam) == IDOK) ) {\r
274           wParam = IDC_GameListDoFilter;\r
275       }\r
276       /* [AS] End command replacement */\r
277 \r
278     switch (LOWORD(wParam)) {\r
279     case OPT_GameListLoad:\r
280       LoadOptionsPopup(hDlg);\r
281       return TRUE;\r
282     case IDOK:\r
283       nItem = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
284       if (nItem < 0) {\r
285         /* is this possible? */\r
286         DisplayError(_("No game selected"), 0);\r
287         return TRUE;\r
288       }\r
289       break; /* load the game*/\r
290 \r
291     case OPT_GameListNext:\r
292       nItem = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
293       nItem++;\r
294       if (nItem >= ((ListGame *) gameList.tailPred)->number) {\r
295         /* [AS] Removed error message */\r
296         /* DisplayError(_("Can't go forward any further"), 0); */\r
297         return TRUE;\r
298       }\r
299       SendDlgItemMessage(hDlg, OPT_GameListText, LB_SETCURSEL, nItem, 0);\r
300       break; /* load the game*/\r
301 \r
302     case OPT_GameListPrev:\r
303 #if 0\r
304       nItem = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
305       nItem--;\r
306       if (nItem < 0) {\r
307         /* [AS] Removed error message, added return */\r
308         /* DisplayError(_("Can't back up any further"), 0); */\r
309         return TRUE;\r
310       }\r
311       SendDlgItemMessage(hDlg, OPT_GameListText, LB_SETCURSEL, nItem, 0);\r
312       break; /* load the game*/\r
313 #endif\r
314     /* [AS] */\r
315     case OPT_GameListFind:\r
316     case IDC_GameListDoFilter:\r
317         {\r
318             char filter[MAX_FILTER_LENGTH+1];\r
319 \r
320             if( GetDlgItemText( hDlg, IDC_GameListFilter, filter, sizeof(filter) ) >= 0 ) {\r
321                 filter[ sizeof(filter)-1 ] = '\0';\r
322                 count = GameListToListBox( hDlg, TRUE, filter, &stats, LOWORD(wParam)!=IDC_GameListDoFilter, LOWORD(wParam)==OPT_GameListNarrow );\r
323                 GameListUpdateTitle( hDlg, _("Game List"), count, ((ListGame *) gameList.tailPred)->number, &stats );\r
324             }\r
325         }\r
326         return FALSE;\r
327         break;\r
328 \r
329     case IDCANCEL:\r
330     case OPT_GameListClose:\r
331       GameListPopDown();\r
332       return TRUE;\r
333 \r
334     case OPT_GameListText:\r
335       switch (HIWORD(wParam)) {\r
336       case LBN_DBLCLK:\r
337         nItem = SendMessage((HWND) lParam, LB_GETCURSEL, 0, 0);\r
338         break; /* load the game*/\r
339 \r
340       default:\r
341         return FALSE;\r
342       }\r
343       break;\r
344 \r
345     default:\r
346       return FALSE;\r
347     }\r
348 \r
349     /* Load the game */\r
350     {\r
351         /* [AS] Get index from the item itself, because filtering makes original order unuseable. */\r
352         int index = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
353         char * text;\r
354         LRESULT res;\r
355 \r
356         if( index < 0 ) {\r
357             return TRUE;\r
358         }\r
359 \r
360         res = SendDlgItemMessage( hDlg, OPT_GameListText, LB_GETTEXTLEN, index, 0 );\r
361 \r
362         if( res == LB_ERR ) {\r
363             return TRUE;\r
364         }\r
365 \r
366         text = (char *) malloc( res+1 );\r
367 \r
368         res = SendDlgItemMessage( hDlg, OPT_GameListText, LB_GETTEXT, index, (LPARAM)text );\r
369 \r
370         index = atoi( text );\r
371 \r
372         nItem = index - 1;\r
373 \r
374         free( text );\r
375         /* [AS] End: nItem has been "patched" now! */\r
376 \r
377         if (cmailMsgLoaded) {\r
378             CmailLoadGame(gameFile, nItem + 1, gameFileName, TRUE);\r
379         }\r
380         else {\r
381             LoadGame(gameFile, nItem + 1, gameFileName, TRUE);\r
382             SetFocus(hwndMain); // [HGM] automatic focus switch\r
383         }\r
384     }\r
385 \r
386     return TRUE;\r
387 \r
388   default:\r
389     break;\r
390   }\r
391   return FALSE;\r
392 }\r
393 \r
394 \r
395 VOID GameListPopUp(FILE *fp, char *filename)\r
396 {\r
397   FARPROC lpProc;\r
398 \r
399   gameFile = fp;\r
400   if (gameFileName != filename) {\r
401     if (gameFileName) free(gameFileName);\r
402     gameFileName = StrSave(filename);\r
403   }\r
404   CheckMenuItem(GetMenu(hwndMain), IDM_ShowGameList, MF_CHECKED);\r
405   EnableMenuItem(GetMenu(hwndMain), IDM_SaveSelected, MF_ENABLED);\r
406   if (gameListDialog) {\r
407     SendMessage(gameListDialog, WM_INITDIALOG, 0, 0);\r
408     if (!gameListUp) ShowWindow(gameListDialog, SW_SHOW);\r
409     else SetFocus(gameListDialog);\r
410   } else {\r
411     lpProc = MakeProcInstance((FARPROC)GameListDialog, hInst);\r
412     CreateDialog(hInst, MAKEINTRESOURCE(DLG_GameList),\r
413       hwndMain, (DLGPROC)lpProc);\r
414     FreeProcInstance(lpProc);\r
415   }\r
416   gameListUp = TRUE;\r
417 }\r
418 \r
419 FILE *GameFile()\r
420 {\r
421   return gameFile;\r
422 }\r
423 \r
424 VOID GameListPopDown(void)\r
425 {\r
426   CheckMenuItem(GetMenu(hwndMain), IDM_ShowGameList, MF_UNCHECKED);\r
427   EnableMenuItem(GetMenu(hwndMain), IDM_SaveSelected, MF_GRAYED);\r
428   if (gameListDialog) ShowWindow(gameListDialog, SW_HIDE);\r
429   gameListUp = FALSE;\r
430 }\r
431 \r
432 \r
433 VOID GameListHighlight(int index)\r
434 {\r
435   char buf[MSG_SIZ];\r
436   int i, j, k, n, res = 0;\r
437   if (gameListDialog == NULL) return;\r
438   for(i=64; ; i+=i) {\r
439         res = SendDlgItemMessage( gameListDialog, OPT_GameListText, LB_GETTEXT, i, (LPARAM)buf );\r
440         if(res == LB_ERR || index < atoi( buf )) break;\r
441   }\r
442   j = i/2;\r
443   while(i-j > 1) {\r
444         n = (i + j) >> 1;\r
445         res = SendDlgItemMessage( gameListDialog, OPT_GameListText, LB_GETTEXT, n, (LPARAM)buf );\r
446         if(res == LB_ERR || index < (k = atoi( buf ))) i = n; else {\r
447             j = n;\r
448             if(index == k) break;\r
449         }\r
450   }\r
451   SendDlgItemMessage(gameListDialog, OPT_GameListText, LB_SETCURSEL, j, 0);\r
452 }\r
453 \r
454 \r
455 VOID GameListDestroy()\r
456 {\r
457   GameListPopDown();\r
458   if (gameFileName) {\r
459     free(gameFileName);\r
460     gameFileName = NULL;\r
461   }\r
462 }\r
463 \r
464 VOID ShowGameListProc()\r
465 {\r
466   if (gameListUp) {\r
467     if(gameListDialog) SetFocus(gameListDialog);\r
468 //    GameListPopDown();\r
469   } else {\r
470     if (gameFileName) {\r
471       GameListPopUp(gameFile, gameFileName);\r
472     } else {\r
473       DisplayError(_("No game list"), 0);\r
474     }\r
475   }\r
476 }\r
477 \r
478 HGLOBAL ExportGameListAsText()\r
479 {\r
480     HGLOBAL result = NULL;\r
481     LPVOID lpMem = NULL;\r
482     ListGame * lg = (ListGame *) gameList.head;\r
483     int nItem;\r
484     DWORD dwLen = 0;\r
485 \r
486     if( ! gameFileName || ((ListGame *) gameList.tailPred)->number <= 0 ) {\r
487         DisplayError(_(_("Game list not loaded or empty")), 0);\r
488         return NULL;\r
489     }\r
490 \r
491     /* Get list size */\r
492     for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
493         char * st = GameListLineFull(lg->number, &lg->gameInfo);\r
494 \r
495         dwLen += strlen(st) + 2; /* Add extra characters for "\r\n" */\r
496 \r
497         free(st);\r
498         lg = (ListGame *) lg->node.succ;\r
499     }\r
500 \r
501     /* Allocate memory for the list */\r
502     result = GlobalAlloc(GHND, dwLen+1 );\r
503 \r
504     if( result != NULL ) {\r
505         lpMem = GlobalLock(result);\r
506     }\r
507 \r
508     /* Copy the list into the global memory block */\r
509     if( lpMem != NULL ) {\r
510         char * dst = (char *) lpMem;\r
511         size_t len;\r
512 \r
513         lg = (ListGame *) gameList.head;\r
514 \r
515         for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
516             char * st = GameListLineFull(lg->number, &lg->gameInfo);\r
517 \r
518             len = sprintf( dst, "%s\r\n", st );\r
519             dst += len;\r
520 \r
521             free(st);\r
522             lg = (ListGame *) lg->node.succ;\r
523         }\r
524 \r
525         GlobalUnlock( result );\r
526     }\r
527 \r
528     return result;\r
529 }\r