Cure flicker in Move History window, fix highlighting
[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 #include <string.h>
32
33 #include "common.h"
34 #include "frontend.h"
35 #include "backend.h"
36
37 /* templates for low-level front-end tasks (requiring platform-dependent implementation) */
38 void ClearHistoryMemo P((void));                                   // essential
39 int AppendToHistoryMemo P(( char * text, int bold, int colorNr )); // essential (coloring / styling optional)
40 void HighlightMove P(( int from, int to, Boolean highlight ));     // optional (can be dummy)
41 void ScrollToCurrent P((int caretPos));                            // optional (can be dummy)
42
43 /* templates for front-end entry point to allow inquiring about front-end state */
44 Boolean MoveHistoryDialogExists P((void));
45 Boolean MoveHistoryIsUp P((void));
46
47 /* Module globals */
48 typedef char MoveHistoryString[ MOVE_LEN*2 ];
49
50 static int lastFirst = 0;
51 static int lastLast = 0;
52 static int lastCurrent = -1;
53
54 static char lastLastMove[ MOVE_LEN ];
55
56 static MoveHistoryString * currMovelist;
57 static ChessProgramStats_Move * currPvInfo;
58 static int currFirst = 0;
59 static int currLast = 0;
60 static int currCurrent = -1;
61
62 typedef struct {
63     int memoOffset;
64     int memoLength;
65 } HistoryMove;
66
67 static HistoryMove histMoves[ MAX_MOVES ];
68
69 /* Note: in the following code a "Memo" is a Rich Edit control (it's Delphi lingo) */
70
71 // back-end after replacing Windows data-types by equivalents
72 static Boolean OnlyCurrentPositionChanged()
73 {
74     Boolean result = FALSE;
75
76     if( lastFirst >= 0 &&
77         lastLast >= lastFirst &&
78         lastCurrent >= lastFirst && 
79         currFirst == lastFirst &&
80         currLast == lastLast &&
81         currCurrent >= 0 &&
82         TRUE )
83     {
84         result = TRUE;
85
86         /* Special case: last move changed */
87         if( currCurrent == currLast-1 ) {
88             if( strcmp( currMovelist[currCurrent], lastLastMove ) != 0 ) {
89                 result = FALSE;
90             }
91         }
92     }
93
94     return result;
95 }
96
97 // back-end, after replacing Windows data types
98 static Boolean OneMoveAppended()
99 {
100     Boolean result = FALSE;
101
102     if( lastCurrent >= 0 && lastCurrent >= lastFirst && lastLast >= lastFirst &&
103         currCurrent >= 0 && currCurrent >= currFirst && currLast >= currFirst &&
104         lastFirst == currFirst &&
105         lastLast == (currLast-1) &&
106         lastCurrent == (currCurrent-1) &&
107         currCurrent == (currLast-1) &&
108         TRUE )
109     {
110         result = TRUE;
111     }
112
113     return result;
114 }
115
116 // back-end, now that color and font-style are passed as numbers
117 static void AppendMoveToMemo( int index )
118 {
119     char buf[64];
120
121     if( index < 0 || index >= MAX_MOVES ) {
122         return;
123     }
124
125     buf[0] = '\0';
126
127     /* Move number */
128     if( (index % 2) == 0 ) {
129         sprintf( buf, "%d.%s ", (index / 2)+1, index & 1 ? ".." : "" );
130         AppendToHistoryMemo( buf, 1, 0 ); // [HGM] 1 means bold, 0 default color
131     }
132
133     /* Move text */
134     safeStrCpy( buf, SavePart( currMovelist[index]) , sizeof( buf)/sizeof( buf[0]) );
135     strcat( buf, " " );
136
137     histMoves[index].memoOffset = AppendToHistoryMemo( buf, 0, 0 );
138     histMoves[index].memoLength = strlen(buf)-1;
139
140     /* PV info (if any) */
141     if( appData.showEvalInMoveHistory && currPvInfo[index].depth > 0 ) {
142         sprintf( buf, "{%s%.2f/%d} ", 
143             currPvInfo[index].score >= 0 ? "+" : "",
144             currPvInfo[index].score / 100.0,
145             currPvInfo[index].depth );
146
147         AppendToHistoryMemo( buf, 0, 1); // [HGM] 1 means gray
148     }
149 }
150
151 // back-end
152 void RefreshMemoContent()
153 {
154     int i;
155
156     ClearHistoryMemo();
157
158     for( i=currFirst; i<currLast; i++ ) {
159         AppendMoveToMemo( i );
160     }
161 }
162
163 // back-end part taken out of HighlightMove to determine character positions
164 static void DoHighlight(int index, int onoff)
165 {
166     if( index >= 0 && index < MAX_MOVES ) {
167         HighlightMove( histMoves[index].memoOffset, 
168             histMoves[index].memoOffset + histMoves[index].memoLength, onoff );
169     }
170 }
171
172 // back-end, now that a wrapper is provided for the front-end code to do the actual scrolling
173 void MemoContentUpdated()
174 {
175     int caretPos;
176
177     if(lastCurrent <= currLast) DoHighlight( lastCurrent, FALSE );
178
179     lastFirst = currFirst;
180     lastLast = currLast;
181     lastCurrent = currCurrent;
182     lastLastMove[0] = '\0';
183
184     if( lastLast > 0 ) {
185       safeStrCpy( lastLastMove, SavePart( currMovelist[lastLast-1] ) , sizeof( lastLastMove)/sizeof( lastLastMove[0]) );
186     }
187
188     /* Deselect any text, move caret to end of memo */
189     if( currCurrent >= 0 ) {
190         caretPos = histMoves[currCurrent].memoOffset + histMoves[currCurrent].memoLength;
191     }
192     else {
193         caretPos = -1;
194     }
195
196     ScrollToCurrent(caretPos);
197     DoHighlight( currCurrent, TRUE ); // [HGM] moved last, because in X some scrolling methods spoil highlighting
198 }
199
200 // back-end. Must be called as double-click call-back on move-history text edit
201 void FindMoveByCharIndex( int char_index )
202 {
203     int index;
204
205     for( index=currFirst; index<currLast; index++ ) {
206         if( char_index >= histMoves[index].memoOffset &&
207             char_index <  (histMoves[index].memoOffset + histMoves[index].memoLength) )
208         {
209             ToNrEvent( index + 1 ); // moved here from call-back
210         }
211     }
212 }
213
214 // back-end. In WinBoard called by call-back, but could be called directly by SetIfExists?
215 void UpdateMoveHistory()
216 {
217         /* Update the GUI */
218         if( OnlyCurrentPositionChanged() ) {
219             /* Only "cursor" changed, no need to update memo content */
220         }
221         else if( OneMoveAppended() ) {
222             AppendMoveToMemo( currCurrent );
223         }
224         else {
225             RefreshMemoContent();
226         }
227
228         MemoContentUpdated();
229 }
230
231 // back-end
232 void MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo )
233 {
234     /* [AS] Danger! For now we rely on the movelist parameter being a static variable! */
235
236     currMovelist = movelist;
237     currFirst = first;
238     currLast = last;
239     currCurrent = current;
240     currPvInfo = pvInfo;
241
242     if(MoveHistoryDialogExists())
243         UpdateMoveHistory(); // [HGM] call this directly, in stead of through call-back
244 }
245