1/* 2 * Copyright (C) 2010-2013 Robert Ancell 3 * 4 * This program is free software: you can redistribute it and/or modify it under 5 * the terms of the GNU General Public License as published by the Free Software 6 * Foundation, either version 2 of the License, or (at your option) any later 7 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the 8 * license. 9 */ 10 11public class Quadrapassel : Gtk.Application 12{ 13 /* Application settings */ 14 private Settings settings; 15 16 /* Main window */ 17 private Gtk.Window window; 18 private Gtk.MenuButton menu_button; 19 private int window_width; 20 private int window_height; 21 private bool is_maximized; 22 private bool is_tiled; 23 24 /* Game being played */ 25 private Game? game = null; 26 27 /* Rendering of game */ 28 private GameView view; 29 30 /* Preview of the next shape */ 31 private Preview preview; 32 33 /* Label showing current score */ 34 private Gtk.Label score_label; 35 36 /* Label showing the number of lines destroyed */ 37 private Gtk.Label n_destroyed_label; 38 39 /* Label showing the current level */ 40 private Gtk.Label level_label; 41 42 private History history; 43 44 private SimpleAction pause_action; 45 46 private Gtk.Button pause_play_button; 47 private Gtk.Image pause_play_button_image; 48 49 private Gtk.Dialog preferences_dialog; 50 private Gtk.SpinButton starting_level_spin; 51 private Preview theme_preview; 52 private Gtk.SpinButton fill_height_spinner; 53 private Gtk.SpinButton fill_prob_spinner; 54 private Gtk.CheckButton do_preview_toggle; 55 private Gtk.CheckButton difficult_blocks_toggle; 56 private Gtk.CheckButton rotate_counter_clock_wise_toggle; 57 private Gtk.CheckButton show_shadow_toggle; 58 private Gtk.CheckButton sound_toggle; 59 private Gtk.ListStore controls_model; 60 61 private const GLib.ActionEntry[] action_entries = 62 { 63 { "new-game", new_game_cb }, 64 { "pause", pause_cb }, 65 { "scores", scores_cb }, 66 { "menu", menu_cb }, 67 { "preferences", preferences_cb }, 68 { "help", help_cb }, 69 { "about", about_cb }, 70 { "quit", quit_cb } 71 }; 72 73 public Quadrapassel () 74 { 75 Object (application_id: "org.gnome.Quadrapassel", flags: ApplicationFlags.FLAGS_NONE); 76 } 77 78 protected override void startup () 79 { 80 base.startup (); 81 82 Gtk.Settings.get_default ().set ("gtk-application-prefer-dark-theme", true); 83 84 add_action_entries (action_entries, this); 85 set_accels_for_action ("app.new-game", {"<Primary>n"}); 86 set_accels_for_action ("app.pause", {"Pause"}); 87 set_accels_for_action ("app.menu", {"F10"}); 88 set_accels_for_action ("app.help", {"F1"}); 89 set_accels_for_action ("app.quit", {"<Primary>q"}); 90 pause_action = lookup_action ("pause") as SimpleAction; 91 92 settings = new Settings ("org.gnome.Quadrapassel"); 93 94 window = new Gtk.ApplicationWindow (this); 95 window.icon_name = "org.gnome.Quadrapassel"; 96 window.set_events (window.get_events () | Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK); 97 window.title = _("Quadrapassel"); 98 window.size_allocate.connect (size_allocate_cb); 99 window.window_state_event.connect (window_state_event_cb); 100 window.key_press_event.connect (key_press_event_cb); 101 window.key_release_event.connect (key_release_event_cb); 102 window.set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height")); 103 if (settings.get_boolean ("window-is-maximized")) 104 window.maximize (); 105 106 var headerbar = new Gtk.HeaderBar (); 107 headerbar.show_close_button = true; 108 headerbar.set_title (_("Quadrapassel")); 109 headerbar.show (); 110 window.set_titlebar (headerbar); 111 112 var menu = new Menu (); 113 var section = new Menu (); 114 menu.append_section (null, section); 115 section.append (_("_New Game"), "app.new-game"); 116 section.append (_("_Scores"), "app.scores"); 117 section = new Menu (); 118 menu.append_section (null, section); 119 section.append (_("_Preferences"), "app.preferences"); 120 section.append (_("_Help"), "app.help"); 121 section.append (_("_About Quadrapassel"), "app.about"); 122 menu_button = new Gtk.MenuButton (); 123 menu_button.set_image (new Gtk.Image.from_icon_name ("open-menu-symbolic", Gtk.IconSize.BUTTON)); 124 menu_button.show (); 125 menu_button.set_menu_model (menu); 126 127 headerbar.pack_end(menu_button); 128 129 var game_grid = new Gtk.Grid (); 130 game_grid.set_column_homogeneous (true); 131 window.add (game_grid); 132 133 view = new GameView (); 134 view.theme = settings.get_string ("theme"); 135 view.mute = !settings.get_boolean ("sound"); 136 view.show_shadow = settings.get_boolean ("show-shadow"); 137 view.game = new Game (20, 10, 1, 20, 10); // Game board size, changed width to 10 138 view.show (); 139 var game_aspect = new Gtk.AspectFrame (null, 0.5f, 0.5f, 10.0f/20.0f, false); // change to 10 from 14 140 game_aspect.show (); 141 game_aspect.add (view); 142 game_aspect.border_width = 1; 143 game_grid.attach (game_aspect, 0, 1, 2, 17); 144 145 pause_play_button = new Gtk.Button (); 146 pause_play_button_image = new Gtk.Image.from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.DIALOG); 147 pause_play_button.add (pause_play_button_image); 148 pause_play_button.action_name = "app.new-game"; 149 pause_play_button.tooltip_text = _("Start a new game"); 150 pause_play_button.margin = 30; 151 pause_play_button_image.show (); 152 pause_play_button.show (); 153 154 var preview_frame = new Gtk.AspectFrame (_("Next"), 0.5f, 0.5f, 1.0f, false); 155 preview_frame.set_label_align (0.5f, 1.0f); 156 preview = new Preview (preview_frame); 157 preview.theme = settings.get_string ("theme"); 158 preview.enabled = settings.get_boolean ("do-preview"); 159 preview_frame.add (preview); 160 preview_frame.show (); 161 preview.show (); 162 163 game_grid.attach (preview_frame, 2, 1, 1, 3); 164 game_grid.show (); 165 166 var label = new Gtk.Label (null); 167 label.set_markup ("<span color='gray'>%s</span>".printf (_("Score"))); 168 label.set_alignment (0.5f, 0.5f); 169 label.show (); 170 game_grid.attach (label, 2, 5, 1, 1); 171 score_label = new Gtk.Label ("<big>-</big>"); 172 score_label.set_use_markup (true); 173 score_label.set_alignment (0.5f, 0.0f); 174 score_label.show (); 175 game_grid.attach (score_label, 2, 6, 1, 2); 176 177 label = new Gtk.Label (null); 178 label.set_markup ("<span color='gray'>%s</span>".printf (_("Lines"))); 179 label.set_alignment (0.5f, 0.5f); 180 label.show (); 181 game_grid.attach (label, 2, 9, 1, 1); 182 n_destroyed_label = new Gtk.Label ("<big>-</big>"); 183 n_destroyed_label.set_use_markup (true); 184 n_destroyed_label.set_alignment (0.5f, 0.0f); 185 n_destroyed_label.show (); 186 game_grid.attach (n_destroyed_label, 2, 10, 1, 2); 187 188 label = new Gtk.Label (null); 189 label.set_markup ("<span color='gray'>%s</span>".printf (_("Level"))); 190 label.set_alignment (0.5f, 0.5f); 191 label.show (); 192 game_grid.attach (label, 2, 13, 1, 1); 193 level_label = new Gtk.Label ("<big>-</big>"); 194 level_label.set_use_markup (true); 195 level_label.set_alignment (0.5f, 0.0f); 196 level_label.show (); 197 game_grid.attach (level_label, 2, 14, 1, 2); 198 199 game_grid.attach (pause_play_button, 2, 16, 1, 2); 200 201 history = new History (Path.build_filename (Environment.get_user_data_dir (), "quadrapassel", "history")); 202 history.load (); 203 204 pause_action.set_enabled (false); 205 } 206 207 private void size_allocate_cb (Gtk.Allocation allocation) 208 { 209 if (is_maximized || is_tiled) 210 return; 211 window.get_size (out window_width, out window_height); 212 } 213 214 private bool window_state_event_cb (Gdk.EventWindowState event) 215 { 216 if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0) 217 is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0; 218 /* We don’t save this state, but track it for saving size allocation */ 219 if ((event.changed_mask & Gdk.WindowState.TILED) != 0) 220 is_tiled = (event.new_window_state & Gdk.WindowState.TILED) != 0; 221 return false; 222 } 223 224 protected override void shutdown () 225 { 226 base.shutdown (); 227 228 /* Save window state */ 229 settings.set_int ("window-width", window_width); 230 settings.set_int ("window-height", window_height); 231 settings.set_boolean ("window-is-maximized", is_maximized); 232 233 /* Record the score if the game isn't over. */ 234 if (game != null && !game.game_over && game.score > 0) 235 { 236 var date = new DateTime.now_local (); 237 var entry = new HistoryEntry (date, game.score); 238 history.add (entry); 239 history.save (); 240 } 241 } 242 243 protected override void activate () 244 { 245 window.present (); 246 } 247 248 private void preferences_dialog_close_cb () 249 { 250 preferences_dialog.destroy (); 251 preferences_dialog = null; 252 } 253 254 private void preferences_dialog_response_cb (int response_id) 255 { 256 preferences_dialog_close_cb (); 257 } 258 259 private void preferences_cb () 260 { 261 if (preferences_dialog != null) 262 { 263 preferences_dialog.present (); 264 return; 265 } 266 267 preferences_dialog = new Gtk.Dialog.with_buttons (_("Preferences"), 268 window, 269 Gtk.DialogFlags.USE_HEADER_BAR, 270 null); 271 preferences_dialog.set_border_width (5); 272 var vbox = (Gtk.Box) preferences_dialog.get_content_area (); 273 vbox.set_spacing (2); 274 preferences_dialog.close.connect (preferences_dialog_close_cb); 275 preferences_dialog.response.connect (preferences_dialog_response_cb); 276 277 var notebook = new Gtk.Notebook (); 278 notebook.set_border_width (5); 279 vbox.pack_start (notebook, true, true, 0); 280 281 var grid = new Gtk.Grid (); 282 grid.set_row_spacing (6); 283 grid.set_column_spacing (12); 284 grid.border_width = 12; 285 var label = new Gtk.Label (_("Game")); 286 notebook.append_page (grid, label); 287 288 /* pre-filled rows */ 289 label = new Gtk.Label.with_mnemonic (_("_Number of pre-filled rows:")); 290 label.set_alignment (0, 0.5f); 291 label.set_hexpand (true); 292 grid.attach (label, 0, 0, 1, 1); 293 294 var adj = new Gtk.Adjustment (settings.get_int ("line-fill-height"), 0, 15, 1, 5, 0); 295 // the maximum should be at least 4 less than the new game height but as long as the game height is a magic 20 and not a setting, we can keep it at 15 296 fill_height_spinner = new Gtk.SpinButton (adj, 10, 0); 297 fill_height_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS); 298 fill_height_spinner.set_snap_to_ticks (true); 299 fill_height_spinner.value_changed.connect (fill_height_spinner_value_changed_cb); 300 grid.attach (fill_height_spinner, 1, 0, 1, 1); 301 label.set_mnemonic_widget (fill_height_spinner); 302 303 /* pre-filled rows density */ 304 label = new Gtk.Label.with_mnemonic (_("_Density of blocks in a pre-filled row:")); 305 label.set_alignment (0, 0.5f); 306 label.set_hexpand (true); 307 grid.attach (label, 0, 1, 1, 1); 308 309 adj = new Gtk.Adjustment (settings.get_int ("line-fill-probability"), 0, 10, 1, 5, 0); 310 fill_prob_spinner = new Gtk.SpinButton (adj, 10, 0); 311 fill_prob_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS); 312 fill_prob_spinner.set_snap_to_ticks (true); 313 fill_prob_spinner.value_changed.connect (fill_prob_spinner_value_changed_cb); 314 grid.attach (fill_prob_spinner, 1, 1, 1, 1); 315 label.set_mnemonic_widget (fill_prob_spinner); 316 317 /* starting level */ 318 label = new Gtk.Label.with_mnemonic (_("_Starting level:")); 319 label.set_alignment (0, 0.5f); 320 label.set_hexpand (true); 321 grid.attach (label, 0, 2, 1, 1); 322 323 adj = new Gtk.Adjustment (settings.get_int ("starting-level"), 1, 20, 1, 5, 0); 324 starting_level_spin = new Gtk.SpinButton (adj, 10.0, 0); 325 starting_level_spin.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS); 326 starting_level_spin.set_snap_to_ticks (true); 327 starting_level_spin.value_changed.connect (starting_level_value_changed_cb); 328 grid.attach (starting_level_spin, 1, 2, 1, 1); 329 label.set_mnemonic_widget (starting_level_spin); 330 331 sound_toggle = new Gtk.CheckButton.with_mnemonic (_("_Enable sounds")); 332 sound_toggle.set_active (settings.get_boolean ("sound")); 333 sound_toggle.toggled.connect (sound_toggle_toggled_cb); 334 grid.attach (sound_toggle, 0, 3, 2, 1); 335 336 difficult_blocks_toggle = new Gtk.CheckButton.with_mnemonic (_("Choose difficult _blocks")); 337 difficult_blocks_toggle.set_active (settings.get_boolean ("pick-difficult-blocks")); 338 difficult_blocks_toggle.toggled.connect (difficult_blocks_toggled_cb); 339 grid.attach (difficult_blocks_toggle, 0, 4, 2, 1); 340 341 do_preview_toggle = new Gtk.CheckButton.with_mnemonic (_("_Preview next block")); 342 do_preview_toggle.set_active (settings.get_boolean ("do-preview")); 343 do_preview_toggle.toggled.connect (do_preview_toggle_toggled_cb); 344 grid.attach (do_preview_toggle, 0, 5, 2, 1); 345 346 /* rotate counter clock wise */ 347 rotate_counter_clock_wise_toggle = new Gtk.CheckButton.with_mnemonic (_("_Rotate blocks counterclockwise")); 348 rotate_counter_clock_wise_toggle.set_active (settings.get_boolean ("rotate-counter-clock-wise")); 349 rotate_counter_clock_wise_toggle.toggled.connect (set_rotate_counter_clock_wise); 350 grid.attach (rotate_counter_clock_wise_toggle, 0, 6, 2, 1); 351 352 show_shadow_toggle = new Gtk.CheckButton.with_mnemonic (_("Show _where the block will land")); 353 show_shadow_toggle.set_active (settings.get_boolean ("show-shadow")); 354 show_shadow_toggle.toggled.connect (user_target_toggled_cb); 355 grid.attach (show_shadow_toggle, 0, 7, 2, 1); 356 357 /* controls page */ 358 controls_model = new Gtk.ListStore (4, typeof (string), typeof (string), typeof (uint), typeof (uint)); 359 Gtk.TreeIter iter; 360 controls_model.append (out iter); 361 var keyval = settings.get_int ("key-left"); 362 controls_model.set (iter, 0, "key-left", 1, _("Move left"), 2, keyval); 363 controls_model.append (out iter); 364 keyval = settings.get_int ("key-right"); 365 controls_model.set (iter, 0, "key-right", 1, _("Move right"), 2, keyval); 366 controls_model.append (out iter); 367 keyval = settings.get_int ("key-down"); 368 controls_model.set (iter, 0, "key-down", 1, _("Move down"), 2, keyval); 369 controls_model.append (out iter); 370 keyval = settings.get_int ("key-drop"); 371 controls_model.set (iter, 0, "key-drop", 1, _("Drop"), 2, keyval); 372 controls_model.append (out iter); 373 keyval = settings.get_int ("key-rotate"); 374 controls_model.set (iter, 0, "key-rotate", 1, _("Rotate"), 2, keyval); 375 controls_model.append (out iter); 376 keyval = settings.get_int ("key-pause"); 377 controls_model.set (iter, 0, "key-pause", 1, _("Pause"), 2, keyval); 378 var controls_view = new Gtk.TreeView.with_model (controls_model); 379 controls_view.headers_visible = false; 380 controls_view.enable_search = false; 381 var label_renderer = new Gtk.CellRendererText (); 382 controls_view.insert_column_with_attributes (-1, "Control", label_renderer, "text", 1); 383 var key_renderer = new Gtk.CellRendererAccel (); 384 key_renderer.editable = true; 385 key_renderer.accel_mode = Gtk.CellRendererAccelMode.OTHER; 386 key_renderer.accel_edited.connect (accel_edited_cb); 387 key_renderer.accel_cleared.connect (accel_cleared_cb); 388 controls_view.insert_column_with_attributes (-1, "Key", key_renderer, "accel-key", 2, "accel-mods", 3); 389 390 var controls_list = new Gtk.ScrolledWindow (null, null); 391 controls_list.border_width = 12; 392 controls_list.hscrollbar_policy = Gtk.PolicyType.NEVER; 393 controls_list.vscrollbar_policy = Gtk.PolicyType.ALWAYS; 394 controls_list.shadow_type = Gtk.ShadowType.IN; 395 controls_list.add (controls_view); 396 label = new Gtk.Label (_("Controls")); 397 notebook.append_page (controls_list, label); 398 399 /* theme page */ 400 vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 401 vbox.set_border_width (12); 402 label = new Gtk.Label (_("Theme")); 403 notebook.append_page (vbox, label); 404 405 var theme_combo = new Gtk.ComboBox (); 406 vbox.pack_start (theme_combo, false, true, 0); 407 var theme_store = new Gtk.ListStore (2, typeof (string), typeof (string)); 408 theme_combo.model = theme_store; 409 var renderer = new Gtk.CellRendererText (); 410 theme_combo.pack_start (renderer, true); 411 theme_combo.add_attribute (renderer, "text", 0); 412 413 theme_store.append (out iter); 414 theme_store.set (iter, 0, _("Plain"), 1, "plain", -1); 415 if (settings.get_string ("theme") == "plain") 416 theme_combo.set_active_iter (iter); 417 418 theme_store.append (out iter); 419 theme_store.set (iter, 0, _("Tango Flat"), 1, "tangoflat", -1); 420 if (settings.get_string ("theme") == "tangoflat") 421 theme_combo.set_active_iter (iter); 422 423 theme_store.append (out iter); 424 theme_store.set (iter, 0, _("Tango Shaded"), 1, "tangoshaded", -1); 425 if (settings.get_string ("theme") == "tangoshaded") 426 theme_combo.set_active_iter (iter); 427 428 theme_store.append (out iter); 429 theme_store.set (iter, 0, _("Clean"), 1, "clean", -1); 430 if (settings.get_string ("theme") == "clean") 431 theme_combo.set_active_iter (iter); 432 433 theme_combo.changed.connect (theme_combo_changed_cb); 434 435 theme_preview = new Preview (null); 436 theme_preview.game = new Game (); 437 theme_preview.theme = settings.get_string ("theme"); 438 vbox.pack_start (theme_preview, true, true, 0); 439 440 preferences_dialog.show_all (); 441 } 442 443 private void sound_toggle_toggled_cb () 444 { 445 var play_sound = sound_toggle.get_active (); 446 settings.set_boolean ("sound", play_sound); 447 view.mute = !play_sound; 448 } 449 450 private void do_preview_toggle_toggled_cb () 451 { 452 var preview_enabled = do_preview_toggle.get_active (); 453 settings.set_boolean ("do-preview", preview_enabled); 454 preview.enabled = preview_enabled; 455 } 456 457 private void difficult_blocks_toggled_cb () 458 { 459 settings.set_boolean ("pick-difficult-blocks", difficult_blocks_toggle.get_active ()); 460 } 461 462 private void set_rotate_counter_clock_wise () 463 { 464 settings.set_boolean ("rotate-counter-clock-wise", rotate_counter_clock_wise_toggle.get_active ()); 465 } 466 467 private void user_target_toggled_cb () 468 { 469 var show_shadow = show_shadow_toggle.get_active (); 470 settings.set_boolean ("show-shadow", show_shadow); 471 view.show_shadow = show_shadow; 472 } 473 474 private void accel_edited_cb (Gtk.CellRendererAccel cell, string path_string, uint keyval, Gdk.ModifierType mask, uint hardware_keycode) 475 { 476 var path = new Gtk.TreePath.from_string (path_string); 477 if (path == null) 478 return; 479 480 Gtk.TreeIter iter; 481 if (!controls_model.get_iter (out iter, path)) 482 return; 483 484 485 if (keyval == settings.get_int ("key-left")|| 486 keyval == settings.get_int ("key-right") || 487 keyval == settings.get_int ("key-down") || 488 keyval == settings.get_int ("key-drop") || 489 keyval == settings.get_int ("key-rotate") || 490 keyval == settings.get_int ("key-pause")) 491 { 492 // Throw up a dialog 493 var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, _("Unable to change key, as this key already exists")); 494 dialog.set_title (Environment.get_application_name ()); 495 dialog.run (); 496 dialog.destroy (); 497 return; 498 } 499 else 500 { 501 string? key = null; 502 controls_model.get (iter, 0, out key); 503 if (key == null) 504 return; 505 506 controls_model.set (iter, 2, keyval); 507 settings.set_int (key, (int) keyval); 508 } 509 } 510 511 private void accel_cleared_cb (Gtk.CellRendererAccel cell, string path_string) 512 { 513 var path = new Gtk.TreePath.from_string (path_string); 514 if (path == null) 515 return; 516 517 Gtk.TreeIter iter; 518 if (!controls_model.get_iter (out iter, path)) 519 return; 520 521 string? key = null; 522 controls_model.get (iter, 0, out key); 523 if (key == null) 524 return; 525 526 controls_model.set (iter, 2, 0); 527 settings.set_int (key, 0); 528 } 529 530 private void theme_combo_changed_cb (Gtk.ComboBox widget) 531 { 532 Gtk.TreeIter iter; 533 widget.get_active_iter (out iter); 534 string theme; 535 widget.model.get (iter, 1, out theme); 536 view.theme = theme; 537 preview.theme = theme; 538 if (theme_preview != null) 539 theme_preview.theme = theme; 540 settings.set_string ("theme", theme); 541 } 542 543 private void fill_height_spinner_value_changed_cb (Gtk.SpinButton spin) 544 { 545 int value = spin.get_value_as_int (); 546 settings.set_int ("line-fill-height", value); 547 } 548 549 private void fill_prob_spinner_value_changed_cb (Gtk.SpinButton spin) 550 { 551 int value = spin.get_value_as_int (); 552 settings.set_int ("line-fill-probability", value); 553 } 554 555 private void starting_level_value_changed_cb (Gtk.SpinButton spin) 556 { 557 int value = spin.get_value_as_int (); 558 settings.set_int ("starting-level", value); 559 } 560 561 private void pause_cb () 562 { 563 if (game != null) 564 game.paused = !game.paused; 565 } 566 567 private void quit_cb () 568 { 569 window.destroy (); 570 } 571 572 private bool key_press_event_cb (Gtk.Widget widget, Gdk.EventKey event) 573 { 574 var keyval = upper_key (event.keyval); 575 576 if (game.game_over && keyval == upper_key (settings.get_int ("key-start"))) 577 { 578 new_game(); 579 } 580 581 if (game == null) { 582 // Pressing pause with no game will start a new game. 583 if (keyval == upper_key (settings.get_int ("key-pause"))) 584 { 585 new_game (); 586 return true; 587 } 588 589 return false; 590 } 591 592 if (keyval == upper_key (settings.get_int ("key-pause"))) 593 { 594 if (!game.game_over) 595 game.paused = !game.paused; 596 return true; 597 } 598 599 if (game.paused) 600 return false; 601 602 if (keyval == upper_key (settings.get_int ("key-left"))) 603 { 604 game.move_left (); 605 return true; 606 } 607 else if (keyval == upper_key (settings.get_int ("key-right"))) 608 { 609 game.move_right (); 610 return true; 611 } 612 else if (keyval == upper_key (settings.get_int ("key-rotate"))) 613 { 614 if (settings.get_boolean ("rotate-counter-clock-wise")) 615 game.rotate_left (); 616 else 617 game.rotate_right (); 618 return true; 619 } 620 else if (keyval == upper_key (settings.get_int ("key-down"))) 621 { 622 game.set_fast_forward (true); 623 return true; 624 } 625 else if (keyval == upper_key (settings.get_int ("key-drop"))) 626 { 627 game.drop (); 628 return true; 629 } 630 631 return false; 632 } 633 634 private bool key_release_event_cb (Gtk.Widget widget, Gdk.EventKey event) 635 { 636 var keyval = upper_key (event.keyval); 637 638 if (game == null) 639 return false; 640 641 if (keyval == upper_key (settings.get_int ("key-left")) || 642 keyval == upper_key (settings.get_int ("key-right"))) 643 { 644 game.stop_moving (); 645 return true; 646 } 647 else if (keyval == upper_key (settings.get_int ("key-down"))) 648 { 649 game.set_fast_forward (false); 650 return true; 651 } 652 653 return false; 654 } 655 656 private uint upper_key (uint keyval) 657 { 658 if (keyval > 255) 659 return keyval; 660 return ((char) keyval).toupper (); 661 } 662 663 private void new_game_cb () 664 { 665 new_game (); 666 } 667 668 private void new_game () 669 { 670 if (game != null) 671 { 672 game.stop (); 673 SignalHandler.disconnect_matched (game, SignalMatchType.DATA, 0, 0, null, null, this); 674 } 675 676 // Set game dimension, change to 10 677 game = new Game (20, 10, settings.get_int ("starting-level"), settings.get_int ("line-fill-height"), settings.get_int ("line-fill-probability"), settings.get_boolean ("pick-difficult-blocks")); 678 game.pause_changed.connect (pause_changed_cb); 679 game.shape_landed.connect (shape_landed_cb); 680 game.complete.connect (complete_cb); 681 preview.game = game; 682 view.game = game; 683 684 game.start (); 685 686 update_score (); 687 pause_action.set_enabled (true); 688 pause_play_button.action_name = "app.pause"; 689 } 690 691 private void pause_changed_cb () 692 { 693 if (game.paused) 694 { 695 pause_play_button_image.set_from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.DIALOG); 696 pause_play_button.tooltip_text = _("Unpause the game"); 697 } 698 else 699 { 700 pause_play_button_image.set_from_icon_name ("media-playback-pause-symbolic", Gtk.IconSize.DIALOG); 701 pause_play_button.tooltip_text = _("Pause the game"); 702 } 703 } 704 705 private void shape_landed_cb (int[] lines, List<Block> line_blocks) 706 { 707 update_score (); 708 } 709 710 private void complete_cb () 711 { 712 pause_action.set_enabled (false); 713 pause_play_button_image.set_from_icon_name ("view-refresh-symbolic" , Gtk.IconSize.DIALOG); 714 pause_play_button.action_name = "app.new-game"; 715 pause_play_button.tooltip_text = _("Start a new game"); 716 717 if (game.score > 0) 718 { 719 var date = new DateTime.now_local (); 720 var entry = new HistoryEntry (date, game.score); 721 history.add (entry); 722 history.save (); 723 724 if (show_scores (entry, true) == Gtk.ResponseType.OK) 725 new_game (); 726 } 727 } 728 729 private int show_scores (HistoryEntry? selected_entry = null, bool show_close = false) 730 { 731 var dialog = new ScoreDialog (history, selected_entry, show_close); 732 dialog.modal = true; 733 dialog.transient_for = window; 734 735 var result = dialog.run (); 736 dialog.destroy (); 737 738 return result; 739 } 740 741 private void update_score () 742 { 743 var score = 0; 744 var level = 0; 745 var n_lines_destroyed = 0; 746 747 if (game != null) 748 { 749 score = game.score; 750 level = game.level; 751 n_lines_destroyed = game.n_lines_destroyed; 752 } 753 754 score_label.set_markup ("<big>%d</big>".printf (score)); 755 level_label.set_markup ("<big>%d</big>".printf (level)); 756 n_destroyed_label.set_markup ("<big>%d</big>".printf (n_lines_destroyed)); 757 } 758 759 private void help_cb () 760 { 761 try 762 { 763 Gtk.show_uri (window.get_screen (), "help:quadrapassel", Gtk.get_current_event_time ()); 764 } 765 catch (Error e) 766 { 767 warning ("Failed to show help: %s", e.message); 768 } 769 } 770 771 private void about_cb () 772 { 773 string[] authors = { "GNOME Games Team", "Maintainer: John Ward<john@johnward.net>", null }; 774 string[] documenters = { "Angela Boyle", null }; 775 776 Gtk.show_about_dialog (window, 777 "program-name", _("Quadrapassel"), 778 "version", VERSION, 779 "comments", _("A classic game where you rotate blocks to make complete rows, but don't pile your blocks too high or it's game over!"), 780 "copyright", "Copyright © 1999 J. Marcin Gorycki, 2000–2015 Others", 781 "license-type", Gtk.License.GPL_2_0, 782 "authors", authors, 783 "documenters", documenters, 784 "translator-credits", _("translator-credits"), 785 "logo-icon-name", "org.gnome.Quadrapassel", 786 "website", "https://wiki.gnome.org/Apps/Quadrapassel", 787 null); 788 } 789 790 private void menu_cb () 791 { 792 menu_button.activate (); 793 } 794 795 private void scores_cb () 796 { 797 show_scores (); 798 } 799 800 public static int main (string[] args) 801 { 802 Intl.setlocale (LocaleCategory.ALL, ""); 803 Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); 804 Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); 805 Intl.textdomain (GETTEXT_PACKAGE); 806 807 var context = new OptionContext (""); 808 809 context.add_group (Gtk.get_option_group (true)); 810 context.add_group (Clutter.get_option_group_without_init ()); 811 812 try 813 { 814 context.parse (ref args); 815 } 816 catch (Error e) 817 { 818 stderr.printf ("%s\n", e.message); 819 return Posix.EXIT_FAILURE; 820 } 821 822 Environment.set_application_name (_("Quadrapassel")); 823 824 Gtk.Window.set_default_icon_name ("quadrapassel"); 825 826 try 827 { 828 GtkClutter.init_with_args (ref args, "", new OptionEntry[0], null); 829 } 830 catch (Error e) 831 { 832 var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE, "Unable to initialize Clutter:\n%s", e.message); 833 dialog.set_title (Environment.get_application_name ()); 834 dialog.run (); 835 dialog.destroy (); 836 return Posix.EXIT_FAILURE; 837 } 838 839 var app = new Quadrapassel (); 840 return app.run (args); 841 } 842} 843