1/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2010-2013 Robert Ancell
4 * Copyright (C) 2013-2020 Michael Catanzaro
5 *
6 * This program is free software: you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
10 * license.
11 */
12
13[GtkTemplate (ui = "/org/gnome/Chess/ui/chess-window.ui")]
14public class ChessWindow : Gtk.ApplicationWindow
15{
16    public enum LayoutMode {
17        NORMAL,
18        NARROW
19    }
20
21    private LayoutMode _layout_mode = LayoutMode.NORMAL;
22    public LayoutMode layout_mode
23    {
24        get { return _layout_mode; }
25
26        private set
27        {
28            if (_layout_mode == value)
29                return;
30
31            _layout_mode = value;
32
33            Idle.add(() => {
34                navigation_box.set_orientation (value == LayoutMode.NORMAL ? Gtk.Orientation.HORIZONTAL : Gtk.Orientation.VERTICAL);
35                return Source.REMOVE;
36            });
37        }
38    }
39
40    public ChessView view
41    {
42        get; private set;
43    }
44
45    public ChessScene scene
46    {
47        get { return view.scene; }
48    }
49
50    public ChessGame? game
51    {
52        get { return scene.game; }
53    }
54
55    private ChessApplication app;
56
57    [GtkChild]
58    private unowned Gtk.Box main_box;
59    [GtkChild]
60    private unowned Gtk.InfoBar info_bar;
61    [GtkChild]
62    private unowned Gtk.Label info_bar_label;
63    [GtkChild]
64    private unowned Gtk.Button pause_resume_button;
65    [GtkChild]
66    private unowned Gtk.Box navigation_box;
67    [GtkChild]
68    private unowned Gtk.ComboBox history_combo;
69    [GtkChild]
70    private unowned Gtk.Box clock_box;
71    [GtkChild]
72    private unowned Gtk.DrawingArea white_time_label;
73    [GtkChild]
74    private unowned Gtk.DrawingArea black_time_label;
75
76    public ChessWindow (ChessApplication app)
77    {
78        this.app = app;
79
80        var scene = new ChessScene ();
81        scene.changed.connect (() => update_history_panel ());
82
83        view = new ChessView (scene);
84        main_box.insert_child_after (view, info_bar);
85
86        update_pause_resume_button ();
87
88        white_time_label.set_draw_func (draw_white_time_label);
89        black_time_label.set_draw_func (draw_black_time_label);
90
91        notify["default-height"].connect (window_state_changed_cb);
92        notify["default-width"].connect (window_state_changed_cb);
93    }
94
95    private void window_state_changed_cb ()
96    {
97        if (fullscreened || maximized)
98            return;
99
100        if (default_width == 0 || default_height == 0)
101            return;
102
103        if (default_width <= 500 && layout_mode == LayoutMode.NORMAL)
104            layout_mode = LayoutMode.NARROW;
105        else if (default_width > 500 && layout_mode == LayoutMode.NARROW)
106            layout_mode = LayoutMode.NORMAL;
107    }
108
109    public void update_game_status (string? title = null, string? info = null)
110    {
111        this.title = title != null ? title : app.compute_current_title ();
112        info_bar_label.label = info != null ? info : app.compute_status_info ();
113        /* Setting the label to null actually just sets it to an empty string. */
114        info_bar.visible = info_bar_label.label != "";
115    }
116
117    public void update_pause_resume_button ()
118    {
119        var game = scene.game;
120
121        if (game != null && game.clock == null)
122            pause_resume_button.hide ();
123        else
124            pause_resume_button.show ();
125
126        if (game != null && game.is_paused)
127        {
128            pause_resume_button.icon_name = "media-playback-start-symbolic";
129            pause_resume_button.tooltip_text = _("Unpause the game");
130        }
131        else
132        {
133            pause_resume_button.icon_name = "media-playback-pause-symbolic";
134            pause_resume_button.tooltip_text = _("Pause the game");
135        }
136    }
137
138    public void update_history_panel ()
139    {
140        if (game == null)
141            return;
142
143        var move_number = scene.move_number;
144        var n_moves = (int) game.n_moves;
145        if (move_number < 0)
146            move_number += 1 + n_moves;
147
148        if (n_moves > 0 && move_number != 0 && !game.is_paused)
149            app.enable_action (HISTORY_GO_FIRST_ACTION_NAME);
150        else
151            app.disable_action (HISTORY_GO_FIRST_ACTION_NAME);
152
153        if (move_number > 0 && !game.is_paused)
154            app.enable_action (HISTORY_GO_PREVIOUS_ACTION_NAME);
155        else
156            app.disable_action (HISTORY_GO_PREVIOUS_ACTION_NAME);
157
158        if (move_number < n_moves && !game.is_paused)
159            app.enable_action (HISTORY_GO_NEXT_ACTION_NAME);
160        else
161            app.disable_action (HISTORY_GO_NEXT_ACTION_NAME);
162
163        if (n_moves > 0 && move_number != n_moves && !game.is_paused)
164            app.enable_action (HISTORY_GO_LAST_ACTION_NAME);
165        else
166            app.disable_action (HISTORY_GO_LAST_ACTION_NAME);
167
168        history_combo.sensitive = !game.is_paused;
169
170        /* Set move text for all moves (it may have changed format) */
171        int i = n_moves;
172        foreach (var state in game.move_stack)
173        {
174            if (state.last_move != null)
175            {
176                Gtk.TreeIter iter;
177                if (history_combo.model.iter_nth_child (out iter, null, i))
178                    set_move_text (iter, state.last_move);
179            }
180            i--;
181        }
182
183        history_combo.set_active (move_number);
184    }
185
186    public void set_clock_visible (bool visible)
187    {
188        clock_box.visible = visible;
189    }
190
191    /* Compute the largest possible size the timer label might ever want to take.
192     * The size of the characters may vary by font, but one digit will always
193     * be the largest.
194     */
195    private int compute_time_label_width_request (Cairo.Context c)
196        ensures (result > 0)
197    {
198        Cairo.TextExtents extents;
199        double max = 0;
200
201        c.text_extents ("000∶00", out extents);
202        max = (max > extents.width ? max : extents.width);
203        c.text_extents ("111∶11", out extents);
204        max = (max > extents.width ? max : extents.width);
205        c.text_extents ("222∶22", out extents);
206        max = (max > extents.width ? max : extents.width);
207        c.text_extents ("333∶33", out extents);
208        max = (max > extents.width ? max : extents.width);
209        c.text_extents ("444∶44", out extents);
210        max = (max > extents.width ? max : extents.width);
211        c.text_extents ("555∶55", out extents);
212        max = (max > extents.width ? max : extents.width);
213        c.text_extents ("666∶66", out extents);
214        max = (max > extents.width ? max : extents.width);
215        c.text_extents ("777∶77", out extents);
216        max = (max > extents.width ? max : extents.width);
217        c.text_extents ("888∶88", out extents);
218        max = (max > extents.width ? max : extents.width);
219        c.text_extents ("999∶99", out extents);
220        max = (max > extents.width ? max : extents.width);
221
222        /* Leave a little bit of room to the sides. */
223        return (int) Math.ceil (max) + 6;
224    }
225
226    private void draw_time (Gtk.Widget widget, Cairo.Context c, int width, int height, string text, double[] fg, double[] bg)
227    {
228        /* We need to draw text on our cairo context to properly compute our
229         * required size. But the only place we are able to access the cairo
230         * context is here, the draw function. And we are not allowed to set our
231         * size inside the draw function. So the best we can do is schedule the
232         * size computation and queue draw again when that's done.
233         */
234        if (widget.width_request == -1)
235        {
236            Idle.add(() => {
237                widget.set_size_request (compute_time_label_width_request (c), -1);
238                widget.queue_draw ();
239                return Source.REMOVE;
240            });
241            return;
242        }
243
244        double alpha = 1.0;
245        if ((widget.get_state_flags () & Gtk.StateFlags.INSENSITIVE) != 0)
246            alpha = 0.5;
247        c.set_source_rgba (bg[0], bg[1], bg[2], alpha);
248        c.paint ();
249
250        c.set_source_rgba (fg[0], fg[1], fg[2], alpha);
251        c.select_font_face ("fixed", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
252        c.set_font_size (0.6 * widget.get_allocated_height ());
253        Cairo.TextExtents extents;
254        c.text_extents (text, out extents);
255        c.move_to ((widget.get_allocated_width () - extents.width) / 2 - extents.x_bearing,
256                   (widget.get_allocated_height () - extents.height) / 2 - extents.y_bearing);
257        c.show_text (text);
258    }
259
260    private string make_clock_text (ChessClock? clock, Color color)
261        requires (clock != null)
262    {
263        int time;
264        if (color == Color.WHITE)
265            time = clock.white_remaining_seconds;
266        else
267            time = clock.black_remaining_seconds;
268
269        if (time >= 60)
270            return "%d∶\xE2\x80\x8E%02d".printf (time / 60, time % 60);
271        else
272            return "∶\xE2\x80\x8E%02d".printf (time);
273    }
274
275    private void draw_white_time_label (Gtk.DrawingArea drawing_area, Cairo.Context c, int width, int height)
276    {
277        draw_time (drawing_area, c, width, height, make_clock_text (scene.game.clock, Color.WHITE), { 0.0, 0.0, 0.0 }, { 1.0, 1.0, 1.0 });
278    }
279
280    private void draw_black_time_label (Gtk.DrawingArea drawing_area, Cairo.Context c, int width, int height)
281    {
282        draw_time (drawing_area, c, width, height, make_clock_text (scene.game.clock, Color.BLACK), { 1.0, 1.0, 1.0 }, { 0.0, 0.0, 0.0 });
283    }
284
285    [GtkCallback]
286    private void history_combo_changed_cb (Gtk.ComboBox combo)
287    {
288        Gtk.TreeIter iter;
289        if (!combo.get_active_iter (out iter))
290            return;
291        int move_number;
292        combo.model.get (iter, 1, out move_number, -1);
293        if (game == null || move_number == game.n_moves)
294            move_number = -1;
295        scene.move_number = move_number;
296    }
297
298    public void set_move_text (Gtk.TreeIter iter, ChessMove move)
299    {
300        /* Note there are no move formats for pieces taking kings and this is not allowed in Chess rules */
301        const string human_descriptions[] = {/* Human Move String: Description of a white pawn moving from %1$s to %2s, e.g. 'c2 to c4' */
302                                             N_("White pawn moves from %1$s to %2$s"),
303                                             /* Human Move String: Description of a white pawn at %1$s capturing a pawn at %2$s */
304                                             N_("White pawn at %1$s takes the black pawn at %2$s"),
305                                             /* Human Move String: Description of a white pawn at %1$s capturing a rook at %2$s */
306                                             N_("White pawn at %1$s takes the black rook at %2$s"),
307                                             /* Human Move String: Description of a white pawn at %1$s capturing a knight at %2$s */
308                                             N_("White pawn at %1$s takes the black knight at %2$s"),
309                                             /* Human Move String: Description of a white pawn at %1$s capturing a bishop at %2$s */
310                                             N_("White pawn at %1$s takes the black bishop at %2$s"),
311                                             /* Human Move String: Description of a white pawn at %1$s capturing a queen at %2$s */
312                                             N_("White pawn at %1$s takes the black queen at %2$s"),
313                                             /* Human Move String: Description of a white rook moving from %1$s to %2$s, e.g. 'a1 to a5' */
314                                             N_("White rook moves from %1$s to %2$s"),
315                                             /* Human Move String: Description of a white rook at %1$s capturing a pawn at %2$s */
316                                             N_("White rook at %1$s takes the black pawn at %2$s"),
317                                             /* Human Move String: Description of a white rook at %1$s capturing a rook at %2$s */
318                                             N_("White rook at %1$s takes the black rook at %2$s"),
319                                             /* Human Move String: Description of a white rook at %1$s capturing a knight at %2$s */
320                                             N_("White rook at %1$s takes the black knight at %2$s"),
321                                             /* Human Move String: Description of a white rook at %1$s capturing a bishop at %2$s */
322                                             N_("White rook at %1$s takes the black bishop at %2$s"),
323                                             /* Human Move String: Description of a white rook at %1$s capturing a queen at %2$s" */
324                                             N_("White rook at %1$s takes the black queen at %2$s"),
325                                             /* Human Move String: Description of a white knight moving from %1$s to %2$s, e.g. 'b1 to c3' */
326                                             N_("White knight moves from %1$s to %2$s"),
327                                             /* Human Move String: Description of a white knight at %1$s capturing a pawn at %2$s */
328                                             N_("White knight at %1$s takes the black pawn at %2$s"),
329                                             /* Human Move String: Description of a white knight at %1$s capturing a rook at %2$s */
330                                             N_("White knight at %1$s takes the black rook at %2$s"),
331                                             /* Human Move String: Description of a white knight at %1$s capturing a knight at %2$s */
332                                             N_("White knight at %1$s takes the black knight at %2$s"),
333                                             /* Human Move String: Description of a white knight at %1$s capturing a bishop at %2$s */
334                                             N_("White knight at %1$s takes the black bishop at %2$s"),
335                                             /* Human Move String: Description of a white knight at %1$s capturing a queen at %2$s */
336                                             N_("White knight at %1$s takes the black queen at %2$s"),
337                                             /* Human Move String: Description of a white bishop moving from %1$s to %2$s, e.g. 'f1 to b5' */
338                                             N_("White bishop moves from %1$s to %2$s"),
339                                             /* Human Move String: Description of a white bishop at %1$s capturing a pawn at %2$s */
340                                             N_("White bishop at %1$s takes the black pawn at %2$s"),
341                                             /* Human Move String: Description of a white bishop at %1$s capturing a rook at %2$s */
342                                             N_("White bishop at %1$s takes the black rook at %2$s"),
343                                             /* Human Move String: Description of a white bishop at %1$s capturing a knight at %2$s */
344                                             N_("White bishop at %1$s takes the black knight at %2$s"),
345                                             /* Human Move String: Description of a white bishop at %1$s capturing a bishop at %2$s */
346                                             N_("White bishop at %1$s takes the black bishop at %2$s"),
347                                             /* Human Move String: Description of a white bishop at %1$s capturing a queen at %2$s */
348                                             N_("White bishop at %1$s takes the black queen at %2$s"),
349                                             /* Human Move String: Description of a white queen moving from %1$s to %2$s, e.g. 'd1 to d4' */
350                                             N_("White queen moves from %1$s to %2$s"),
351                                             /* Human Move String: Description of a white queen at %1$s capturing a pawn at %2$s */
352                                             N_("White queen at %1$s takes the black pawn at %2$s"),
353                                             /* Human Move String: Description of a white queen at %1$s capturing a rook at %2$s */
354                                             N_("White queen at %1$s takes the black rook at %2$s"),
355                                             /* Human Move String: Description of a white queen at %1$s capturing a knight at %2$s */
356                                             N_("White queen at %1$s takes the black knight at %2$s"),
357                                             /* Human Move String: Description of a white queen at %1$s capturing a bishop at %2$s */
358                                             N_("White queen at %1$s takes the black bishop at %2$s"),
359                                             /* Human Move String: Description of a white queen at %1$s capturing a queen at %2$s */
360                                             N_("White queen at %1$s takes the black queen at %2$s"),
361                                             /* Human Move String: Description of a white king moving from %1$s to %2$s, e.g. 'e1 to f1' */
362                                             N_("White king moves from %1$s to %2$s"),
363                                             /* Human Move String: Description of a white king at %1$s capturing a pawn at %2$s */
364                                             N_("White king at %1$s takes the black pawn at %2$s"),
365                                             /* Human Move String: Description of a white king at %1$s capturing a rook at %2$s */
366                                             N_("White king at %1$s takes the black rook at %2$s"),
367                                             /* Human Move String: Description of a white king at %1$s capturing a knight at %2$s */
368                                             N_("White king at %1$s takes the black knight at %2$s"),
369                                             /* Human Move String: Description of a white king at %1$s capturing a bishop at %2$s */
370                                             N_("White king at %1$s takes the black bishop at %2$s"),
371                                             /* Human Move String: Description of a white king at %1$s capturing a queen at %2$s */
372                                             N_("White king at %1$s takes the black queen at %2$s"),
373                                             /* Human Move String: Description of a black pawn moving from %1$s to %2$s, e.g. 'c8 to c6' */
374                                             N_("Black pawn moves from %1$s to %2$s"),
375                                             /* Human Move String: Description of a black pawn at %1$s capturing a pawn at %2$s */
376                                             N_("Black pawn at %1$s takes the white pawn at %2$s"),
377                                             /* Human Move String: Description of a black pawn at %1$s capturing a rook at %2$s */
378                                             N_("Black pawn at %1$s takes the white rook at %2$s"),
379                                             /* Human Move String: Description of a black pawn at %1$s capturing a knight at %2$s */
380                                             N_("Black pawn at %1$s takes the white knight at %2$s"),
381                                             /* Human Move String: Description of a black pawn at %1$s capturing a bishop at %2$s */
382                                             N_("Black pawn at %1$s takes the white bishop at %2$s"),
383                                             /* Human Move String: Description of a black pawn at %1$s capturing a queen at %2$s */
384                                             N_("Black pawn at %1$s takes the white queen at %2$s"),
385                                             /* Human Move String: Description of a black rook moving from %1$s to %2$s, e.g. 'a8 to a4' */
386                                             N_("Black rook moves from %1$s to %2$s"),
387                                             /* Human Move String: Description of a black rook at %1$s capturing a pawn at %2$s */
388                                             N_("Black rook at %1$s takes the white pawn at %2$s"),
389                                             /* Human Move String: Description of a black rook at %1$s capturing a rook at %2$s */
390                                             N_("Black rook at %1$s takes the white rook at %2$s"),
391                                             /* Human Move String: Description of a black rook at %1$s capturing a knight at %2$s */
392                                             N_("Black rook at %1$s takes the white knight at %2$s"),
393                                             /* Human Move String: Description of a black rook at %1$s capturing a bishop at %2$s */
394                                             N_("Black rook at %1$s takes the white bishop at %2$s"),
395                                             /* Human Move String: Description of a black rook at %1$s capturing a queen at %2$s */
396                                             N_("Black rook at %1$s takes the white queen at %2$s"),
397                                             /* Human Move String: Description of a black knight moving from %1$s to %2$s, e.g. 'b8 to c6' */
398                                             N_("Black knight moves from %1$s to %2$s"),
399                                             /* Human Move String: Description of a black knight at %1$s capturing a pawn at %2$s */
400                                             N_("Black knight at %1$s takes the white pawn at %2$s"),
401                                             /* Human Move String: Description of a black knight at %1$s capturing a rook at %2$s */
402                                             N_("Black knight at %1$s takes the white rook at %2$s"),
403                                             /* Human Move String: Description of a black knight at %1$s capturing a knight at %2$s */
404                                             N_("Black knight at %1$s takes the white knight at %2$s"),
405                                             /* Human Move String: Description of a black knight at %1$s capturing a bishop at %2$s */
406                                             N_("Black knight at %1$s takes the white bishop at %2$s"),
407                                             /* Human Move String: Description of a black knight at %1$s capturing a queen at %2$s */
408                                             N_("Black knight at %1$s takes the white queen at %2$s"),
409                                             /* Human Move String: Description of a black bishop moving from %1$s to %2$s, e.g. 'f8 to b3' */
410                                             N_("Black bishop moves from %1$s to %2$s"),
411                                             /* Human Move String: Description of a black bishop at %1$s capturing a pawn at %2$s */
412                                             N_("Black bishop at %1$s takes the white pawn at %2$s"),
413                                             /* Human Move String: Description of a black bishop at %1$s capturing a rook at %2$s */
414                                             N_("Black bishop at %1$s takes the white rook at %2$s"),
415                                             /* Human Move String: Description of a black bishop at %1$s capturing a knight at %2$s */
416                                             N_("Black bishop at %1$s takes the white knight at %2$s"),
417                                             /* Human Move String: Description of a black bishop at %1$s capturing a bishop at %2$s */
418                                             N_("Black bishop at %1$s takes the white bishop at %2$s"),
419                                             /* Human Move String: Description of a black bishop at %1$s capturing a queen at %2$s */
420                                             N_("Black bishop at %1$s takes the white queen at %2$s"),
421                                             /* Human Move String: Description of a black queen moving from %1$s to %2$s, e.g. 'd8 to d5' */
422                                             N_("Black queen moves from %1$s to %2$s"),
423                                             /* Human Move String: Description of a black queen at %1$s capturing a pawn at %2$s */
424                                             N_("Black queen at %1$s takes the white pawn at %2$s"),
425                                             /* Human Move String: Description of a black queen at %1$s capturing a rook at %2$s */
426                                             N_("Black queen at %1$s takes the white rook at %2$s"),
427                                             /* Human Move String: Description of a black queen at %1$s capturing a knight at %2$s */
428                                             N_("Black queen at %1$s takes the white knight at %2$s"),
429                                             /* Human Move String: Description of a black queen at %1$s capturing a bishop at %2$s */
430                                             N_("Black queen at %1$s takes the white bishop at %2$s"),
431                                             /* Human Move String: Description of a black queen at %1$s capturing a queen at %2$s */
432                                             N_("Black queen at %1$s takes the white queen at %2$s"),
433                                             /* Human Move String: Description of a black king moving from %1$s to %2$s, e.g. 'e8 to f8' */
434                                             N_("Black king moves from %1$s to %2$s"),
435                                             /* Human Move String: Description of a black king at %1$s capturing a pawn at %2$s */
436                                             N_("Black king at %1$s takes the white pawn at %2$s"),
437                                             /* Human Move String: Description of a black king at %1$s capturing a rook at %2$s */
438                                             N_("Black king at %1$s takes the white rook at %2$s"),
439                                             /* Human Move String: Description of a black king at %1$s capturing a knight at %2$s */
440                                             N_("Black king at %1$s takes the white knight at %2$s"),
441                                             /* Human Move String: Description of a black king at %1$s capturing a bishop at %2$s */
442                                             N_("Black king at %1$s takes the white bishop at %2$s"),
443                                             /* Human Move String: Description of a black king at %1$s capturing a queen at %2$s" */
444                                             N_("Black king at %1$s takes the white queen at %2$s")};
445
446        var move_text = "";
447        switch (scene.move_format)
448        {
449        case "human":
450            if (move.castling_rook != null)
451            {
452                if (move.f0 < move.f1 && move.r0 == 0)
453                    move_text = _("White castles kingside");
454                else if (move.f1 < move.f0 && move.r0 == 0)
455                    move_text = _("White castles queenside");
456                else if (move.f0 < move.f1 && move.r0 == 7)
457                    move_text = _("Black castles kingside");
458                else if (move.f1 < move.f0 && move.r0 == 7)
459                    move_text = _("Black castles queenside");
460                else
461                    assert_not_reached ();
462            }
463            else
464            {
465                int index;
466                if (move.victim == null)
467                    index = 0;
468                else
469                    index = move.victim.type + 1;
470                index += move.piece.type * 6;
471                if (move.piece.player.color == Color.BLACK)
472                    index += 36;
473
474                var start = "%c%d".printf ('a' + move.f0, move.r0 + 1);
475                var end = "%c%d".printf ('a' + move.f1, move.r1 + 1);
476                var template = _(human_descriptions[index]);
477                if (move.en_passant)
478                {
479                    if (move.r0 < move.r1)
480                    {   /* Human Move String: Description of a white pawn at %1$s capturing a pawn at %2$s en passant */
481                        template = _("White pawn at %1$s takes the black pawn at %2$s en passant");
482                    }
483                    else
484                    {   /* Human Move String: Description of a black pawn at %1$s capturing a pawn at %2$s en passant */
485                        template = _("Black pawn at %1$s takes white pawn at %2$s en passant");
486                    }
487                }
488                move_text = template.printf (start, end);
489            }
490            break;
491
492        case "san":
493            move_text = move.get_san ();
494            break;
495
496        case "fan":
497            move_text = move.get_fan ();
498            break;
499
500        default:
501        case "lan":
502            move_text = move.get_lan ();
503            break;
504        }
505
506        var model = (Gtk.ListStore) history_combo.model;
507        var label = "%u%c. %s".printf ((move.number + 1) / 2, move.number % 2 == 0 ? 'b' : 'a', move_text);
508        model.set (iter, 0, label, -1);
509    }
510
511    public void start_game ()
512    {
513        var model = (Gtk.ListStore) history_combo.model;
514        model.clear ();
515        Gtk.TreeIter iter;
516        model.append (out iter);
517        model.set (iter, 0,
518                   /* Move History Combo: Go to the start of the game */
519                   _("Game Start"), 1, 0, -1);
520        history_combo.set_active_iter (iter);
521
522        white_time_label.queue_draw ();
523        black_time_label.queue_draw ();
524
525        if (game.clock != null)
526        {
527            game.clock.tick.connect (() => {
528                white_time_label.queue_draw ();
529                black_time_label.queue_draw ();
530            });
531        }
532    }
533
534    public void move (ChessMove m)
535    {
536        /* Automatically return view to the present */
537        scene.move_number = -1;
538
539        var model = (Gtk.ListStore) history_combo.model;
540        Gtk.TreeIter iter;
541        model.append (out iter);
542        model.set (iter, 1, m.number, -1);
543        set_move_text (iter, m);
544
545        /* Follow the latest move */
546        if (m.number == game.n_moves)
547            history_combo.set_active_iter (iter);
548    }
549
550    public void undo ()
551    {
552        /* Remove from the history */
553        var model = (Gtk.ListStore) history_combo.model;
554        Gtk.TreeIter iter;
555        model.iter_nth_child (out iter, null, model.iter_n_children (null) - 1);
556        model.remove (ref iter);
557
558        /* Always undo from the most recent move */
559        scene.move_number = -1;
560
561        /* Go back one */
562        model.iter_nth_child (out iter, null, model.iter_n_children (null) - 1);
563        history_combo.set_active_iter (iter);
564        view.queue_draw ();
565    }
566
567    public void end_game ()
568    {
569        white_time_label.queue_draw ();
570        black_time_label.queue_draw ();
571    }
572}
573