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