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