1 /*
2  * gtkmovelist.c
3  * by Jon Kinsey, 2005
4  *
5  * Analysis move list
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of version 3 or later of the GNU General Public License as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * $Id: gtkmovelist.c,v 1.38 2018/05/13 17:05:24 plm Exp $
21  */
22 
23 #include "config.h"
24 #include "gtklocdefs.h"
25 #include "gtkgame.h"
26 #include <string.h>
27 
28 #include "format.h"
29 #include "gtkmovelistctrl.h"
30 #include "drawboard.h"
31 
32 #define DETAIL_COLUMN_COUNT 11
33 #define MIN_COLUMN_COUNT 5
34 
35 enum {
36     ML_COL_RANK = 0,
37     ML_COL_TYPE,
38     ML_COL_WIN,
39     ML_COL_GWIN,
40     ML_COL_BGWIN,
41     ML_COL_LOSS,
42     ML_COL_GLOSS,
43     ML_COL_BGLOSS,
44     ML_COL_EQUITY,
45     ML_COL_DIFF,
46     ML_COL_MOVE,
47     ML_COL_FGCOL,
48     ML_COL_DATA
49 };
50 
51 extern void
MoveListCreate(hintdata * phd)52 MoveListCreate(hintdata * phd)
53 {
54     static const char *aszTitleDetails[] = {
55         N_("Rank"),
56         N_("Type"),
57         N_("Win"),
58         N_("W g"),
59         N_("W bg"),
60         N_("Lose"),
61         N_("L g"),
62         N_("L bg"),
63         NULL,
64         N_("Diff."),
65         N_("noun|Move")
66     };
67     unsigned int i;
68     int showWLTree = showMoveListDetail && !phd->fDetails;
69 
70     /* Create list widget */
71     GtkListStore *store;
72     GtkTreeIter iter;
73     GtkTreeSelection *sel;
74     GtkWidget *view = gtk_tree_view_new();
75     int offset = (phd->fDetails) ? 0 : MIN_COLUMN_COUNT - DETAIL_COLUMN_COUNT;
76 
77     if (showWLTree) {
78         GtkStyle *psDefault = gtk_widget_get_style(view);
79 
80         GtkCellRenderer *renderer = custom_cell_renderer_movelist_new();
81         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_RANK], renderer,
82                                                     "movelist", 0, "rank", 1, NULL);
83         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
84         g_object_set(renderer, "cell-background-gdk", &psDefault->bg[GTK_STATE_NORMAL],
85                      "cell-background-set", TRUE, NULL);
86 
87         g_object_set_data(G_OBJECT(view), "hintdata", phd);
88     } else {
89         GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
90         g_object_set(renderer, "ypad", 0, NULL);
91 
92         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_RANK], renderer,
93                                                     "text", ML_COL_RANK, "foreground", ML_COL_FGCOL + offset, NULL);
94         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_TYPE], renderer,
95                                                     "text", ML_COL_TYPE, "foreground", ML_COL_FGCOL + offset, NULL);
96 
97         if (phd->fDetails) {
98             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_WIN], renderer,
99                                                         "text", ML_COL_WIN, "foreground", ML_COL_FGCOL + offset, NULL);
100             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_GWIN], renderer,
101                                                         "text", ML_COL_GWIN, "foreground", ML_COL_FGCOL + offset, NULL);
102             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_BGWIN],
103                                                         renderer, "text", ML_COL_BGWIN, "foreground",
104                                                         ML_COL_FGCOL + offset, NULL);
105             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_LOSS], renderer,
106                                                         "text", ML_COL_LOSS, "foreground", ML_COL_FGCOL + offset, NULL);
107             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_GLOSS],
108                                                         renderer, "text", ML_COL_GLOSS, "foreground",
109                                                         ML_COL_FGCOL + offset, NULL);
110             gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_BGLOSS],
111                                                         renderer, "text", ML_COL_BGLOSS, "foreground",
112                                                         ML_COL_FGCOL + offset, NULL);
113         }
114 
115         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_EQUITY], renderer,
116                                                     "text", ML_COL_EQUITY + offset, "foreground", ML_COL_FGCOL + offset,
117                                                     NULL);
118         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, aszTitleDetails[ML_COL_DIFF], renderer,
119                                                     "text", ML_COL_DIFF + offset, "foreground", ML_COL_FGCOL + offset,
120                                                     NULL);
121         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, Q_(aszTitleDetails[ML_COL_MOVE]), renderer,
122                                                     "text", ML_COL_MOVE + offset, "foreground", ML_COL_FGCOL + offset,
123                                                     NULL);
124     }
125 
126     phd->pwMoves = view;
127 
128     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
129     gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
130 
131     g_signal_connect(view, "row-activated", G_CALLBACK(HintDoubleClick), phd);
132     g_signal_connect(sel, "changed", G_CALLBACK(HintSelect), phd);
133 
134 
135     /* Add empty rows */
136     if (phd->fDetails)
137         store =
138             gtk_list_store_new(DETAIL_COLUMN_COUNT + 2, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
139                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
140                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
141     else {
142         if (showWLTree)
143             store = gtk_list_store_new(2, G_TYPE_POINTER, G_TYPE_INT);
144         else
145             store =
146                 gtk_list_store_new(MIN_COLUMN_COUNT + 2, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
147                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
148     }
149 
150     for (i = 0; i < phd->pml->cMoves; i++)
151         gtk_list_store_append(store, &iter);
152 
153     gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
154     MoveListUpdate(phd);
155 }
156 
157 float rBest;
158 
159 GtkStyle *psHighlight = NULL;
160 
161 extern void
MoveListRefreshSize(void)162 MoveListRefreshSize(void)
163 {
164     custom_cell_renderer_invalidate_size();
165     if (pwMoveAnalysis) {
166         hintdata *phd = (hintdata *) g_object_get_data(G_OBJECT(pwMoveAnalysis), "user_data");
167         MoveListUpdate(phd);
168     }
169 }
170 
171 /*
172  * Call UpdateMostList to update the movelist in the GTK hint window.
173  * For example, after new evaluations, rollouts or toggle of MWC/Equity.
174  *
175  */
176 extern void
MoveListUpdate(const hintdata * phd)177 MoveListUpdate(const hintdata * phd)
178 {
179     unsigned int i, j, colNum;
180     char sz[32];
181     cubeinfo ci;
182     movelist *pml = phd->pml;
183     int col = phd->fDetails ? 8 : 2;
184     int showWLTree = showMoveListDetail && !phd->fDetails;
185 
186     int offset = (phd->fDetails) ? 0 : MIN_COLUMN_COUNT - DETAIL_COLUMN_COUNT;
187     GtkTreeIter iter;
188     GtkListStore *store;
189     store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(phd->pwMoves)));
190     gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
191 
192     if (!psHighlight) {         /* Get highlight style first time in */
193         GtkStyle *psTemp;
194         GtkStyle *psMoves = gtk_widget_get_style(phd->pwMoves);
195         GetStyleFromRCFile(&psHighlight, "move-done", psMoves);
196         /* Use correct background colour when selected */
197         memcpy(&psHighlight->bg[GTK_STATE_SELECTED], &psMoves->bg[GTK_STATE_SELECTED], sizeof(GdkColor));
198 
199         /* Also get colour to use for w/l stats in detail view */
200         GetStyleFromRCFile(&psTemp, "move-winlossfg", psMoves);
201         memcpy(&wlCol, &psTemp->fg[GTK_STATE_NORMAL], sizeof(GdkColor));
202         g_object_unref(psTemp);
203     }
204 
205     /* This function should only be called when the game state matches
206      * the move list. */
207     g_assert(ms.fMove == 0 || ms.fMove == 1);
208 
209     GetMatchStateCubeInfo(&ci, &ms);
210     rBest = pml->amMoves[0].rScore;
211 
212     if (!showWLTree)
213         gtk_tree_view_column_set_title(gtk_tree_view_get_column(GTK_TREE_VIEW(phd->pwMoves), col),
214                                        (fOutputMWC && ms.nMatchTo) ? _("MWC") : _("Equity"));
215 
216     for (i = 0; i < pml->cMoves; i++) {
217         float *ar = pml->amMoves[i].arEvalMove;
218         int rankKnown;
219         const char *highlight_sz;
220 
221         if (showWLTree)
222             gtk_list_store_set(store, &iter, 0, pml->amMoves + i, -1);
223         else
224             gtk_list_store_set(store, &iter, ML_COL_DATA + offset, pml->amMoves + i, -1);
225 
226         rankKnown = 1;
227         if (i && i == pml->cMoves - 1 && phd->piHighlight && i == *phd->piHighlight)
228             /* The move made is the last on the list.  Some moves might
229              * have been deleted to fit this one in */
230         {
231             /* Lets count how many moves are possible to see if this is the last move */
232             movelist ml;
233             int dice[2];
234             memcpy(dice, ms.anDice, sizeof(dice));
235             if (!dice[0]) {     /* If the dice have got lost, try to find them */
236                 moverecord *pmr = (moverecord *) plLastMove->plNext->p;
237                 if (pmr) {
238                     dice[0] = pmr->anDice[0];
239                     dice[1] = pmr->anDice[1];
240                 }
241             }
242             GenerateMoves(&ml, msBoard(), dice[0], dice[1], FALSE);
243             if (i < ml.cMoves - 1)
244                 rankKnown = 0;
245         }
246 
247         highlight_sz = (phd->piHighlight && *phd->piHighlight == i) ? "*" : "";
248 
249         if (rankKnown)
250             sprintf(sz, "%s%s%u", pml->amMoves[i].cmark ? "+" : "", highlight_sz, i + 1);
251         else
252             sprintf(sz, "%s%s??", pml->amMoves[i].cmark ? "+" : "", highlight_sz);
253 
254         if (showWLTree) {
255             gtk_list_store_set(store, &iter, 1, rankKnown ? (int) i + 1 : -1, -1);
256             goto skipoldcode;
257         } else
258             gtk_list_store_set(store, &iter, ML_COL_RANK, sz, -1);
259         FormatEval(sz, &pml->amMoves[i].esMove);
260         gtk_list_store_set(store, &iter, ML_COL_TYPE, sz, -1);
261 
262         /* gwc */
263         if (phd->fDetails) {
264             colNum = ML_COL_WIN;
265             for (j = 0; j < 5; j++) {
266                 if (j == 3) {
267                     gtk_list_store_set(store, &iter, colNum, OutputPercent(1.0f - ar[OUTPUT_WIN]), -1);
268                     colNum++;
269                 }
270                 gtk_list_store_set(store, &iter, colNum, OutputPercent(ar[j]), -1);
271                 colNum++;
272             }
273         }
274 
275         /* cubeless equity */
276         gtk_list_store_set(store, &iter, ML_COL_EQUITY + offset, OutputEquity(pml->amMoves[i].rScore, &ci, TRUE), -1);
277         if (i != 0) {
278             gtk_list_store_set(store, &iter, ML_COL_DIFF + offset,
279                                OutputEquityDiff(pml->amMoves[i].rScore, rBest, &ci), -1);
280         }
281 
282         gtk_list_store_set(store, &iter, ML_COL_MOVE + offset, FormatMove(sz, msBoard(), pml->amMoves[i].anMove), -1);
283 
284         /* highlight row */
285         if (phd->piHighlight && *phd->piHighlight == i) {
286             char buf[20];
287             sprintf(buf, "#%02x%02x%02x", psHighlight->fg[GTK_STATE_SELECTED].red / 256,
288                     psHighlight->fg[GTK_STATE_SELECTED].green / 256, psHighlight->fg[GTK_STATE_SELECTED].blue / 256);
289             gtk_list_store_set(store, &iter, ML_COL_FGCOL + offset, buf, -1);
290         } else
291             gtk_list_store_set(store, &iter, ML_COL_FGCOL + offset, NULL, -1);
292       skipoldcode:             /* Messy as 3 copies of code at moment... */
293         gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
294     }
295 
296 }
297 
298 extern GList *
MoveListGetSelectionList(const hintdata * phd)299 MoveListGetSelectionList(const hintdata * phd)
300 {
301     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(phd->pwMoves));
302     GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(phd->pwMoves));
303     return gtk_tree_selection_get_selected_rows(sel, &model);
304 }
305 
306 /* gtk_tree_path_free() is not the right function type
307    to be called directly from g_list_foreach() below
308 */
309 static inline void
my_gtk_tree_path_free(gpointer data,gpointer UNUSED (user_data))310 my_gtk_tree_path_free(gpointer data, gpointer UNUSED(user_data))
311 {
312     gtk_tree_path_free((GtkTreePath *) data);
313 }
314 
315 extern void
MoveListFreeSelectionList(GList * pl)316 MoveListFreeSelectionList(GList * pl)
317 {
318     g_list_foreach(pl, my_gtk_tree_path_free, NULL);
319     g_list_free(pl);
320 }
321 
322 extern move *
MoveListGetMove(const hintdata * phd,GList * pl)323 MoveListGetMove(const hintdata * phd, GList * pl)
324 {
325     move *m;
326     int showWLTree = showMoveListDetail && !phd->fDetails;
327     int col, offset = (phd->fDetails) ? 0 : MIN_COLUMN_COUNT - DETAIL_COLUMN_COUNT;
328     GtkTreeIter iter;
329     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(phd->pwMoves));
330 
331     gboolean check = gtk_tree_model_get_iter(model, &iter, (GtkTreePath *) (pl->data));
332     if (check == 0)
333         printf("Error in move list!\n");
334 
335     if (showWLTree)
336         col = 0;
337     else
338         col = ML_COL_DATA + offset;
339     gtk_tree_model_get(model, &iter, col, &m, -1);
340     return m;
341 }
342 
343 extern void
MoveListShowToggledClicked(GtkWidget * UNUSED (pw),hintdata * phd)344 MoveListShowToggledClicked(GtkWidget * UNUSED(pw), hintdata * phd)
345 {
346     int f = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(phd->pwShow));
347     if (f)
348         gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(phd->pwMoves)), GTK_SELECTION_SINGLE);
349     else
350         gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(phd->pwMoves)), GTK_SELECTION_MULTIPLE);
351 
352     ShowMove(phd, f);
353 }
354 
355 extern gint
MoveListClearSelection(GtkWidget * UNUSED (pw),GdkEventSelection * UNUSED (pes),hintdata * phd)356 MoveListClearSelection(GtkWidget * UNUSED(pw), GdkEventSelection * UNUSED(pes), hintdata * phd)
357 {
358     gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(phd->pwMoves)));
359     return TRUE;
360 }
361