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