1
public class Mahjongg : Gtk.Application
3
private Settings settings;
5
private History history;
7
private List<Map> maps = null;
9
private Gtk.ApplicationWindow window;
10
private int window_width;
11
private int window_height;
12
private bool is_fullscreen;
13
private bool is_maximized;
15
private GameView game_view;
16
private Gtk.ToolButton pause_button;
17
private Gtk.ToolItem status_item;
18
private Gtk.Label moves_label;
19
private Gtk.Label clock_label;
20
private Gtk.Dialog? preferences_dialog = null;
24
Object (application_id: "org.gnome.gnome-mahjongg", flags: ApplicationFlags.FLAGS_NONE);
26
add_action_entries (action_entries, this);
27
add_accelerator ("<Primary>n", "app.new-game", null);
28
add_accelerator ("Pause", "app.pause", null);
29
add_accelerator ("<Primary>h", "app.hint", null);
30
add_accelerator ("<Primary>z", "app.undo", null);
31
add_accelerator ("<Primary><Shift>z", "app.redo", null);
32
add_accelerator ("F1", "app.help", null);
33
add_accelerator ("<Primary>q", "app.quit", null);
36
protected override void startup ()
40
settings = new Settings ("org.gnome.gnome-mahjongg");
44
history = new History (Path.build_filename (Environment.get_user_data_dir (), "gnome-mahjongg", "history"));
47
window = new Gtk.ApplicationWindow (this);
48
window.title = _("Mahjongg");
49
window.configure_event.connect (window_configure_event_cb);
50
window.window_state_event.connect (window_state_event_cb);
51
window.set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));
52
if (settings.get_boolean ("window-is-fullscreen"))
54
else if (settings.get_boolean ("window-is-maximized"))
57
var status_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10);
59
var group_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
60
var label = new Gtk.Label (_("Moves Left:"));
61
group_box.pack_start (label, false, false, 0);
62
var spacer = new Gtk.Label (" ");
63
group_box.pack_start (spacer, false, false, 0);
64
moves_label = new Gtk.Label ("");
65
group_box.pack_start (moves_label, false, false, 0);
66
status_box.pack_start (group_box, false, false, 0);
68
clock_label = new Gtk.Label ("");
69
status_box.pack_start (clock_label, false, false, 0);
71
var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
73
/* Create the menus */
74
var menu = new Menu ();
75
var section = new Menu ();
76
menu.append_section (null, section);
77
section.append (_("_New Game"), "app.new-game");
78
section.append (_("_Restart Game"), "app.restart-game");
79
section.append (_("_Scores"), "app.scores");
80
section.append (_("_Preferences"), "app.preferences");
81
section = new Menu ();
82
menu.append_section (null, section);
83
section.append (_("_Help"), "app.help");
84
section.append (_("_About"), "app.about");
85
section = new Menu ();
86
menu.append_section (null, section);
87
section.append (_("_Quit"), "app.quit");
90
game_view = new GameView ();
91
game_view.button_press_event.connect (view_button_press_event);
92
game_view.set_size_request (600, 400);
94
var toolbar = new Gtk.Toolbar ();
95
toolbar.show_arrow = false;
96
toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
98
var new_game_button = new Gtk.ToolButton (null, _("_New"));
99
new_game_button.use_underline = true;
100
new_game_button.icon_name = "document-new";
101
new_game_button.action_name = "app.new-game";
102
new_game_button.show ();
103
toolbar.insert (new_game_button, -1);
105
var undo_button = new Gtk.ToolButton (null, _("_Undo Move"));
106
undo_button.use_underline = true;
107
undo_button.icon_name = "edit-undo";
108
undo_button.action_name = "app.undo";
109
undo_button.is_important = true;
111
toolbar.insert (undo_button, -1);
113
var redo_button = new Gtk.ToolButton (null, _("_Redo Move"));
114
redo_button.use_underline = true;
115
redo_button.icon_name = "edit-redo";
116
redo_button.action_name = "app.redo";
117
redo_button.is_important = true;
119
toolbar.insert (redo_button, -1);
121
var hint_button = new Gtk.ToolButton (null, _("Hint"));
122
hint_button.use_underline = true;
123
hint_button.icon_name = "dialog-information";
124
hint_button.action_name = "app.hint";
125
hint_button.is_important = true;
127
toolbar.insert (hint_button, -1);
129
pause_button = new Gtk.ToolButton (null, _("_Pause"));
130
pause_button.icon_name = "media-playback-pause";
131
pause_button.use_underline = true;
132
pause_button.action_name = "app.pause";
133
pause_button.is_important = true;
134
pause_button.show ();
135
toolbar.insert (pause_button, -1);
137
var status_alignment = new Gtk.Alignment (1.0f, 0.5f, 0.0f, 0.0f);
138
status_alignment.add (status_box);
140
status_item = new Gtk.ToolItem ();
141
status_item.set_expand (true);
142
status_item.add (status_alignment);
144
toolbar.insert (status_item, -1);
146
vbox.pack_start (toolbar, false, false, 0);
147
vbox.pack_start (game_view, true, true, 0);
152
settings.changed.connect (conf_value_changed_cb);
156
game_view.grab_focus ();
158
conf_value_changed_cb (settings, "tileset");
159
conf_value_changed_cb (settings, "bgcolour");
163
private bool window_configure_event_cb (Gdk.EventConfigure event)
165
if (!is_maximized && !is_fullscreen)
167
window_width = event.width;
168
window_height = event.height;
174
private bool window_state_event_cb (Gdk.EventWindowState event)
176
if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
177
is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
178
if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
179
is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
183
protected override void shutdown ()
187
/* Save window state */
188
settings.set_int ("window-width", window_width);
189
settings.set_int ("window-height", window_height);
190
settings.set_boolean ("window-is-maximized", is_maximized);
191
settings.set_boolean ("window-is-fullscreen", is_fullscreen);
194
public override void activate ()
199
private void update_ui ()
201
var pause_action = lookup_action ("pause") as SimpleAction;
202
var hint_action = lookup_action ("hint") as SimpleAction;
203
var undo_action = lookup_action ("undo") as SimpleAction;
204
var redo_action = lookup_action ("redo") as SimpleAction;
206
pause_action.set_enabled (game_view.game.started);
208
if (game_view.game.paused)
210
hint_action.set_enabled (false);
211
undo_action.set_enabled (false);
212
redo_action.set_enabled (false);
216
hint_action.set_enabled (game_view.game.moves_left > 0);
217
undo_action.set_enabled (game_view.game.can_undo);
218
redo_action.set_enabled (game_view.game.can_redo);
221
moves_label.set_text ("%2u".printf (game_view.game.moves_left));
224
private void theme_changed_cb (Gtk.ComboBox widget)
227
widget.get_active_iter (out iter);
229
widget.model.get (iter, 1, out theme);
230
settings.set_string ("tileset", theme);
233
private void conf_value_changed_cb (Settings settings, string key)
235
if (key == "tileset")
237
var theme = settings.get_string ("tileset");
238
game_view.theme = Path.build_filename (DATA_DIRECTORY, "themes", theme);
240
else if (key == "bgcolour")
242
game_view.set_background (settings.get_string ("bgcolour"));
244
else if (key == "mapset")
246
/* Prompt user if already made a move */
247
if (game_view.game.started)
249
var dialog = new Gtk.MessageDialog (window,
250
Gtk.DialogFlags.MODAL,
251
Gtk.MessageType.QUESTION,
252
Gtk.ButtonsType.NONE,
253
"%s", _("Do you want to start a new game with this map?"));
254
dialog.format_secondary_text (_("If you continue playing the next game will use the new map."));
255
dialog.add_buttons (_("_Continue playing"), Gtk.ResponseType.REJECT,
256
_("Use _new map"), Gtk.ResponseType.ACCEPT,
258
dialog.set_default_response (Gtk.ResponseType.ACCEPT);
259
var response = dialog.run ();
260
if (response == Gtk.ResponseType.ACCEPT)
269
private bool view_button_press_event (Gtk.Widget widget, Gdk.EventButton event)
271
/* Cancel pause on click */
272
if (game_view.game.paused)
274
game_view.game.paused = false;
281
private void background_changed_cb (Gtk.ColorButton widget)
284
/* See https://bugzilla.gnome.org/show_bug.cgi?id=669386 */
285
Gtk.color_button_get_rgba (widget, out colour);
286
settings.set_string ("bgcolour", "#%04x%04x%04x".printf ((int) (colour.red * 65536 + 0.5), (int) (colour.green * 65536 + 0.5), (int) (colour.blue * 65536 + 0.5)));
289
private void map_changed_cb (Gtk.ComboBox widget)
291
settings.set_string ("mapset", maps.nth_data (widget.active).name);
294
private void moved_cb ()
298
if (game_view.game.complete)
300
var date = new DateTime.now_local ();
301
var duration = (uint) (game_view.game.elapsed + 0.5);
302
var entry = new HistoryEntry (date, game_view.game.map.score_name, duration);
306
if (show_scores (entry, true) == Gtk.ResponseType.CLOSE)
311
else if (!game_view.game.can_move)
313
var dialog = new Gtk.MessageDialog (window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
314
Gtk.MessageType.INFO,
315
Gtk.ButtonsType.NONE,
316
"%s", _("There are no more moves."));
317
dialog.format_secondary_text (_("Each puzzle has at least one solution. You can undo your moves and try and find the solution for a time penalty, restart this game or start an new one."));
318
dialog.add_buttons (Gtk.Stock.UNDO, Gtk.ResponseType.REJECT,
319
_("_Restart"), Gtk.ResponseType.CANCEL,
320
_("_New game"), Gtk.ResponseType.ACCEPT);
322
dialog.set_default_response (Gtk.ResponseType.ACCEPT);
323
switch (dialog.run ())
325
case Gtk.ResponseType.REJECT:
328
case Gtk.ResponseType.CANCEL:
332
case Gtk.ResponseType.ACCEPT:
340
private int show_scores (HistoryEntry? selected_entry = null, bool show_quit = false)
342
var dialog = new ScoreDialog (history, selected_entry, show_quit);
344
dialog.transient_for = window;
346
var result = dialog.run ();
352
private void preferences_cb ()
354
if (preferences_dialog != null)
356
preferences_dialog.present ();
360
preferences_dialog = new Gtk.Dialog.with_buttons (_("Mahjongg Preferences"),
362
Gtk.DialogFlags.DESTROY_WITH_PARENT,
364
Gtk.ResponseType.CLOSE, null);
365
preferences_dialog.set_border_width (5);
366
var dialog_content_area = (Gtk.Box) preferences_dialog.get_content_area ();
367
dialog_content_area.set_spacing (2);
368
preferences_dialog.set_resizable (false);
369
preferences_dialog.set_default_response (Gtk.ResponseType.CLOSE);
370
preferences_dialog.response.connect (preferences_dialog_response_cb);
372
var grid = new Gtk.Grid ();
373
grid.border_width = 5;
374
grid.set_row_spacing (6);
375
grid.set_column_spacing (18);
377
var label = new Gtk.Label.with_mnemonic (_("_Theme:"));
378
label.set_alignment (0, 0.5f);
379
grid.attach (label, 0, 0, 1, 1);
381
var themes = load_themes ();
382
var theme_combo = new Gtk.ComboBox ();
383
var theme_store = new Gtk.ListStore (2, typeof (string), typeof (string));
384
theme_combo.model = theme_store;
385
var renderer = new Gtk.CellRendererText ();
386
theme_combo.pack_start (renderer, true);
387
theme_combo.add_attribute (renderer, "text", 0);
388
foreach (var theme in themes)
390
var tokens = theme.split (".", -1);
391
var name = tokens[0];
394
theme_store.append (out iter);
395
theme_store.set (iter, 0, name, 1, theme, -1);
397
if (theme == settings.get_string ("tileset"))
398
theme_combo.set_active_iter (iter);
400
theme_combo.changed.connect (theme_changed_cb);
401
theme_combo.set_hexpand (true);
402
grid.attach (theme_combo, 1, 0, 1, 1);
403
label.set_mnemonic_widget (theme_combo);
405
label = new Gtk.Label.with_mnemonic (_("_Layout:"));
406
label.set_alignment (0, 0.5f);
407
grid.attach (label, 0, 1, 1, 1);
409
var map_combo = new Gtk.ComboBox ();
410
var map_store = new Gtk.ListStore (2, typeof (string), typeof (string));
411
map_combo.model = map_store;
412
renderer = new Gtk.CellRendererText ();
413
map_combo.pack_start (renderer, true);
414
map_combo.add_attribute (renderer, "text", 0);
415
foreach (var map in maps)
417
var display_name = dpgettext2 (null, "mahjongg map name", map.name);
420
map_store.append (out iter);
421
map_store.set (iter, 0, display_name, 1, map, -1);
423
if (settings.get_string ("mapset") == map.name)
424
map_combo.set_active_iter (iter);
426
map_combo.changed.connect (map_changed_cb);
427
map_combo.set_hexpand (true);
428
grid.attach (map_combo, 1, 1, 1, 1);
429
label.set_mnemonic_widget (map_combo);
431
label = new Gtk.Label.with_mnemonic (_("_Background color:"));
432
label.set_alignment (0, 0.5f);
433
grid.attach (label, 0, 2, 1, 1);
435
var widget = new Gtk.ColorButton ();
436
widget.set_rgba (game_view.background_color);
437
widget.color_set.connect (background_changed_cb);
438
widget.set_hexpand (true);
439
grid.attach (widget, 1, 2, 1, 1);
440
label.set_mnemonic_widget (widget);
442
dialog_content_area.pack_start (grid, true, true, 0);
444
preferences_dialog.show_all ();
447
private void preferences_dialog_response_cb (Gtk.Dialog dialog, int response)
449
preferences_dialog.destroy ();
450
preferences_dialog = null;
453
private List<string> load_themes ()
455
List<string> themes = null;
460
dir = Dir.open (Path.build_filename (DATA_DIRECTORY, "themes"));
469
var s = dir.read_name ();
473
if (s.has_suffix (".xpm") || s.has_suffix (".svg") || s.has_suffix (".gif") ||
474
s.has_suffix (".png") || s.has_suffix (".jpg") || s.has_suffix (".xbm"))
481
private void hint_cb ()
483
var matches = game_view.game.find_matches (game_view.game.selected_tile);
484
var n_matches = matches.length ();
486
/* No match, just flash the selected tile */
489
if (game_view.game.selected_tile == null)
491
game_view.game.set_hint (game_view.game.selected_tile, null);
495
var n = Random.int_range (0, (int) n_matches);
496
var match = matches.nth_data (n);
497
game_view.game.set_hint (match.tile0, match.tile1);
503
private void about_cb ()
508
"Francisco Bustamante",
533
string[] documenters =
535
"Tiffany Antopolski",
540
var license = "Mahjongg is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.\n\nMahjongg is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with Mahjongg; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA";
542
Gtk.show_about_dialog (window,
543
"program-name", _("Mahjongg"),
546
_("A matching game played with Mahjongg tiles.\n\nMahjongg is a part of GNOME Games."),
547
"copyright", "Copyright \xc2\xa9 1998-2008 Free Software Foundation, Inc.",
549
"wrap-license", true,
552
"documenters", documenters,
553
"translator-credits", _("translator-credits"),
554
"logo-icon-name", "gnome-mahjongg",
555
"website", "http://www.gnome.org/projects/gnome-games",
556
"website-label", _("GNOME Games web site"),
560
private void pause_cb ()
562
game_view.game.paused = !game_view.game.paused;
563
game_view.game.set_hint (null, null);
564
game_view.game.selected_tile = null;
565
if (game_view.game.paused)
567
pause_button.icon_name = "media-playback-start";
568
pause_button.label = _("Res_ume");
572
pause_button.icon_name = "media-playback-pause";
573
pause_button.label = _("_Pause");
579
private void scores_cb ()
584
private void new_game_cb ()
589
private void restart_game_cb ()
591
game_view.game.reset ();
592
game_view.queue_draw ();
595
private void quit_cb ()
600
private void redo_cb ()
602
if (game_view.game.paused)
605
game_view.game.redo ();
609
private void undo_cb ()
611
game_view.game.undo ();
615
private void restart_game ()
617
game_view.game.reset ();
621
private void new_game ()
624
foreach (var m in maps)
626
if (m.name == settings.get_string ("mapset"))
633
map = maps.nth_data (0);
635
game_view.game = new Game (map);
636
game_view.game.moved.connect (moved_cb);
637
game_view.game.tick.connect (tick_cb);
639
/* Set window title */
640
var display_name = dpgettext2 (null, "mahjongg map name", game_view.game.map.name);
641
/* Translators: This is the window title for Mahjongg which contains the map name, e.g. 'Mahjongg - Red Dragon' */
642
window.set_title (_("Mahjongg - %s").printf (display_name));
647
private void tick_cb ()
650
if (game_view.game != null)
651
elapsed = (int) (game_view.game.elapsed + 0.5);
652
var hours = elapsed / 3600;
653
var minutes = (elapsed - hours * 3600) / 60;
654
var seconds = elapsed - hours * 3600 - minutes * 60;
655
clock_label.set_text ("%s: %02d:%02d:%02d".printf (_("Time"), hours, minutes, seconds));
658
private void help_cb ()
662
Gtk.show_uri (window.get_screen (), "help:gnome-mahjongg", Gtk.get_current_event_time ());
666
warning ("Failed to show help: %s", e.message);
670
private const GLib.ActionEntry[] action_entries =
672
{ "new-game", new_game_cb },
676
{ "pause", pause_cb },
677
{ "restart-game", restart_game_cb },
678
{ "scores", scores_cb },
679
{ "preferences", preferences_cb },
681
{ "about", about_cb },
685
private void load_maps ()
689
/* Add the builtin map */
690
maps.append (new Map.builtin ());
695
dir = Dir.open (Path.build_filename (DATA_DIRECTORY, "maps"));
703
var filename = dir.read_name ();
704
if (filename == null)
707
if (!filename.has_suffix (".map"))
710
var loader = new MapLoader ();
711
var path = Path.build_filename (DATA_DIRECTORY, "maps", filename);
718
warning ("Could not load map %s: %s\n", path, e.message);
721
foreach (var map in loader.maps)
726
public static int main (string[] args)
728
Intl.setlocale (LocaleCategory.ALL, "");
729
Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
730
Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
731
Intl.textdomain (GETTEXT_PACKAGE);
735
var context = new OptionContext ("");
736
context.set_translation_domain (GETTEXT_PACKAGE);
737
context.add_group (Gtk.get_option_group (true));
741
context.parse (ref args);
745
stdout.printf ("%s\n", e.message);
746
return Posix.EXIT_FAILURE;
749
Environment.set_application_name (_("Mahjongg"));
750
Gtk.Window.set_default_icon_name ("gnome-mahjongg");
752
var app = new Mahjongg ();
753
var result = app.run ();
761
public class ScoreDialog : Gtk.Dialog
763
private History history;
764
private HistoryEntry? selected_entry = null;
765
private Gtk.ListStore size_model;
766
private Gtk.ListStore score_model;
767
private Gtk.ComboBox size_combo;
769
public ScoreDialog (History history, HistoryEntry? selected_entry = null, bool show_quit = false)
771
this.history = history;
772
history.entry_added.connect (entry_added_cb);
773
this.selected_entry = selected_entry;
777
add_button (Gtk.Stock.QUIT, Gtk.ResponseType.CLOSE);
778
add_button (_("New Game"), Gtk.ResponseType.OK);
781
add_button (Gtk.Stock.OK, Gtk.ResponseType.DELETE_EVENT);
782
set_size_request (200, 300);
784
var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
785
vbox.border_width = 6;
787
get_content_area ().pack_start (vbox, true, true, 0);
789
var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
791
vbox.pack_start (hbox, false, false, 0);
793
var label = new Gtk.Label (_("Size:"));
795
hbox.pack_start (label, false, false, 0);
797
size_model = new Gtk.ListStore (2, typeof (string), typeof (string));
799
size_combo = new Gtk.ComboBox ();
800
size_combo.changed.connect (size_changed_cb);
801
size_combo.model = size_model;
802
var renderer = new Gtk.CellRendererText ();
803
size_combo.pack_start (renderer, true);
804
size_combo.add_attribute (renderer, "text", 0);
806
hbox.pack_start (size_combo, true, true, 0);
808
var scroll = new Gtk.ScrolledWindow (null, null);
809
scroll.shadow_type = Gtk.ShadowType.ETCHED_IN;
810
scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
812
vbox.pack_start (scroll, true, true, 0);
814
score_model = new Gtk.ListStore (3, typeof (string), typeof (string), typeof (int));
816
var scores = new Gtk.TreeView ();
817
renderer = new Gtk.CellRendererText ();
818
scores.insert_column_with_attributes (-1, _("Date"), renderer, "text", 0, "weight", 2);
819
renderer = new Gtk.CellRendererText ();
820
renderer.xalign = 1.0f;
821
scores.insert_column_with_attributes (-1, _("Time"), renderer, "text", 1, "weight", 2);
822
scores.model = score_model;
826
foreach (var entry in history.entries)
827
entry_added_cb (entry);
830
public void set_map (string name)
832
score_model.clear ();
834
var entries = history.entries.copy ();
835
entries.sort (compare_entries);
837
foreach (var entry in entries)
839
if (entry.name != name)
842
var date_label = entry.date.format ("%d/%m/%Y");
844
var time_label = "%us".printf (entry.duration);
845
if (entry.duration >= 60)
846
time_label = "%um %us".printf (entry.duration / 60, entry.duration % 60);
848
int weight = Pango.Weight.NORMAL;
849
if (entry == selected_entry)
850
weight = Pango.Weight.BOLD;
853
score_model.append (out iter);
854
score_model.set (iter, 0, date_label, 1, time_label, 2, weight);
858
private static int compare_entries (HistoryEntry a, HistoryEntry b)
860
var d = strcmp (a.name, b.name);
863
return a.date.compare (b.date);
866
private void size_changed_cb (Gtk.ComboBox combo)
869
if (!combo.get_active_iter (out iter))
873
combo.model.get (iter, 1, out name);
877
private void entry_added_cb (HistoryEntry entry)
879
/* Ignore if already have an entry for this */
881
var have_size_entry = false;
882
if (size_model.get_iter_first (out iter))
887
size_model.get (iter, 1, out name);
888
if (name == entry.name)
890
have_size_entry = true;
893
} while (size_model.iter_next (ref iter));
896
if (!have_size_entry)
898
var label = "%s".printf (entry.name);
900
size_model.append (out iter);
901
size_model.set (iter, 0, label, 1, entry.name);
903
/* Select this entry if don't have any */
904
if (size_combo.get_active () == -1)
905
size_combo.set_active_iter (iter);
907
/* Select this entry if the same category as the selected one */
908
if (selected_entry != null && entry.name == selected_entry.name)
909
size_combo.set_active_iter (iter);