changes from Alessandro Scotti from 20051231
[xboard.git] / winboard / whistory.c
1 /*
2  * Move history for WinBoard
3  *
4  * Author: Alessandro Scotti
5  *
6  * ------------------------------------------------------------------------
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  * ------------------------------------------------------------------------
21  */
22 #include "config.h"
23
24 #include <windows.h> /* required for all Windows applications */
25 #include <richedit.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <malloc.h>
29 #include <commdlg.h>
30 #include <dlgs.h>
31
32 #include "common.h"
33 #include "winboard.h"
34 #include "frontend.h"
35 #include "backend.h"
36
37 #include "wsnap.h"
38
39 VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo );
40 VOID MoveHistoryPopUp();
41 VOID MoveHistoryPopDown();
42 BOOL MoveHistoryIsUp();
43
44 /* Imports from backend.c */
45 char * SavePart(char *str);
46
47 /* Imports from winboard.c */
48 extern HWND moveHistoryDialog;
49 extern BOOLEAN moveHistoryDialogUp;
50
51 extern HINSTANCE hInst;
52 extern HWND hwndMain;
53
54 extern WindowPlacement wpMoveHistory;
55
56 /* Module globals */
57 typedef char MoveHistoryString[ MOVE_LEN*2 ];
58
59 static int lastFirst = 0;
60 static int lastLast = 0;
61 static int lastCurrent = -1;
62
63 static MoveHistoryString * currMovelist;
64 static ChessProgramStats_Move * currPvInfo;
65 static int currFirst = 0;
66 static int currLast = 0;
67 static int currCurrent = -1;
68
69 typedef struct {
70     int memoOffset;
71     int memoLength;
72 } HistoryMove;
73
74 static HistoryMove histMoves[ MAX_MOVES ];
75
76 #define WM_REFRESH_HISTORY  (WM_USER+4657)
77
78 #define DEFAULT_COLOR       0xFFFFFFFF
79
80 #define H_MARGIN            2
81 #define V_MARGIN            2
82
83 /* Note: in the following code a "Memo" is a Rich Edit control (it's Delphi lingo) */
84
85 static VOID HighlightMove( int index, BOOL highlight )
86 {
87     if( index >= 0 && index < MAX_MOVES ) {
88         CHARFORMAT cf;
89         HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );
90
91         SendMessage( hMemo,
92             EM_SETSEL,
93             histMoves[index].memoOffset,
94             histMoves[index].memoOffset + histMoves[index].memoLength );
95
96
97         /* Set style */
98         ZeroMemory( &cf, sizeof(cf) );
99
100         cf.cbSize = sizeof(cf);
101         cf.dwMask = CFM_BOLD | CFM_COLOR;
102
103         if( highlight ) {
104             cf.dwEffects |= CFE_BOLD;
105             cf.crTextColor = RGB( 0x00, 0x00, 0xFF );
106         }
107         else {
108             cf.dwEffects |= CFE_AUTOCOLOR;
109         }
110
111         SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );
112     }
113 }
114
115 static BOOL OnlyCurrentPositionChanged()
116 {
117     BOOL result = FALSE;
118
119     if( lastFirst >= 0 &&
120         lastLast >= lastFirst &&
121         lastCurrent >= lastFirst &&
122         currFirst == lastFirst &&
123         currLast == lastLast &&
124         currCurrent >= 0 &&
125         TRUE )
126     {
127         result = TRUE;
128     }
129
130     return result;
131 }
132
133 static BOOL OneMoveAppended()
134 {
135     BOOL result = FALSE;
136
137     if( lastCurrent >= 0 && lastCurrent >= lastFirst && lastLast >= lastFirst &&
138         currCurrent >= 0 && currCurrent >= currFirst && currLast >= currFirst &&
139         lastFirst == currFirst &&
140         lastLast == (currLast-1) &&
141         lastCurrent == (currCurrent-1) &&
142         currCurrent == (currLast-1) &&
143         TRUE )
144     {
145         result = TRUE;
146     }
147
148     return result;
149 }
150
151 static VOID ClearMemo()
152 {
153     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_SETTEXT, 0, (LPARAM) "" );
154 }
155
156 static int AppendToMemo( char * text, DWORD flags, DWORD color )
157 {
158     CHARFORMAT cf;
159
160     HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );
161
162     /* Select end of text */
163     int cbTextLen = (int) SendMessage( hMemo, WM_GETTEXTLENGTH, 0, 0 );
164
165     SendMessage( hMemo, EM_SETSEL, cbTextLen, cbTextLen );
166
167     /* Set style */
168     ZeroMemory( &cf, sizeof(cf) );
169
170     cf.cbSize = sizeof(cf);
171     cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_COLOR | CFM_UNDERLINE;
172     cf.dwEffects = flags;
173
174     if( color != DEFAULT_COLOR ) {
175         cf.crTextColor = color;
176     }
177     else {
178         cf.dwEffects |= CFE_AUTOCOLOR;
179     }
180
181     SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );
182
183     /* Append text */
184     SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );
185
186     /* Return offset of appended text */
187     return cbTextLen;
188 }
189
190 static VOID AppendMoveToMemo( int index )
191 {
192     char buf[64];
193     DWORD flags = 0;
194     DWORD color = DEFAULT_COLOR;
195
196     if( index < 0 || index >= MAX_MOVES ) {
197         return;
198     }
199
200     buf[0] = '\0';
201
202     /* Move number */
203     if( (index % 2) == 0 ) {
204         sprintf( buf, "%d.%s ", (index / 2)+1, index & 1 ? ".." : "" );
205         AppendToMemo( buf, CFE_BOLD, DEFAULT_COLOR );
206     }
207
208     /* Move text */
209     strcpy( buf, SavePart( currMovelist[index] ) );
210     strcat( buf, " " );
211
212     histMoves[index].memoOffset = AppendToMemo( buf, flags, color );
213     histMoves[index].memoLength = strlen(buf)-1;
214
215     /* PV info (if any) */
216     if( appData.showEvalInMoveHistory && currPvInfo[index].depth > 0 ) {
217         sprintf( buf, "%{%s%.2f/%d} ",
218             currPvInfo[index].score >= 0 ? "+" : "",
219             currPvInfo[index].score / 100.0,
220             currPvInfo[index].depth );
221
222         AppendToMemo( buf, flags,
223             color == DEFAULT_COLOR ? GetSysColor(COLOR_GRAYTEXT) : color );
224     }
225 }
226
227 static void RefreshMemoContent()
228 {
229     int i;
230
231     ClearMemo();
232
233     for( i=currFirst; i<currLast; i++ ) {
234         AppendMoveToMemo( i );
235     }
236 }
237
238 static void MemoContentUpdated()
239 {
240     int caretPos;
241
242     HighlightMove( lastCurrent, FALSE );
243     HighlightMove( currCurrent, TRUE );
244
245     lastFirst = currFirst;
246     lastLast = currLast;
247     lastCurrent = currCurrent;
248
249     /* Deselect any text, move caret to end of memo */
250     if( currCurrent >= 0 ) {
251         caretPos = histMoves[currCurrent].memoOffset + histMoves[currCurrent].memoLength;
252     }
253     else {
254         caretPos = (int) SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_GETTEXTLENGTH, 0, 0 );
255     }
256
257     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETSEL, caretPos, caretPos );
258
259     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SCROLLCARET, 0, 0 );
260 }
261
262 int FindMoveByCharIndex( int char_index )
263 {
264     int index;
265
266     for( index=currFirst; index<currLast; index++ ) {
267         if( char_index >= histMoves[index].memoOffset &&
268             char_index <  (histMoves[index].memoOffset + histMoves[index].memoLength) )
269         {
270             return index;
271         }
272     }
273
274     return -1;
275 }
276
277 LRESULT CALLBACK HistoryDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
278 {
279     static SnapData sd;
280
281     switch (message) {
282     case WM_INITDIALOG:
283         if( moveHistoryDialog == NULL ) {
284             moveHistoryDialog = hDlg;
285
286             /* Enable word wrapping and notifications */
287             SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETTARGETDEVICE, 0, 0 );
288
289             SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );
290
291             /* Restore window placement */
292             RestoreWindowPlacement( hDlg, &wpMoveHistory );
293         }
294
295         /* Update memo */
296         RefreshMemoContent();
297
298         MemoContentUpdated();
299
300         return FALSE;
301
302     case WM_COMMAND:
303         switch (LOWORD(wParam)) {
304         case IDOK:
305           EndDialog(hDlg, TRUE);
306           return TRUE;
307
308         case IDCANCEL:
309           EndDialog(hDlg, FALSE);
310           return TRUE;
311
312         default:
313           break;
314         }
315
316         break;
317
318     case WM_NOTIFY:
319         if( wParam == IDC_MoveHistory ) {
320             MSGFILTER * lpMF = (MSGFILTER *) lParam;
321
322             if( lpMF->msg == WM_LBUTTONDBLCLK && (lpMF->wParam & (MK_CONTROL | MK_SHIFT)) == 0 ) {
323                 POINTL pt;
324                 LRESULT index;
325
326                 pt.x = LOWORD( lpMF->lParam );
327                 pt.y = HIWORD( lpMF->lParam );
328
329                 index = SendDlgItemMessage( hDlg, IDC_MoveHistory, EM_CHARFROMPOS, 0, (LPARAM) &pt );
330
331                 index = FindMoveByCharIndex( index );
332
333                 if( index >= 0 ) {
334                     ToNrEvent( index + 1 );
335                 }
336
337                 /* Zap the message for good: apparently, returning non-zero is not enough */
338                 lpMF->msg = WM_USER;
339
340                 return TRUE;
341             }
342         }
343         break;
344
345     case WM_REFRESH_HISTORY:
346         /* Update the GUI */
347         if( OnlyCurrentPositionChanged() ) {
348             /* Only "cursor" changed, no need to update memo content */
349         }
350         else if( OneMoveAppended() ) {
351             AppendMoveToMemo( currCurrent );
352         }
353         else {
354             RefreshMemoContent();
355         }
356
357         MemoContentUpdated();
358
359         break;
360
361     case WM_SIZE:
362         SetWindowPos( GetDlgItem( moveHistoryDialog, IDC_MoveHistory ),
363             HWND_TOP,
364             H_MARGIN, V_MARGIN,
365             LOWORD(lParam) - 2*H_MARGIN,
366             HIWORD(lParam) - 2*V_MARGIN,
367             SWP_NOZORDER );
368         break;
369
370     case WM_GETMINMAXINFO:
371         {
372             MINMAXINFO * mmi = (MINMAXINFO *) lParam;
373
374             mmi->ptMinTrackSize.x = 100;
375             mmi->ptMinTrackSize.y = 100;
376         }
377         break;
378
379     case WM_CLOSE:
380         MoveHistoryPopDown();
381         break;
382
383     case WM_ENTERSIZEMOVE:
384         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );
385
386     case WM_SIZING:
387         return OnSizing( &sd, hDlg, wParam, lParam );
388
389     case WM_MOVING:
390         return OnMoving( &sd, hDlg, wParam, lParam );
391
392     case WM_EXITSIZEMOVE:
393         return OnExitSizeMove( &sd, hDlg, wParam, lParam );
394     }
395
396     return FALSE;
397 }
398
399 VOID MoveHistoryPopUp()
400 {
401   FARPROC lpProc;
402
403   CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_CHECKED);
404
405   if( moveHistoryDialog ) {
406     SendMessage( moveHistoryDialog, WM_INITDIALOG, 0, 0 );
407
408     if( ! moveHistoryDialogUp ) {
409         ShowWindow(moveHistoryDialog, SW_SHOW);
410     }
411   }
412   else {
413     lpProc = MakeProcInstance( (FARPROC) HistoryDialogProc, hInst );
414
415     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */
416     CreateDialog( hInst, MAKEINTRESOURCE(DLG_MoveHistory), hwndMain, (DLGPROC)lpProc );
417
418     FreeProcInstance(lpProc);
419   }
420
421   moveHistoryDialogUp = TRUE;
422 }
423
424 VOID MoveHistoryPopDown()
425 {
426   CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_UNCHECKED);
427
428   if( moveHistoryDialog ) {
429       ShowWindow(moveHistoryDialog, SW_HIDE);
430   }
431
432   moveHistoryDialogUp = FALSE;
433 }
434
435 VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo )
436 {
437     /* [AS] Danger! For now we rely on the movelist parameter being a static variable! */
438
439     currMovelist = movelist;
440     currFirst = first;
441     currLast = last;
442     currCurrent = current;
443     currPvInfo = pvInfo;
444
445     if( moveHistoryDialog ) {
446         SendMessage( moveHistoryDialog, WM_REFRESH_HISTORY, 0, 0 );
447     }
448 }
449
450 BOOL MoveHistoryIsUp()
451 {
452     return moveHistoryDialogUp;
453 }