check if malloc.h is present before including it
[xboard.git] / history.c
1 /*
2  * Move history for WinBoard
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  * back-end part split off by HGM
6  *
7  * Copyright 2005 Alessandro Scotti
8  *
9  * ------------------------------------------------------------------------
10  *
11  * GNU XBoard is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or (at
14  * your option) any later version.
15  *
16  * GNU XBoard is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see http://www.gnu.org/licenses/. 
23  *
24  * ------------------------------------------------------------------------
25  ** See the file ChangeLog for a revision history.  */
26
27 #include "config.h"
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #if HAVE_MALLOC_H
32 #include <malloc.h>
33 #endif
34 #include <string.h>
35
36 #include "common.h"
37 #include "frontend.h"
38 #include "backend.h"
39
40 /* templates for low-level front-end tasks (requiring platform-dependent implementation) */
41 void ClearHistoryMemo P((void));                                   // essential
42 int AppendToHistoryMemo P(( char * text, int bold, int colorNr )); // essential (coloring / styling optional)
43 void HighlightMove P(( int from, int to, Boolean highlight ));     // optional (can be dummy)
44 void ScrollToCurrent P((int caretPos));                            // optional (can be dummy)
45
46 /* templates for front-end entry point to allow inquiring about front-end state */
47 Boolean MoveHistoryDialogExists P((void));
48 Boolean MoveHistoryIsUp P((void));
49
50 /* Module globals */
51 typedef char MoveHistoryString[ MOVE_LEN*2 ];
52
53 static int lastFirst = 0;
54 static int lastLast = 0;
55 static int lastCurrent = -1;
56
57 static char lastLastMove[ MOVE_LEN ];
58
59 static MoveHistoryString * currMovelist;
60 static ChessProgramStats_Move * currPvInfo;
61 static int currFirst = 0;
62 static int currLast = 0;
63 static int currCurrent = -1;
64
65 typedef struct {
66     int memoOffset;
67     int memoLength;
68 } HistoryMove;
69
70 static HistoryMove histMoves[ MAX_MOVES ];
71
72 /* Note: in the following code a "Memo" is a Rich Edit control (it's Delphi lingo) */
73
74 // back-end after replacing Windows data-types by equivalents
75 static Boolean OnlyCurrentPositionChanged()
76 {
77     Boolean result = FALSE;
78
79     if( lastFirst >= 0 &&
80         lastLast >= lastFirst &&
81         lastCurrent >= lastFirst && 
82         currFirst == lastFirst &&
83         currLast == lastLast &&
84         currCurrent >= 0 &&
85         TRUE )
86     {
87         result = TRUE;
88
89         /* Special case: last move changed */
90         if( currCurrent == currLast-1 ) {
91             if( strcmp( currMovelist[currCurrent], lastLastMove ) != 0 ) {
92                 result = FALSE;
93             }
94         }
95     }
96
97     return result;
98 }
99
100 // back-end, after replacing Windows data types
101 static Boolean OneMoveAppended()
102 {
103     Boolean result = FALSE;
104
105     if( lastCurrent >= 0 && lastCurrent >= lastFirst && lastLast >= lastFirst &&
106         currCurrent >= 0 && currCurrent >= currFirst && currLast >= currFirst &&
107         lastFirst == currFirst &&
108         lastLast == (currLast-1) &&
109         lastCurrent == (currCurrent-1) &&
110         currCurrent == (currLast-1) &&
111         TRUE )
112     {
113         result = TRUE;
114     }
115
116     return result;
117 }
118
119 // back-end, now that color and font-style are passed as numbers
120 static void AppendMoveToMemo( int index )
121 {
122     char buf[64];
123
124     if( index < 0 || index >= MAX_MOVES ) {
125         return;
126     }
127
128     buf[0] = '\0';
129
130     /* Move number */
131     if( (index % 2) == 0 ) {
132         sprintf( buf, "%d.%s ", (index / 2)+1, index & 1 ? ".." : "" );
133         AppendToHistoryMemo( buf, 1, 0 ); // [HGM] 1 means bold, 0 default color
134     }
135
136     /* Move text */
137     safeStrCpy( buf, SavePart( currMovelist[index]) , sizeof( buf)/sizeof( buf[0]) );
138     strcat( buf, " " );
139
140     histMoves[index].memoOffset = AppendToHistoryMemo( buf, 0, 0 );
141     histMoves[index].memoLength = strlen(buf)-1;
142
143     /* PV info (if any) */
144     if( appData.showEvalInMoveHistory && currPvInfo[index].depth > 0 ) {
145         sprintf( buf, "{%s%.2f/%d} ", 
146             currPvInfo[index].score >= 0 ? "+" : "",
147             currPvInfo[index].score / 100.0,
148             currPvInfo[index].depth );
149
150         AppendToHistoryMemo( buf, 0, 1); // [HGM] 1 means gray
151     }
152 }
153
154 // back-end
155 void RefreshMemoContent()
156 {
157     int i;
158
159     ClearHistoryMemo();
160
161     for( i=currFirst; i<currLast; i++ ) {
162         AppendMoveToMemo( i );
163     }
164 }
165
166 // back-end part taken out of HighlightMove to determine character positions
167 static void DoHighlight(int index, int onoff)
168 {
169     if( index >= 0 && index < MAX_MOVES ) {
170         HighlightMove( histMoves[index].memoOffset, 
171             histMoves[index].memoOffset + histMoves[index].memoLength, onoff );
172     }
173 }
174
175 // back-end, now that a wrapper is provided for the front-end code to do the actual scrolling
176 void MemoContentUpdated()
177 {
178     int caretPos;
179
180     DoHighlight( lastCurrent, FALSE );
181     DoHighlight( currCurrent, TRUE );
182
183     lastFirst = currFirst;
184     lastLast = currLast;
185     lastCurrent = currCurrent;
186     lastLastMove[0] = '\0';
187
188     if( lastLast > 0 ) {
189       safeStrCpy( lastLastMove, SavePart( currMovelist[lastLast-1] ) , sizeof( lastLastMove)/sizeof( lastLastMove[0]) );
190     }
191
192     /* Deselect any text, move caret to end of memo */
193     if( currCurrent >= 0 ) {
194         caretPos = histMoves[currCurrent].memoOffset + histMoves[currCurrent].memoLength;
195     }
196     else {
197         caretPos = -1;
198     }
199
200     ScrollToCurrent(caretPos);
201 }
202
203 // back-end. Must be called as double-click call-back on move-history text edit
204 void FindMoveByCharIndex( int char_index )
205 {
206     int index;
207
208     for( index=currFirst; index<currLast; index++ ) {
209         if( char_index >= histMoves[index].memoOffset &&
210             char_index <  (histMoves[index].memoOffset + histMoves[index].memoLength) )
211         {
212             ToNrEvent( index + 1 ); // moved here from call-back
213         }
214     }
215 }
216
217 // back-end. In WinBoard called by call-back, but could be called directly by SetIfExists?
218 void UpdateMoveHistory()
219 {
220         /* Update the GUI */
221         if( OnlyCurrentPositionChanged() ) {
222             /* Only "cursor" changed, no need to update memo content */
223         }
224         else if( OneMoveAppended() ) {
225             AppendMoveToMemo( currCurrent );
226         }
227         else {
228             RefreshMemoContent();
229         }
230
231         MemoContentUpdated();
232 }
233
234 // back-end
235 void MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo )
236 {
237     /* [AS] Danger! For now we rely on the movelist parameter being a static variable! */
238
239     currMovelist = movelist;
240     currFirst = first;
241     currLast = last;
242     currCurrent = current;
243     currPvInfo = pvInfo;
244
245     if(MoveHistoryDialogExists())
246         UpdateMoveHistory(); // [HGM] call this directly, in stead of through call-back
247 }
248