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