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