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