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