1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
2 /*
3 * Copyright © 1998, 2003 Jonathan Blandford <jrb@alum.mit.edu>
4 * Copyright © 2003 Callum McKenzie <callum@physics.otago.ac.nz>
5 * Copyright © 2007, 2008, 2009, 2010 Christian Persch
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <errno.h>
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30
31 #include <gdk/gdk.h>
32 #include <gtk/gtk.h>
33
34 #include "ar-clock.h"
35 #include "ar-debug.h"
36 #include "ar-stock.h"
37 #include "ar-runtime.h"
38 #include "ar-sound.h"
39 #include "ar-string-utils.h"
40 #include "ar-gsettings.h"
41
42 #include "board-noclutter.h"
43
44 #include "ar-card-theme.h"
45 #include "ar-card-themes.h"
46 #include "conf.h"
47 #include "game.h"
48 #include "stats-dialog.h"
49 #include "util.h"
50 #include "ar-game-chooser.h"
51
52 #include "window.h"
53
54 #define AISLERIOT_WINDOW_GET_PRIVATE(window)(G_TYPE_INSTANCE_GET_PRIVATE ((window), AISLERIOT_TYPE_WINDOW, AisleriotWindowPrivate))
55
56 #define MIN_WIDTH 800
57 #define MIN_HEIGHT 600
58
59 #define MAIN_MENU_UI_PATH "/MainMenu"
60 #define RECENT_GAMES_MENU_PATH MAIN_MENU_UI_PATH "/GameMenu/RecentMenu"
61 #define OPTIONS_MENU_PATH MAIN_MENU_UI_PATH "/OptionsMenu"
62 #define CARD_THEMES_MENU_PATH MAIN_MENU_UI_PATH "/ViewMenu/ThemeMenu/ThemesPH"
63 #define TOOLBAR_UI_PATH "/Toolbar"
64
65 /* The maximum number of recent games saved */
66 #define MAX_RECENT 5
67
68 #define AR_SETTINGS_PATH_PREFIX "/org/gnome/solitaire/"
69 #define AR_SETTINGS_WINDOW_STATE_PATH AR_SETTINGS_PATH_PREFIX "window-state/"
70
71 enum
72 {
73 ACTION_UNDO_MOVE,
74 ACTION_REDO_MOVE,
75 ACTION_RESTART_GAME,
76 ACTION_FULLSCREEN,
77 ACTION_HELP_GAME,
78 ACTION_OPTIONS_MENU,
79 ACTION_DEAL,
80 ACTION_HINT,
81 ACTION_LEAVE_FULLSCREEN,
82 LAST_ACTION
83 };
84
85 struct _AisleriotWindowPrivate
86 {
87 AisleriotGame *game;
88 ArStyle *board_style;
89 AisleriotBoard *board;
90
91 ArCardThemes *theme_manager;
92 ArCardTheme *theme;
93
94 GtkStatusbar *statusbar;
95 guint game_message_id;
96 guint board_message_id;
97 GtkWidget *score_box;
98 GtkWidget *score_label;
99 GtkWidget *clock;
100
101 GtkUIManager *ui_manager;
102 GtkActionGroup *action_group;
103 GtkWidget *main_menu;
104 GtkWidget *toolbar;
105 GtkAction *action[LAST_ACTION];
106
107 GtkActionGroup *options_group;
108 guint options_merge_id;
109
110 GtkActionGroup *recent_games_group;
111 guint recent_games_merge_id;
112
113 GtkActionGroup *card_themes_group;
114 guint card_themes_merge_id;
115
116 GtkWidget *game_over_dialog;
117 GtkWidget *game_choice_dialog;
118 AisleriotStatsDialog *stats_dialog;
119
120 GtkWidget *hint_dialog;
121
122 #ifdef LEAVE_FULLSCREEN_BUTTON
123 GtkWidget *fullscreen_button;
124 #endif
125
126 guint load_idle_id;
127
128 guint changing_game_type : 1;
129 guint toolbar_visible : 1;
130 guint statusbar_visible : 1;
131 guint fullscreen : 1;
132 };
133
134 enum {
135 OPTION_CHECKMENU,
136 OPTION_RADIOMENU
137 };
138
139 /* Game Over dialogue */
140
141 enum {
142 RESPONSE_UNDO = 1,
143 RESPONSE_RESTART = 2,
144 RESPONSE_NEW_GAME = 3
145 };
146
147 static void
game_over_dialog_response_cb(GtkWidget * dialog,int response,AisleriotWindow * window)148 game_over_dialog_response_cb (GtkWidget *dialog,
149 int response,
150 AisleriotWindow *window)
151 {
152 AisleriotWindowPrivate *priv = window->priv;
153 gboolean game_won;
154
155 game_won = aisleriot_game_get_state (priv->game) == GAME_WON;
156
157 gtk_widget_destroy (dialog);
158
159 switch (response) {
160 case RESPONSE_UNDO:
161 aisleriot_game_undo_move (priv->game);
162 break;
163 case RESPONSE_RESTART:
164 aisleriot_game_restart_game (priv->game);
165 break;
166 case RESPONSE_NEW_GAME:
167 aisleriot_game_new_game (priv->game);
168 break;
169 case GTK_RESPONSE_CLOSE:
170 gtk_widget_destroy (GTK_WIDGET (window)); /* this will quit */
171 break;
172 default:
173 /* The player closed the dialog window from the window border
174 * close button.
175 * If the game is over, start a new one. Otherwise undo. This latter
176 * one isn't actually the default, but it is probably the most reasonable
177 * thing to do.
178 */
179 if (game_won) {
180 aisleriot_game_new_game (priv->game);
181 } else {
182 aisleriot_game_undo_move (priv->game);
183 }
184 }
185 }
186
187 static void
show_game_over_dialog(AisleriotWindow * window)188 show_game_over_dialog (AisleriotWindow *window)
189 {
190 AisleriotWindowPrivate *priv = window->priv;
191 GtkWidget *dialog;
192 const char *message;
193 gboolean game_won;
194
195 game_won = aisleriot_game_get_state (priv->game) == GAME_WON;
196
197 if (game_won) {
198 message = _("Congratulations, you have won!");
199 ar_sound_play ("victory");
200
201 } else {
202 message = _("There are no more moves");
203 ar_sound_play ("splat");
204 }
205
206 dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window),
207 GTK_DIALOG_DESTROY_WITH_PARENT,
208 GTK_MESSAGE_INFO,
209 GTK_BUTTONS_NONE,
210 "<b>%s</b>",
211 message);
212
213 gtk_window_set_title (GTK_WINDOW (dialog), "");
214 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
215
216 if (game_won) {
217 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
218 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
219 AR_STOCK_START_NEW_GAME, RESPONSE_NEW_GAME,
220 NULL);
221 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
222 RESPONSE_NEW_GAME,
223 GTK_RESPONSE_CLOSE,
224 -1);
225 } else {
226 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
227 AR_STOCK_UNDO_MOVE, RESPONSE_UNDO,
228 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
229 AR_STOCK_RESTART_GAME, RESPONSE_RESTART,
230 AR_STOCK_START_NEW_GAME, RESPONSE_NEW_GAME,
231 NULL);
232 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
233 RESPONSE_NEW_GAME,
234 RESPONSE_RESTART,
235 GTK_RESPONSE_CLOSE,
236 RESPONSE_UNDO,
237 -1);
238 }
239
240 gtk_dialog_set_default_response (GTK_DIALOG (dialog), RESPONSE_NEW_GAME);
241
242 priv->game_over_dialog = dialog;
243 g_signal_connect (dialog, "response",
244 G_CALLBACK (game_over_dialog_response_cb), window);
245 g_signal_connect (dialog, "destroy",
246 G_CALLBACK (gtk_widget_destroyed), &priv->game_over_dialog);
247
248 gtk_widget_show (dialog);
249 }
250
251 /* Statistics dialogue */
252
253 static void
update_statistics_display(AisleriotWindow * window)254 update_statistics_display (AisleriotWindow *window)
255 {
256 AisleriotWindowPrivate *priv = window->priv;
257 AisleriotStatistic current_stats;
258 char *game_name;
259
260 if (!priv->stats_dialog)
261 return;
262
263 game_name = aisleriot_game_get_name (priv->game);
264 aisleriot_stats_dialog_set_name (priv->stats_dialog, game_name);
265 g_free (game_name);
266
267 aisleriot_conf_get_statistic (aisleriot_game_get_game_module (priv->game),
268 ¤t_stats);
269
270 aisleriot_stats_dialog_update (priv->stats_dialog, ¤t_stats);
271 }
272
273 static void
stats_dialog_response_cb(GtkWidget * widget,int response,AisleriotWindow * window)274 stats_dialog_response_cb (GtkWidget *widget,
275 int response,
276 AisleriotWindow *window)
277 {
278 AisleriotWindowPrivate *priv = window->priv;
279
280 if (response == GTK_RESPONSE_REJECT) {
281 AisleriotStatistic current_stats = { 0, 0, 0, 0 };
282
283 aisleriot_conf_set_statistic (aisleriot_game_get_game_module (priv->game),
284 ¤t_stats);
285 aisleriot_stats_dialog_update (priv->stats_dialog, ¤t_stats);
286
287 return;
288 }
289
290 gtk_widget_destroy (widget);
291 }
292
293 /* action methods */
294
295 void
aisleriot_window_new_game(AisleriotWindow * window)296 aisleriot_window_new_game (AisleriotWindow *window)
297 {
298 AisleriotWindowPrivate *priv = window->priv;
299
300 aisleriot_game_new_game (priv->game);
301
302 gtk_widget_grab_focus (GTK_WIDGET (priv->board));
303 }
304
305 void
aisleriot_window_change_game(AisleriotWindow * window)306 aisleriot_window_change_game (AisleriotWindow *window)
307 {
308 AisleriotWindowPrivate *priv = window->priv;
309
310 if (priv->game_choice_dialog) {
311 gtk_window_present (GTK_WINDOW (priv->game_choice_dialog));
312 return;
313 }
314
315 priv->game_choice_dialog = ar_game_chooser_new (window);
316 g_signal_connect (priv->game_choice_dialog, "destroy",
317 G_CALLBACK (gtk_widget_destroyed), &priv->game_choice_dialog);
318
319 gtk_window_present (GTK_WINDOW (priv->game_choice_dialog));
320 }
321
322 void
aisleriot_window_show_statistics_dialog(AisleriotWindow * window)323 aisleriot_window_show_statistics_dialog (AisleriotWindow *window)
324 {
325 AisleriotWindowPrivate *priv = window->priv;
326
327 if (!priv->stats_dialog) {
328 priv->stats_dialog = aisleriot_stats_dialog_new ();
329 gtk_window_set_transient_for (GTK_WINDOW (priv->stats_dialog),
330 GTK_WINDOW (window));
331
332 g_signal_connect (priv->stats_dialog, "response",
333 G_CALLBACK (stats_dialog_response_cb), window);
334 g_signal_connect (priv->stats_dialog, "destroy",
335 G_CALLBACK (gtk_widget_destroyed), &priv->stats_dialog);
336 }
337
338 update_statistics_display (window);
339
340 gtk_window_present (GTK_WINDOW (priv->stats_dialog));
341 }
342
343 void
aisleriot_window_show_about_dialog(AisleriotWindow * window)344 aisleriot_window_show_about_dialog (AisleriotWindow * window)
345 {
346 const char *authors[] = {
347 _("Main game:"),
348 "Jonathan Blandford <jrb@redhat.com>",
349 "Felix Bellaby <felix@pooh.u-net.com>",
350 "Rosanna Yuen <zana@webwynk.net>",
351 "Callum McKenzie <callum@physics.otago.ac.nz>",
352 "Christian Persch <chpe" "\100" "src.gnome.org>",
353 "Andreas Røsdal <andreasr" "\100" "gnome.org>",
354 "",
355 _("Card games:"),
356 "Jonathan Blandford <jrb@redhat.com>",
357 "W. Borgert <debacle@debian.org>",
358 "Robert Brady <rwb197@ecs.soton.ac.uk>",
359 "Nick Lamb <njl195@zepler.org.uk>",
360 "Changwoo Ryu <cwryu@adam.kaist.ac.kr>",
361 "Matthew Wilcox <matthew@wil.cx>",
362 "Rosanna Yuen <zana@webwynk.net>",
363 "Alan Horkan <horkana@maths.tcd.ie>",
364 "Richard Hoelscher <rah@rahga.com>",
365 "Vincent Povirk",
366 "Sapphire Becker",
367 NULL
368 };
369 const char *artists[] = {
370 _("Card themes:"),
371 /* Bellot cards */
372 "David Bellot http://david.bellot.free.fr/svg-cards",
373 /* Ornamental cards */
374 "Nicu Buculei http://www.nicubunu.ro/cards",
375 /* "Tango" cards */
376 "Frederik Elwert <frederik.elwert@web.de>",
377 /* Dondorf and Paris cards */
378 "Richard Hoelscher http://www.rahga.com/svg",
379 /* Anglo-American cards */
380 "Aike Reyer",
381 /* Guyenne Classic and Swiss cards */
382 "Mario Frasca <mariotomo@gmail.com>",
383 /* FIXMEchpe: who did the Bonded cards? */
384 NULL
385 };
386 const char *documenters[] = {
387 "Rosanna Yuen <zana@webwynk.net>",
388 NULL
389 };
390
391 char *licence;
392
393 licence = ar_get_licence ("AisleRiot");
394
395 gtk_show_about_dialog (GTK_WINDOW (window),
396 "program-name",
397 _("AisleRiot"),
398 "version", VERSION,
399 "title", _("About AisleRiot"),
400 "comments",
401 _("AisleRiot provides a rule-based solitaire "
402 "card engine that allows many different "
403 "games to be played."),
404 "copyright", "Copyright © 1998-2006 Jonathan Blandford\n"
405 "Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Christian Persch",
406 "license", licence,
407 "authors", authors,
408 "artists", artists,
409 "documenters", documenters,
410 "translator-credits", _("translator-credits"),
411 "logo-icon-name", "gnome-aisleriot",
412 "website", "http://wiki.gnome.org/Apps/Aisleriot",
413 "website-label", _("AisleRiot web site"),
414 "wrap-license", TRUE,
415 NULL);
416 g_free (licence);
417 }
418
419 /* action callbacks */
420
421 static void
new_game_cb(GtkAction * action,AisleriotWindow * window)422 new_game_cb (GtkAction *action,
423 AisleriotWindow *window)
424 {
425 aisleriot_window_new_game (window);
426 }
427
428 static void
undo_cb(GtkAction * action,AisleriotWindow * window)429 undo_cb (GtkAction *action,
430 AisleriotWindow *window)
431 {
432 AisleriotWindowPrivate *priv = window->priv;
433
434 /* If a move is in progress, cancel it before changing the game! */
435 aisleriot_board_abort_move (priv->board);
436
437 aisleriot_game_undo_move (priv->game);
438 }
439
440 static void
redo_cb(GtkAction * action,AisleriotWindow * window)441 redo_cb (GtkAction *action,
442 AisleriotWindow *window)
443 {
444 AisleriotWindowPrivate *priv = window->priv;
445
446 aisleriot_board_abort_move (priv->board);
447
448 aisleriot_game_redo_move (priv->game);
449 }
450
451 static void
help_about_cb(GtkAction * action,AisleriotWindow * window)452 help_about_cb (GtkAction *action,
453 AisleriotWindow *window)
454 {
455 aisleriot_window_show_about_dialog (window);
456 }
457
458 static void
restart_game(GtkAction * action,AisleriotWindow * window)459 restart_game (GtkAction *action,
460 AisleriotWindow *window)
461 {
462 AisleriotWindowPrivate *priv = window->priv;
463
464 aisleriot_game_restart_game (priv->game);
465 };
466
467 static void
select_game_cb(GtkAction * action,AisleriotWindow * window)468 select_game_cb (GtkAction *action,
469 AisleriotWindow *window)
470 {
471 aisleriot_window_change_game (window);
472 }
473
474 static void
help_general_cb(GtkAction * action,AisleriotWindow * window)475 help_general_cb (GtkAction *action,
476 AisleriotWindow *window)
477 {
478 aisleriot_show_help (GTK_WIDGET (window), NULL);
479 }
480
481 static void
help_on_game_cb(GtkAction * action,AisleriotWindow * window)482 help_on_game_cb (GtkAction *action,
483 AisleriotWindow *window)
484 {
485 AisleriotWindowPrivate *priv = window->priv;
486 const char *game_module;
487
488 game_module = aisleriot_game_get_game_module (priv->game);
489 aisleriot_show_help (GTK_WIDGET (window), game_module);
490 }
491
492 static void
help_overlay_cb(GSimpleAction * action,AisleriotWindow * window)493 help_overlay_cb (GSimpleAction *action,
494 AisleriotWindow *window)
495 {
496 g_action_group_activate_action (G_ACTION_GROUP (window), "show-help-overlay", NULL);
497 }
498
499 static void
close_window_cb(GtkAction * action,AisleriotWindow * window)500 close_window_cb (GtkAction *action,
501 AisleriotWindow *window)
502 {
503 gtk_widget_destroy (GTK_WIDGET (window));
504 }
505
506 static void
statistics_cb(GtkAction * action,AisleriotWindow * window)507 statistics_cb (GtkAction *action,
508 AisleriotWindow *window)
509 {
510 aisleriot_window_show_statistics_dialog (window);
511 }
512
513 static void
install_themes_cb(GtkAction * action,AisleriotWindow * window)514 install_themes_cb (GtkAction *action,
515 AisleriotWindow *window)
516 {
517 AisleriotWindowPrivate *priv = window->priv;
518
519 ar_card_themes_install_themes (priv->theme_manager,
520 GTK_WIDGET (window),
521 gtk_get_current_event_time ());
522 }
523
524 #ifdef ENABLE_DEBUG_UI
525
526 static void
debug_exception_cb(GtkAction * action,AisleriotWindow * window)527 debug_exception_cb (GtkAction *action,
528 AisleriotWindow *window)
529 {
530 AisleriotWindowPrivate *priv = window->priv;
531
532 aisleriot_game_generate_exception (priv->game);
533 }
534
535 #define DEBUG_WINDOW_DATA_KEY "debug-data"
536
537 typedef struct {
538 AisleriotWindow *window;
539 char **games;
540 int n_games;
541 int current;
542 } DebugWindowData;
543
544 static void
debug_data_free(DebugWindowData * data)545 debug_data_free (DebugWindowData *data)
546 {
547 g_strfreev (data->games);
548
549 g_slice_free (DebugWindowData, data);
550 }
551
552 static DebugWindowData *
debug_ensure_game_list(AisleriotWindow * window)553 debug_ensure_game_list (AisleriotWindow *window)
554 {
555 AisleriotWindowPrivate *priv = window->priv;
556 DebugWindowData *data;
557 char **games;
558 int n_games;
559 const char *current_game_module;
560 int i;
561
562 data = g_object_get_data (G_OBJECT (window), DEBUG_WINDOW_DATA_KEY);
563 if (data != NULL)
564 return data;
565
566 games = ar_get_game_modules ();
567 if (games == NULL)
568 return NULL;
569
570 n_games = g_strv_length (games);
571 if (n_games == 0) {
572 g_strfreev (games);
573 return NULL;
574 }
575
576 data = g_slice_new (DebugWindowData);
577 data->window = window;
578 data->games = games;
579 data->n_games = n_games;
580 data->current = -1;
581
582 current_game_module = aisleriot_game_get_game_module (priv->game);
583 for (i = 0; data->games[i]; ++i) {
584 if (strcmp (data->games[i], current_game_module) == 0) {
585 data->current = i;
586 break;
587 }
588 }
589
590 g_object_set_data_full (G_OBJECT (window), DEBUG_WINDOW_DATA_KEY,
591 data, (GDestroyNotify) debug_data_free);
592
593 return data;
594 }
595
596 static gboolean
debug_cycle_timeout_cb(DebugWindowData * data)597 debug_cycle_timeout_cb (DebugWindowData *data)
598 {
599 if (data->current >= -1)
600 data->current++;
601
602 /* We're done */
603 if (data->current >= data->n_games) {
604 data->current = data->n_games - 1;
605 return FALSE; /* don't run again */
606 }
607
608 aisleriot_window_set_game_module (data->window, data->games[data->current], NULL);
609
610 return TRUE; /* run again */
611 }
612
613 static void
debug_cycle_cb(GtkAction * action,AisleriotWindow * window)614 debug_cycle_cb (GtkAction *action,
615 AisleriotWindow *window)
616 {
617 DebugWindowData *data;
618
619 data = debug_ensure_game_list (window);
620 if (data == NULL)
621 return;
622
623 g_timeout_add (500, (GSourceFunc) debug_cycle_timeout_cb, data);
624 }
625
626 static void
debug_game_first(GtkAction * action,AisleriotWindow * window)627 debug_game_first (GtkAction *action,
628 AisleriotWindow *window)
629 {
630 DebugWindowData *data;
631
632 data = debug_ensure_game_list (window);
633 if (data == NULL)
634 return;
635
636 data->current = 0;
637 aisleriot_window_set_game_module (data->window, data->games[data->current], NULL);
638 }
639
640 static void
debug_game_last(GtkAction * action,AisleriotWindow * window)641 debug_game_last (GtkAction *action,
642 AisleriotWindow *window)
643 {
644 DebugWindowData *data;
645
646 data = debug_ensure_game_list (window);
647 if (data == NULL)
648 return;
649
650 data->current = data->n_games - 1;
651 aisleriot_window_set_game_module (data->window, data->games[data->current], NULL);
652 }
653
654 static void
debug_game_next(GtkAction * action,AisleriotWindow * window)655 debug_game_next (GtkAction *action,
656 AisleriotWindow *window)
657 {
658 DebugWindowData *data;
659
660 data = debug_ensure_game_list (window);
661 if (data == NULL || data->current + 1 == data->n_games)
662 return;
663
664 data->current++;
665 aisleriot_window_set_game_module (data->window, data->games[data->current], NULL);
666 }
667
668 static void
debug_game_prev(GtkAction * action,AisleriotWindow * window)669 debug_game_prev (GtkAction *action,
670 AisleriotWindow *window)
671 {
672 DebugWindowData *data;
673
674 data = debug_ensure_game_list (window);
675 if (data == NULL || data->current == 0)
676 return;
677
678 data->current--;
679 aisleriot_window_set_game_module (data->window, data->games[data->current], NULL);
680 }
681
682 static void
debug_choose_seed_response_cb(GtkWidget * dialog,int response,AisleriotWindow * window)683 debug_choose_seed_response_cb (GtkWidget *dialog,
684 int response,
685 AisleriotWindow *window)
686 {
687 if (response == GTK_RESPONSE_OK) {
688 AisleriotWindowPrivate *priv = window->priv;
689 GtkEntry *entry;
690 const char *text;
691 char *endptr;
692 guint seed;
693 GRand *rand;
694
695 entry = g_object_get_data (G_OBJECT (dialog), "entry");
696 text = gtk_entry_get_text (entry);
697
698 errno = 0;
699 seed = g_ascii_strtoull (text, &endptr, 10);
700 if (errno == 0 && endptr != text) {
701 rand = g_rand_new_with_seed (seed);
702
703 aisleriot_game_new_game_with_rand (priv->game, rand /* adopts */);
704
705 gtk_widget_grab_focus (GTK_WIDGET (priv->board));
706 }
707 }
708
709 gtk_widget_destroy (dialog);
710 }
711
712 static void
debug_choose_seed_cb(GtkAction * action,AisleriotWindow * window)713 debug_choose_seed_cb (GtkAction *action,
714 AisleriotWindow *window)
715 {
716 GtkWidget *dialog, *entry;
717
718 dialog = gtk_message_dialog_new (GTK_WINDOW (window),
719 GTK_DIALOG_DESTROY_WITH_PARENT |
720 GTK_DIALOG_MODAL,
721 GTK_MESSAGE_QUESTION,
722 GTK_BUTTONS_OK_CANCEL,
723 "%s", "Choose game seed");
724 g_signal_connect (dialog, "response",
725 G_CALLBACK (debug_choose_seed_response_cb), window);
726 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
727
728 entry = gtk_entry_new ();
729
730 #if 0
731 {
732 char str[32];
733 g_snprintf (str, sizeof (str), "%u", aisleriot_game_get_seed (priv->game));
734 gtk_entry_set_text (GTK_ENTRY (entry), str);
735 }
736 #endif
737
738 gtk_box_pack_end (GTK_BOX (gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog))), entry, FALSE, FALSE, 0);
739 gtk_widget_show (entry);
740 g_object_set_data (G_OBJECT (dialog), "entry", entry);
741 gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
742
743 gtk_window_present (GTK_WINDOW (dialog));
744 }
745
746 #endif /* ENABLE_DEBUG_UI */
747
748 static void
set_fullscreen_button_active(AisleriotWindow * window)749 set_fullscreen_button_active (AisleriotWindow *window)
750 {
751 #ifdef LEAVE_FULLSCREEN_BUTTON
752 AisleriotWindowPrivate *priv = window->priv;
753 gboolean active;
754
755 active = priv->fullscreen && !priv->toolbar_visible;
756 if (!active) {
757 if (priv->fullscreen_button != NULL) {
758 ar_fullscreen_button_set_active (AR_FULLSCREEN_BUTTON (priv->fullscreen_button), FALSE);
759 }
760
761 return;
762 }
763
764 if (active && priv->fullscreen_button == NULL) {
765 priv->fullscreen_button = ar_fullscreen_button_new (GTK_WINDOW (window),
766 GTK_CORNER_TOP_RIGHT);
767 }
768
769 ar_fullscreen_button_set_active (AR_FULLSCREEN_BUTTON (priv->fullscreen_button), TRUE);
770 #endif /* LEAVE_FULLSCREEN_BUTTON */
771 }
772
773 static void
toolbar_toggled_cb(GtkToggleAction * action,AisleriotWindow * window)774 toolbar_toggled_cb (GtkToggleAction *action,
775 AisleriotWindow *window)
776 {
777 AisleriotWindowPrivate *priv = window->priv;
778 gboolean state;
779
780 state = gtk_toggle_action_get_active (action);
781
782 g_object_set (priv->toolbar, "visible", state, NULL);
783
784 priv->toolbar_visible = state != FALSE;
785
786 ar_conf_set_boolean (NULL, aisleriot_conf_get_key (CONF_SHOW_TOOLBAR), state);
787
788 set_fullscreen_button_active (window);
789 }
790
791 static void
statusbar_toggled_cb(GtkToggleAction * action,AisleriotWindow * window)792 statusbar_toggled_cb (GtkToggleAction *action,
793 AisleriotWindow *window)
794 {
795 AisleriotWindowPrivate *priv = window->priv;
796 gboolean state;
797
798 state = gtk_toggle_action_get_active (action);
799
800 g_object_set (priv->statusbar, "visible", state, NULL);
801
802 /* Only update the clock continually if it's visible */
803 ar_clock_set_update (AR_CLOCK (priv->clock), state);
804
805 priv->statusbar_visible = state != FALSE;
806
807 ar_conf_set_boolean (NULL, aisleriot_conf_get_key (CONF_SHOW_STATUSBAR), state);
808 }
809
810 static void
set_fullscreen_actions(AisleriotWindow * window,gboolean is_fullscreen)811 set_fullscreen_actions (AisleriotWindow *window,
812 gboolean is_fullscreen)
813 {
814 AisleriotWindowPrivate *priv = window->priv;
815
816 priv->fullscreen = is_fullscreen;
817
818 g_object_set (priv->main_menu, "visible", !is_fullscreen, NULL);
819
820 gtk_action_set_visible (priv->action[ACTION_LEAVE_FULLSCREEN], is_fullscreen);
821 g_object_set (gtk_ui_manager_get_widget (priv->ui_manager, "/Toolbar/LeaveFullscreenSep"),
822 "visible", is_fullscreen,
823 "draw", FALSE,
824 NULL);
825
826 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->action[ACTION_FULLSCREEN]),
827 is_fullscreen);
828 }
829
830 static void
fullscreen_toggled_cb(GtkToggleAction * action,GtkWindow * window)831 fullscreen_toggled_cb (GtkToggleAction *action,
832 GtkWindow *window)
833 {
834 ar_debug_print (AR_DEBUG_WINDOW_STATE,
835 "[window %p] fullscreen_toggled_cb, %s fullscreen\n",
836 window,
837 gtk_toggle_action_get_active (action) ? "going" : "leaving");
838
839 if (gtk_toggle_action_get_active (action)) {
840 gtk_window_fullscreen (window);
841 } else {
842 gtk_window_unfullscreen (window);
843 }
844 }
845
846 static void
leave_fullscreen_cb(GtkAction * action,GtkWindow * window)847 leave_fullscreen_cb (GtkAction *action,
848 GtkWindow *window)
849 {
850 gtk_window_unfullscreen (window);
851 }
852
853 static void
clickmove_toggle_cb(GtkToggleAction * action,AisleriotWindow * window)854 clickmove_toggle_cb (GtkToggleAction *action,
855 AisleriotWindow *window)
856 {
857 AisleriotWindowPrivate *priv = window->priv;
858 gboolean click_to_move;
859
860 click_to_move = gtk_toggle_action_get_active (action);
861
862 aisleriot_game_set_click_to_move (priv->game, click_to_move);
863 ar_style_set_click_to_move (priv->board_style, click_to_move);
864
865 ar_conf_set_boolean (NULL, aisleriot_conf_get_key (CONF_CLICK_TO_MOVE), click_to_move);
866 }
867
868 static void
sound_toggle_cb(GtkToggleAction * action,AisleriotWindow * window)869 sound_toggle_cb (GtkToggleAction *action,
870 AisleriotWindow *window)
871 {
872 #ifdef ENABLE_SOUND
873 gboolean sound_enabled;
874
875 sound_enabled = gtk_toggle_action_get_active (action);
876
877 ar_sound_enable (sound_enabled);
878
879 ar_conf_set_boolean (NULL, aisleriot_conf_get_key (CONF_SOUND), sound_enabled);
880 #endif /* ENABLE_SOUND */
881 }
882
883 static void
show_hint_cb(GtkAction * action,AisleriotWindow * window)884 show_hint_cb (GtkAction *action,
885 AisleriotWindow *window)
886 {
887 AisleriotWindowPrivate *priv = window->priv;
888 char *message;
889 GtkWidget *dialog;
890
891 /* If the game hasn't started yet, getting a hint starts it */
892 aisleriot_game_start (priv->game);
893
894 if (priv->hint_dialog) {
895 gtk_widget_destroy (priv->hint_dialog);
896 priv->hint_dialog = NULL;
897 }
898
899 /* Only can show hints for running games */
900 if (aisleriot_game_get_state (priv->game) != GAME_RUNNING)
901 return;
902
903 message = aisleriot_game_get_hint (priv->game);
904 if (!message)
905 return;
906
907 dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window),
908 GTK_DIALOG_DESTROY_WITH_PARENT,
909 GTK_MESSAGE_INFO,
910 GTK_BUTTONS_OK,
911 "<b>%s</b>", message);
912 gtk_window_set_title (GTK_WINDOW (dialog), "");
913 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
914
915 gtk_widget_show (dialog);
916
917 priv->hint_dialog = dialog;
918 g_signal_connect (dialog, "response",
919 G_CALLBACK (gtk_widget_destroy), NULL);
920 g_signal_connect (dialog, "destroy",
921 G_CALLBACK (gtk_widget_destroyed), &priv->hint_dialog);
922
923 g_free (message);
924 }
925
926 static void
deal_cb(GtkAction * action,AisleriotWindow * window)927 deal_cb (GtkAction *action,
928 AisleriotWindow *window)
929 {
930 AisleriotWindowPrivate *priv = window->priv;
931
932 aisleriot_game_deal_cards (priv->game);
933 }
934
935 /* The "Game Options" menu */
936
937 static void
apply_option(GtkToggleAction * action,guint32 * changed_mask,guint32 * changed_value)938 apply_option (GtkToggleAction *action,
939 guint32 *changed_mask,
940 guint32 *changed_value)
941 {
942 gboolean active;
943 const char *action_name;
944 guint32 value;
945
946 active = gtk_toggle_action_get_active (action);
947
948 action_name = gtk_action_get_name (GTK_ACTION (action));
949 value = g_ascii_strtoull (action_name + strlen ("Option"), NULL, 10);
950
951 /* g_print ("option %s changed, value=%x set=%d\n", action_name, value, active); */
952
953 *changed_mask |= value;
954 if (active)
955 *changed_value |= value;
956 }
957
958 static void
option_cb(GtkToggleAction * action,AisleriotWindow * window)959 option_cb (GtkToggleAction *action,
960 AisleriotWindow *window)
961 {
962 AisleriotWindowPrivate *priv = window->priv;
963 gboolean active;
964 guint32 changed_mask = 0, changed_value = 0, value;
965
966 /* Don't change the options if we're just installing the options menu */
967 if (priv->changing_game_type)
968 return;
969
970 active = gtk_toggle_action_get_active (action);
971
972 /* If we're toggling OFF a radio action, don't redeal now,
973 * since we'll get called another time right again when the new option
974 * is toggled ON.
975 * The game options will be updated when we get the toggled signal
976 * for the newly active action in this group.
977 */
978 if (GTK_IS_RADIO_ACTION (action) &&
979 !active)
980 return;
981
982 if (GTK_IS_RADIO_ACTION (action)) {
983 GSList *group, *l;
984
985 /* If toggling ON a radio action, we didn't turn off the other option
986 * earlier. So we need to refresh the whole group.
987 */
988
989 group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
990
991 for (l = group; l; l = l->next) {
992 apply_option (GTK_TOGGLE_ACTION (l->data), &changed_mask, &changed_value);
993 }
994 } else {
995 apply_option (action, &changed_mask, &changed_value);
996 }
997
998 value = aisleriot_game_change_options (priv->game, changed_mask, changed_value);
999
1000 aisleriot_conf_set_options (aisleriot_game_get_game_module (priv->game), (int) value);
1001
1002 /* Now re-deal, so the option is applied */
1003 aisleriot_game_new_game (priv->game);
1004 }
1005
1006 static void
install_options_menu(AisleriotWindow * window)1007 install_options_menu (AisleriotWindow *window)
1008 {
1009 AisleriotWindowPrivate *priv = window->priv;
1010 GList *options, *l;
1011 int options_value = 0;
1012 GSList *radiogroup = NULL;
1013 int radion = 0;
1014
1015 if (priv->options_merge_id != 0) {
1016 gtk_ui_manager_remove_ui (priv->ui_manager, priv->options_merge_id);
1017 priv->options_merge_id = 0;
1018 }
1019
1020 if (priv->options_group) {
1021 gtk_ui_manager_remove_action_group (priv->ui_manager, priv->options_group);
1022 priv->options_group = NULL;
1023 }
1024
1025 /* See gtk bug #424448 */
1026 gtk_ui_manager_ensure_update (priv->ui_manager);
1027
1028 /* Only apply the options if they exist. Otherwise the options in the menu
1029 * and the real game options are out of sync until first changed by the user.
1030 */
1031 if (aisleriot_conf_get_options (aisleriot_game_get_game_module (priv->game), &options_value)) {
1032 aisleriot_game_change_options (priv->game, AISLERIOT_GAME_OPTIONS_MAX, options_value);
1033 }
1034
1035 /* To get radio buttons in the menu insert an atom into the option list
1036 * in your scheme code. To get back out of radio-button mode insert
1037 * another atom. The exact value of the atoms is irrelevant - they merely
1038 * trigger a toggle - but descriptive names like begin-exclusive and
1039 * end-exclusive are probably a good idea.
1040 */
1041 options = aisleriot_game_get_options (priv->game);
1042 if (!options)
1043 return;
1044
1045 priv->options_group = gtk_action_group_new ("Options");
1046 gtk_ui_manager_insert_action_group (priv->ui_manager, priv->options_group, -1);
1047 g_object_unref (priv->options_group);
1048
1049 priv->options_merge_id = gtk_ui_manager_new_merge_id (priv->ui_manager);
1050
1051 for (l = options; l != NULL; l = l->next) {
1052 AisleriotGameOption *option = (AisleriotGameOption *) l->data;
1053 GtkToggleAction *action;
1054 gchar actionname[32];
1055
1056 g_snprintf (actionname, sizeof (actionname), "Option%u", option->value);
1057
1058 if (option->type == AISLERIOT_GAME_OPTION_CHECK) {
1059 action = gtk_toggle_action_new (actionname,
1060 option->display_name,
1061 NULL,
1062 NULL /* tooltip */);
1063 radiogroup = NULL; /* make sure to start a new radio group when the next RADIO option comes */
1064 radion = 0;
1065 } else {
1066 action = GTK_TOGGLE_ACTION (gtk_radio_action_new (actionname,
1067 option->display_name,
1068 NULL,
1069 NULL /* tooltip */,
1070 radion++));
1071 gtk_radio_action_set_group (GTK_RADIO_ACTION (action),
1072 radiogroup);
1073 radiogroup = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
1074 }
1075
1076 gtk_toggle_action_set_active (action, option->set);
1077 g_signal_connect (action, "toggled",
1078 G_CALLBACK (option_cb), window);
1079
1080 gtk_action_group_add_action (priv->options_group, GTK_ACTION (action));
1081 g_object_unref (action);
1082
1083 gtk_ui_manager_add_ui (priv->ui_manager,
1084 priv->options_merge_id,
1085 OPTIONS_MENU_PATH,
1086 actionname, actionname,
1087 GTK_UI_MANAGER_MENUITEM, FALSE);
1088
1089 aisleriot_game_option_free (option);
1090 }
1091
1092 g_list_free (options);
1093 }
1094
1095 /* The "Recent Games" menu */
1096
1097 /*
1098 * Takes the name of the file that drives the game and
1099 * stores it as a recently played game
1100 * Recent games are stored at the end of the list.
1101 */
1102 static void
add_recently_played_game(AisleriotWindow * window,const char * game_module)1103 add_recently_played_game (AisleriotWindow *window,
1104 const char *game_module)
1105 {
1106 char **recent_games, **new_recent;
1107 gsize i, n_recent = 0, n_new_recent = 0;
1108
1109 if (!game_module)
1110 return;
1111
1112 recent_games = ar_conf_get_string_list (NULL, aisleriot_conf_get_key (CONF_RECENT_GAMES), &n_recent, NULL);
1113
1114 if (recent_games == NULL) {
1115 new_recent = g_new (char *, 2);
1116 new_recent[0] = g_strdup (game_module);
1117 new_recent[1] = NULL;
1118 n_new_recent = 1;
1119 } else {
1120 new_recent = g_new (char *, MIN (n_recent + 1, MAX_RECENT) + 1);
1121 n_new_recent = 0;
1122
1123 new_recent[n_new_recent++] = g_strdup (game_module);
1124
1125 for (i = 0; i < n_recent && n_new_recent < MAX_RECENT; ++i) {
1126 if (g_ascii_strcasecmp (game_module, recent_games[i]) != 0) {
1127 new_recent[n_new_recent++] = g_strdup (recent_games[i]);
1128 }
1129 }
1130
1131 /* NULL termination */
1132 new_recent[n_new_recent] = NULL;
1133
1134 g_strfreev (recent_games);
1135 }
1136
1137 ar_conf_set_string_list (NULL, aisleriot_conf_get_key (CONF_RECENT_GAMES),
1138 (const char * const *) new_recent, n_new_recent);
1139 g_strfreev (new_recent);
1140 }
1141
1142 static void
recent_game_cb(GtkAction * action,AisleriotWindow * window)1143 recent_game_cb (GtkAction *action,
1144 AisleriotWindow *window)
1145 {
1146 const char *game_module;
1147
1148 game_module = g_object_get_data (G_OBJECT (action), "game");
1149 g_return_if_fail (game_module != NULL);
1150
1151 aisleriot_window_set_game_module (window, game_module, NULL);
1152
1153 ar_conf_set_string (NULL, aisleriot_conf_get_key (CONF_VARIATION), game_module);
1154 }
1155
1156 static void
install_recently_played_menu(AisleriotWindow * window)1157 install_recently_played_menu (AisleriotWindow *window)
1158 {
1159 AisleriotWindowPrivate *priv = window->priv;
1160 char **recent_games;
1161 gsize i, n_recent = 0;
1162
1163 /* Clean out the old menu */
1164 if (priv->recent_games_merge_id != 0) {
1165 gtk_ui_manager_remove_ui (priv->ui_manager, priv->recent_games_merge_id);
1166 }
1167 if (priv->recent_games_group != NULL) {
1168 gtk_ui_manager_remove_action_group (priv->ui_manager, priv->recent_games_group);
1169 }
1170
1171 /* See gtk bug #424448 */
1172 gtk_ui_manager_ensure_update (priv->ui_manager);
1173
1174 priv->recent_games_group = gtk_action_group_new ("Recent");
1175 gtk_ui_manager_insert_action_group (priv->ui_manager, priv->recent_games_group, -1);
1176 g_object_unref (priv->recent_games_group);
1177
1178 priv->recent_games_merge_id = gtk_ui_manager_new_merge_id (priv->ui_manager);
1179
1180 recent_games = ar_conf_get_string_list (NULL, aisleriot_conf_get_key (CONF_RECENT_GAMES), &n_recent, NULL);
1181
1182 for (i = 0; i < n_recent; ++i) {
1183 GtkAction *action;
1184 char actionname[32];
1185 char *game_name, *tooltip;
1186
1187 g_snprintf (actionname, sizeof (actionname), "Recent%"G_GSIZE_FORMAT, i);
1188 game_name = ar_filename_to_display_name (recent_games[i]);
1189 tooltip = g_strdup_printf (_("Play “%s”"), game_name);
1190 action = gtk_action_new (actionname, game_name, tooltip, NULL);
1191 g_free (game_name);
1192 g_free (tooltip);
1193
1194 g_object_set_data_full (G_OBJECT (action), "game",
1195 ar_filename_to_game_module (recent_games[i]),
1196 (GDestroyNotify) g_free);
1197 g_signal_connect (action, "activate",
1198 G_CALLBACK (recent_game_cb), window);
1199 gtk_action_group_add_action (priv->recent_games_group, action);
1200 g_object_unref (action);
1201
1202 gtk_ui_manager_add_ui (priv->ui_manager,
1203 priv->recent_games_merge_id,
1204 RECENT_GAMES_MENU_PATH,
1205 actionname, actionname,
1206 GTK_UI_MANAGER_MENUITEM, FALSE);
1207 }
1208
1209 g_strfreev (recent_games);
1210 }
1211
1212 /* Card Theme menu */
1213
1214 static void
aisleriot_window_take_card_theme(AisleriotWindow * window,ArCardTheme * theme)1215 aisleriot_window_take_card_theme (AisleriotWindow *window,
1216 ArCardTheme *theme /* adopting */)
1217 {
1218 AisleriotWindowPrivate *priv = window->priv;
1219 GtkWidget *widget = GTK_WIDGET (window);
1220
1221 if (theme == priv->theme)
1222 return;
1223
1224 if (priv->theme) {
1225 g_object_unref (priv->theme);
1226 }
1227 priv->theme = theme;
1228
1229 if (gtk_widget_has_screen (widget)) {
1230 const cairo_font_options_t *font_options;
1231
1232 font_options = gdk_screen_get_font_options (gtk_widget_get_screen (widget));
1233 ar_card_theme_set_font_options (theme, font_options);
1234 }
1235
1236 ar_style_set_card_theme (priv->board_style, theme);
1237 }
1238
1239 static void
card_theme_changed_cb(GtkToggleAction * action,AisleriotWindow * window)1240 card_theme_changed_cb (GtkToggleAction *action,
1241 AisleriotWindow *window)
1242 {
1243 AisleriotWindowPrivate *priv = window->priv;
1244 ArCardThemeInfo *current_theme_info = NULL, *new_theme_info;
1245 ArCardTheme *theme;
1246 const char *theme_name;
1247
1248 if (!gtk_toggle_action_get_active (action))
1249 return;
1250
1251 new_theme_info = g_object_get_data (G_OBJECT (action), "theme-info");
1252 g_assert (new_theme_info != NULL);
1253
1254 if (priv->theme) {
1255 current_theme_info = ar_card_theme_get_theme_info (priv->theme);
1256 }
1257
1258 if (ar_card_theme_info_equal (new_theme_info, current_theme_info))
1259 return;
1260
1261 theme = ar_card_themes_get_theme (priv->theme_manager, new_theme_info);
1262 if (!theme) {
1263 GSList *group, *l;
1264
1265 gtk_widget_error_bell (GTK_WIDGET (window));
1266
1267 /* Set this action insensitive so we don't try again */
1268 gtk_action_set_sensitive (GTK_ACTION (action), FALSE);
1269
1270 /* Re-set the radio action of the current theme to active */
1271 group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
1272 for (l = group; l != NULL; l = l->next) {
1273 GtkToggleAction *theme_action = GTK_TOGGLE_ACTION (l->data);
1274 ArCardThemeInfo *info;
1275
1276 if (theme_action == action)
1277 continue;
1278
1279 info = g_object_get_data (G_OBJECT (theme_action), "theme-info");
1280 if (!ar_card_theme_info_equal (info, current_theme_info))
1281 continue;
1282
1283 /* The check at the top will prevent an infinite loop */
1284 gtk_toggle_action_set_active (theme_action, TRUE);
1285 break;
1286 }
1287
1288 return;
1289 }
1290
1291 aisleriot_window_take_card_theme (window, theme);
1292
1293 theme_name = ar_card_theme_info_get_persistent_name (new_theme_info);
1294 ar_conf_set_string (NULL, aisleriot_conf_get_key (CONF_THEME), theme_name);
1295 }
1296
1297 static void
install_card_theme_menu(ArCardThemes * theme_manager,AisleriotWindow * window)1298 install_card_theme_menu (ArCardThemes *theme_manager,
1299 AisleriotWindow *window)
1300 {
1301 AisleriotWindowPrivate *priv = window->priv;
1302 GList *list, *l;
1303 GSList *radio_group = NULL;
1304 ArCardThemeInfo *current_theme_info;
1305 guint i = 0;
1306
1307 /* Clean out the old menu */
1308 if (priv->card_themes_merge_id != 0) {
1309 gtk_ui_manager_remove_ui (priv->ui_manager, priv->card_themes_merge_id);
1310 priv->card_themes_merge_id = 0;
1311 }
1312 if (priv->card_themes_group != NULL) {
1313 gtk_ui_manager_remove_action_group (priv->ui_manager, priv->card_themes_group);
1314 priv->card_themes_group = NULL;
1315 }
1316
1317 /* See gtk bug #424448 */
1318 gtk_ui_manager_ensure_update (priv->ui_manager);
1319
1320 list = ar_card_themes_get_themes (priv->theme_manager);
1321
1322 /* No need to install the menu when there's only one theme available anyway */
1323 if (list == NULL || list->next == NULL) {
1324 g_list_foreach (list, (GFunc) ar_card_theme_info_unref, NULL);
1325 g_list_free (list);
1326 return;
1327 }
1328
1329 priv->card_themes_group = gtk_action_group_new ("Theme");
1330 gtk_ui_manager_insert_action_group (priv->ui_manager, priv->card_themes_group, -1);
1331 g_object_unref (priv->card_themes_group);
1332
1333 priv->card_themes_merge_id = gtk_ui_manager_new_merge_id (priv->ui_manager);
1334
1335 if (priv->theme) {
1336 current_theme_info = ar_card_theme_get_theme_info (priv->theme);
1337 } else {
1338 current_theme_info = NULL;
1339 }
1340
1341 for (l = list; l != NULL; l = l->next) {
1342 ArCardThemeInfo *info = (ArCardThemeInfo *) l->data;
1343 GtkRadioAction *action;
1344 char actionname[32];
1345 char *display_name, *tooltip;
1346
1347 display_name = g_strdup (ar_card_theme_info_get_display_name (info));
1348
1349 g_snprintf (actionname, sizeof (actionname), "Theme%d", i);
1350 tooltip = g_strdup_printf (_("Display cards with “%s” card theme"), display_name);
1351 action = gtk_radio_action_new (actionname, display_name, tooltip, NULL, i);
1352 g_free (display_name);
1353 g_free (tooltip);
1354
1355 gtk_radio_action_set_group (action, radio_group);
1356 radio_group = gtk_radio_action_get_group (action);
1357
1358 /* Check if this is the current theme's action. Do this before connecting the callback */
1359 if (ar_card_theme_info_equal (info, current_theme_info)) {
1360 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
1361 }
1362
1363 /* We steal the data from the list */
1364 g_object_set_data_full (G_OBJECT (action), "theme-info",
1365 l->data, (GDestroyNotify) ar_card_theme_info_unref);
1366 l->data = NULL;
1367
1368 g_signal_connect (action, "toggled",
1369 G_CALLBACK (card_theme_changed_cb), window);
1370 gtk_action_group_add_action (priv->card_themes_group, GTK_ACTION (action));
1371 g_object_unref (action);
1372
1373 gtk_ui_manager_add_ui (priv->ui_manager,
1374 priv->card_themes_merge_id,
1375 CARD_THEMES_MENU_PATH,
1376 actionname, actionname,
1377 GTK_UI_MANAGER_MENUITEM, FALSE);
1378
1379 ++i;
1380 }
1381
1382 /* The list elements' data's refcount has been adopted above */
1383 g_list_free (list);
1384 }
1385
1386 static void
view_menu_activate_cb(GtkAction * action,AisleriotWindow * window)1387 view_menu_activate_cb (GtkAction *action,
1388 AisleriotWindow *window)
1389 {
1390 AisleriotWindowPrivate *priv = window->priv;
1391
1392 /* Request the list of themes. If it wasn't updated yet, the "changed"
1393 * callback will build the card themes submenu.
1394 */
1395 ar_card_themes_request_themes (priv->theme_manager);
1396 }
1397
1398 /* Callbacks */
1399
1400 /* Game state synchronisation */
1401
1402 static void
sync_game_score(AisleriotGame * game,GParamSpec * pspec,AisleriotWindow * window)1403 sync_game_score (AisleriotGame *game,
1404 GParamSpec *pspec,
1405 AisleriotWindow *window)
1406 {
1407 AisleriotWindowPrivate *priv = window->priv;
1408
1409 gtk_label_set_text (GTK_LABEL (priv->score_label),
1410 aisleriot_game_get_score (game));
1411 }
1412
1413 static void
sync_game_state(AisleriotGame * game,GParamSpec * pspec,AisleriotWindow * window)1414 sync_game_state (AisleriotGame *game,
1415 GParamSpec *pspec,
1416 AisleriotWindow *window)
1417 {
1418 AisleriotWindowPrivate *priv = window->priv;
1419 guint state;
1420
1421 state = aisleriot_game_get_state (priv->game);
1422
1423 /* Can only change options before the game start.
1424 * Set all the options insensitive, not the menu item in the main menu,
1425 * since the HIG disapproves of that.
1426 */
1427 if (priv->options_group != NULL) {
1428 gtk_action_group_set_sensitive (priv->options_group, state <= GAME_BEGIN);
1429 }
1430
1431 /* Can only get hints while the game is running */
1432 gtk_action_set_sensitive (priv->action[ACTION_HINT],
1433 state == GAME_BEGIN || state == GAME_RUNNING);
1434
1435 if (state == GAME_RUNNING) {
1436 ar_clock_start (AR_CLOCK (priv->clock));
1437 } else {
1438 ar_clock_stop (AR_CLOCK (priv->clock));
1439 }
1440
1441 if (state >= GAME_OVER) {
1442 update_statistics_display (window);
1443 show_game_over_dialog (window);
1444 }
1445 }
1446
1447 static void
sync_game_undoable(AisleriotGame * game,GParamSpec * pspec,AisleriotWindow * window)1448 sync_game_undoable (AisleriotGame *game,
1449 GParamSpec *pspec,
1450 AisleriotWindow *window)
1451 {
1452 AisleriotWindowPrivate *priv = window->priv;
1453 gboolean enabled;
1454
1455 g_object_get (game, "can-undo", &enabled, NULL);
1456
1457 gtk_action_set_sensitive (priv->action[ACTION_UNDO_MOVE], enabled);
1458
1459 /* The restart game validity condition is the same as for undo. */
1460 gtk_action_set_sensitive (priv->action[ACTION_RESTART_GAME], enabled);
1461 }
1462
1463 static void
sync_game_redoable(AisleriotGame * game,GParamSpec * pspec,AisleriotWindow * window)1464 sync_game_redoable (AisleriotGame *game,
1465 GParamSpec *pspec,
1466 AisleriotWindow *window)
1467 {
1468 AisleriotWindowPrivate *priv = window->priv;
1469 gboolean enabled;
1470
1471 g_object_get (game, "can-redo", &enabled, NULL);
1472
1473 gtk_action_set_sensitive (priv->action[ACTION_REDO_MOVE], enabled);
1474 }
1475
1476 static void
sync_game_dealable(AisleriotGame * game,GParamSpec * pspec,AisleriotWindow * window)1477 sync_game_dealable (AisleriotGame *game,
1478 GParamSpec *pspec,
1479 AisleriotWindow *window)
1480 {
1481 AisleriotWindowPrivate *priv = window->priv;
1482 gboolean enabled;
1483
1484 g_object_get (game, "can-deal", &enabled, NULL);
1485
1486 gtk_action_set_sensitive (priv->action[ACTION_DEAL], enabled);
1487 }
1488
1489 static void
game_type_changed_cb(AisleriotGame * game,AisleriotWindow * window)1490 game_type_changed_cb (AisleriotGame *game,
1491 AisleriotWindow *window)
1492 {
1493 AisleriotWindowPrivate *priv = window->priv;
1494 char *game_name;
1495 guint features;
1496 gboolean dealable;
1497 gboolean show_scores;
1498
1499 priv->changing_game_type = TRUE;
1500
1501 game_name = aisleriot_game_get_name (game);
1502
1503 g_object_set (priv->action[ACTION_HELP_GAME], "label", game_name, NULL);
1504 g_object_set (priv->action[ACTION_OPTIONS_MENU], "label", game_name, NULL);
1505
1506 gtk_window_set_title (GTK_WINDOW (window), game_name);
1507
1508 g_free (game_name);
1509
1510 add_recently_played_game (window, aisleriot_game_get_game_module (game));
1511
1512 install_recently_played_menu (window);
1513
1514 install_options_menu (window);
1515
1516 update_statistics_display (window);
1517
1518 features = aisleriot_game_get_features (game);
1519
1520 dealable = (features & FEATURE_DEALABLE) != 0;
1521 gtk_action_set_visible (priv->action[ACTION_DEAL], dealable);
1522
1523 ar_clock_reset (AR_CLOCK (priv->clock));
1524
1525 gtk_statusbar_pop (priv->statusbar, priv->game_message_id);
1526 gtk_statusbar_pop (priv->statusbar, priv->board_message_id);
1527
1528 show_scores = (features & FEATURE_SCORE_HIDDEN) == 0;
1529 g_object_set (priv->score_box, "visible", show_scores, NULL);
1530
1531 priv->changing_game_type = FALSE;
1532 }
1533
1534 static void
game_new_cb(AisleriotGame * game,AisleriotWindow * window)1535 game_new_cb (AisleriotGame *game,
1536 AisleriotWindow *window)
1537 {
1538 AisleriotWindowPrivate *priv = window->priv;
1539
1540 update_statistics_display (window);
1541
1542 ar_clock_reset (AR_CLOCK (priv->clock));
1543
1544 gtk_statusbar_pop (priv->statusbar, priv->game_message_id);
1545 gtk_statusbar_pop (priv->statusbar, priv->board_message_id);
1546 }
1547
1548 static void
game_statusbar_message_cb(AisleriotGame * game,const char * message,AisleriotWindow * window)1549 game_statusbar_message_cb (AisleriotGame *game,
1550 const char *message,
1551 AisleriotWindow *window)
1552 {
1553 AisleriotWindowPrivate *priv = window->priv;
1554 guint id = priv->game_message_id;
1555
1556 gtk_statusbar_pop (priv->statusbar, id);
1557 if (message != NULL) {
1558 gtk_statusbar_push (priv->statusbar, id, message);
1559 }
1560 }
1561
1562 static void
game_exception_response_cb(GtkWidget * dialog,int response,AisleriotWindow * window)1563 game_exception_response_cb (GtkWidget *dialog,
1564 int response,
1565 AisleriotWindow *window)
1566 {
1567 AisleriotWindowPrivate *priv = window->priv;
1568 GError *error;
1569 gboolean did_report;
1570
1571 error = g_object_get_data (G_OBJECT (dialog), "error");
1572 g_assert (error != NULL);
1573
1574 did_report = FALSE;
1575 if (response == GTK_RESPONSE_ACCEPT) {
1576 GError *err = NULL;
1577 char pidstr[64];
1578 int fd;
1579 char *error_file;
1580
1581 g_snprintf (pidstr, sizeof (pidstr), "%d", getpid ());
1582
1583 fd = g_file_open_tmp ("arcrashXXXXXX", &error_file, &err);
1584 if (fd >= 0) {
1585 const char * const argv[] = {
1586 "bug-buddy",
1587 "--package", "aisleriot",
1588 "--appname", "sol",
1589 "--pid", pidstr,
1590 "--include", (const char *) error_file,
1591 "--unlink-tempfile",
1592 NULL
1593 };
1594
1595 close (fd);
1596
1597 g_file_set_contents (error_file, error->message, strlen (error->message), NULL);
1598
1599 if (g_spawn_async (NULL /* working dir */,
1600 (char **) argv,
1601 NULL /* envp */,
1602 G_SPAWN_SEARCH_PATH,
1603 NULL, NULL,
1604 NULL,
1605 &err)) {
1606 did_report = TRUE;
1607 } else {
1608 g_warning ("Failed to launch bug buddy: %s\n", err->message);
1609 g_error_free (err);
1610 }
1611
1612 g_free (error_file);
1613 } else {
1614 g_warning ("Failed to create temp file: %s\n", err->message);
1615 g_error_free (err);
1616 }
1617 }
1618
1619 if (!did_report) {
1620 g_printerr ("Aisleriot " VERSION " scheme exception occurred\n-- 8< --\n%s\n-- >8 --\n", error->message);
1621 }
1622
1623 gtk_widget_destroy (dialog);
1624
1625 /* Start a new game */
1626 aisleriot_game_new_game (priv->game);
1627
1628 gtk_widget_grab_focus (GTK_WIDGET (priv->board));
1629 }
1630
1631 static void
game_exception_cb(AisleriotGame * game,const GError * error,AisleriotWindow * window)1632 game_exception_cb (AisleriotGame *game,
1633 const GError *error,
1634 AisleriotWindow *window)
1635 {
1636 GtkWidget *dialog;
1637
1638 g_return_if_fail (error != NULL);
1639
1640 dialog = gtk_message_dialog_new (GTK_WINDOW (window),
1641 GTK_DIALOG_DESTROY_WITH_PARENT,
1642 GTK_MESSAGE_ERROR,
1643 GTK_BUTTONS_NONE,
1644 "%s", _("A scheme exception occurred"));
1645 gtk_message_dialog_format_secondary_text
1646 (GTK_MESSAGE_DIALOG (dialog),
1647 "%s", _("Please report this bug to the developers."));
1648
1649 gtk_window_set_title (GTK_WINDOW (dialog), "");
1650 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1651
1652 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1653 _("_Don't report"), GTK_RESPONSE_REJECT,
1654 _("_Report"), GTK_RESPONSE_ACCEPT,
1655 NULL);
1656 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
1657 GTK_RESPONSE_ACCEPT,
1658 GTK_RESPONSE_REJECT,
1659 -1);
1660 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1661
1662 g_signal_connect (dialog, "response",
1663 G_CALLBACK (game_exception_response_cb), window);
1664 g_object_set_data_full (G_OBJECT (dialog), "error",
1665 g_error_copy (error),
1666 (GDestroyNotify) g_error_free);
1667
1668 gtk_widget_show (dialog);
1669 }
1670
1671 #if defined(ENABLE_SOUND)
1672
1673 static void
settings_changed_cb(GtkSettings * settings,GParamSpec * pspec,AisleriotWindow * window)1674 settings_changed_cb (GtkSettings *settings,
1675 GParamSpec *pspec,
1676 AisleriotWindow *window)
1677 {
1678 AisleriotWindowPrivate *priv = window->priv;
1679 GtkAction *action;
1680 gboolean enabled;
1681 const char *name;
1682
1683 if (pspec)
1684 name = pspec->name;
1685 else
1686 name = NULL;
1687
1688 if (name == NULL || strcmp (name, "gtk-enable-event-sounds") == 0) {
1689 g_object_get (settings, "gtk-enable-event-sounds", &enabled, NULL);
1690
1691 action = gtk_action_group_get_action (priv->action_group, "Sound");
1692 gtk_action_set_visible (action, enabled);
1693 }
1694 }
1695
1696 static void
screen_changed_cb(GtkWidget * widget,GdkScreen * previous_screen,AisleriotWindow * window)1697 screen_changed_cb (GtkWidget *widget,
1698 GdkScreen *previous_screen,
1699 AisleriotWindow *window)
1700 {
1701 GdkScreen *screen;
1702 GtkSettings *settings;
1703
1704 screen = gtk_widget_get_screen (widget);
1705 if (screen == previous_screen)
1706 return;
1707
1708 if (previous_screen) {
1709 g_signal_handlers_disconnect_by_func (gtk_settings_get_for_screen (previous_screen),
1710 G_CALLBACK (settings_changed_cb),
1711 window);
1712 }
1713
1714 if (screen == NULL)
1715 return;
1716
1717 ar_sound_init (screen);
1718
1719 settings = gtk_widget_get_settings (widget);
1720 settings_changed_cb (settings, NULL, window);
1721 g_signal_connect (settings, "notify::gtk-enable-event-sounds",
1722 G_CALLBACK (settings_changed_cb), window);
1723 }
1724
1725 #endif /* ENABLE_SOUND */
1726
1727 static void
board_status_message_cb(AisleriotBoard * board,const char * status_message,AisleriotWindow * window)1728 board_status_message_cb (AisleriotBoard *board,
1729 const char *status_message,
1730 AisleriotWindow *window)
1731 {
1732 AisleriotWindowPrivate *priv = window->priv;
1733
1734 gtk_statusbar_pop (priv->statusbar, priv->board_message_id);
1735
1736 if (status_message != NULL) {
1737 gtk_statusbar_push (priv->statusbar, priv->board_message_id, status_message);
1738 }
1739 }
1740
1741 /* Class implementation */
1742
G_DEFINE_TYPE(AisleriotWindow,aisleriot_window,GTK_TYPE_APPLICATION_WINDOW)1743 G_DEFINE_TYPE (AisleriotWindow, aisleriot_window, GTK_TYPE_APPLICATION_WINDOW)
1744
1745 static void
1746 aisleriot_window_style_set (GtkWidget *widget,
1747 GtkStyle *previous_style)
1748 {
1749 AisleriotWindow *window = AISLERIOT_WINDOW (widget);
1750 AisleriotWindowPrivate *priv = window->priv;
1751 const cairo_font_options_t *font_options;
1752 void (* style_set) (GtkWidget *, GtkStyle *) =
1753 GTK_WIDGET_CLASS (aisleriot_window_parent_class)->style_set;
1754
1755 if (style_set)
1756 style_set (widget, previous_style);
1757
1758 if (!priv->theme)
1759 return;
1760
1761 font_options = gdk_screen_get_font_options (gtk_widget_get_screen (widget));
1762 ar_card_theme_set_font_options (priv->theme, font_options);
1763
1764 /* FIXMEchpe: clear the cached cards in the slots?? */
1765 }
1766
1767 static gboolean
aisleriot_window_state_event(GtkWidget * widget,GdkEventWindowState * event)1768 aisleriot_window_state_event (GtkWidget *widget,
1769 GdkEventWindowState *event)
1770 {
1771 AisleriotWindow *window = AISLERIOT_WINDOW (widget);
1772 AisleriotWindowPrivate *priv = window->priv;
1773
1774 if (event->changed_mask & (GDK_WINDOW_STATE_FULLSCREEN | GDK_WINDOW_STATE_MAXIMIZED)) {
1775 gboolean is_fullscreen;
1776
1777 is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
1778
1779 set_fullscreen_actions (window, is_fullscreen);
1780
1781 set_fullscreen_button_active (window);
1782 }
1783
1784 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
1785 if (aisleriot_game_get_state (priv->game) == GAME_RUNNING) {
1786 gboolean is_iconified;
1787
1788 is_iconified = (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED);
1789
1790 aisleriot_game_set_paused (priv->game, is_iconified);
1791 if (is_iconified) {
1792 ar_clock_stop (AR_CLOCK (priv->clock));
1793 } else {
1794 ar_clock_start (AR_CLOCK (priv->clock));
1795 }
1796 }
1797 }
1798
1799 if (GTK_WIDGET_CLASS (aisleriot_window_parent_class)->window_state_event) {
1800 return GTK_WIDGET_CLASS (aisleriot_window_parent_class)->window_state_event (widget, event);
1801 }
1802
1803 return FALSE;
1804 }
1805
1806 static void
aisleriot_window_init(AisleriotWindow * window)1807 aisleriot_window_init (AisleriotWindow *window)
1808 {
1809 const GtkActionEntry actions[] = {
1810 /* Menu actions */
1811 { "GameMenu", NULL, N_("_Game") },
1812 { "ViewMenu", NULL, N_("_View") },
1813 { "ControlMenu", NULL, N_("_Control") },
1814 { "OptionsMenu", NULL, "Options" },
1815 { "HelpMenu", NULL, N_("_Help") },
1816
1817 /* Menu item actions */
1818 { "NewGame", AR_STOCK_NEW_GAME, NULL,
1819 "<ctrl>N",
1820 N_("Start a new game"),
1821 G_CALLBACK (new_game_cb) },
1822 { "RestartGame", AR_STOCK_RESTART_GAME, NULL, NULL,
1823 N_("Restart the game"),
1824 G_CALLBACK (restart_game) },
1825 { "Select", GTK_STOCK_INDEX, N_("_Select Game…"),
1826 "<ctrl>O",
1827 N_("Play a different game"),
1828 G_CALLBACK (select_game_cb) },
1829 { "RecentMenu", NULL, N_("_Recently Played") },
1830 { "Statistics", NULL, N_("S_tatistics"), NULL,
1831 N_("Show gameplay statistics"),
1832 G_CALLBACK (statistics_cb) },
1833 { "CloseWindow", GTK_STOCK_CLOSE, NULL, NULL,
1834 N_("Close this window"),
1835 G_CALLBACK (close_window_cb) },
1836 { "UndoMove", AR_STOCK_UNDO_MOVE, NULL, NULL,
1837 N_("Undo the last move"),
1838 G_CALLBACK (undo_cb) },
1839 { "RedoMove", AR_STOCK_REDO_MOVE, NULL, NULL,
1840 N_("Redo the undone move"),
1841 G_CALLBACK (redo_cb) },
1842 { "Deal", AR_STOCK_DEAL_CARDS, NULL, NULL,
1843 N_("Deal next card or cards"),
1844 G_CALLBACK (deal_cb) },
1845 { "Hint", AR_STOCK_HINT, NULL, NULL,
1846 N_("Get a hint for your next move"),
1847 G_CALLBACK (show_hint_cb) },
1848 { "Contents", AR_STOCK_CONTENTS, NULL, NULL,
1849 N_("View help for Aisleriot"),
1850 G_CALLBACK (help_general_cb) },
1851 { "HelpGame", AR_STOCK_CONTENTS, NULL,
1852 "<shift>F1",
1853 N_("View help for this game"),
1854 G_CALLBACK (help_on_game_cb) },
1855 { "About", GTK_STOCK_ABOUT, NULL, NULL,
1856 N_("About this game"),
1857 G_CALLBACK (help_about_cb) },
1858 { "KeyboardShortcuts", NULL, N_("_Keyboard Shortcuts"),
1859 NULL,
1860 NULL,
1861 G_CALLBACK (help_overlay_cb) },
1862 { "InstallThemes", NULL, N_("Install card themes…"), NULL,
1863 N_("Install new card themes from the distribution packages repositories"),
1864 G_CALLBACK (install_themes_cb) },
1865
1866 /* Toolbar-only actions */
1867 { "LeaveFullscreen", AR_STOCK_LEAVE_FULLSCREEN, NULL, NULL, NULL,
1868 G_CALLBACK (leave_fullscreen_cb) },
1869 { "ThemeMenu", NULL, N_("_Card Style"), NULL, NULL, NULL },
1870
1871 #ifdef ENABLE_DEBUG_UI
1872 /* Debug UI actions */
1873 { "DebugMenu", NULL, "_Debug" },
1874 { "DebugChooseSeed", NULL, "_Choose seed", NULL, NULL,
1875 G_CALLBACK (debug_choose_seed_cb) },
1876 { "DebugException", NULL, "Generate E_xception", NULL, NULL,
1877 G_CALLBACK (debug_exception_cb) },
1878 { "DebugCycle", NULL, "Cycle through _all games", NULL, NULL,
1879 G_CALLBACK (debug_cycle_cb) },
1880 { "DebugGameFirst", GTK_STOCK_GOTO_FIRST, NULL, NULL, NULL,
1881 G_CALLBACK (debug_game_first) },
1882 { "DebugGameLast", GTK_STOCK_GOTO_LAST, NULL, NULL, NULL,
1883 G_CALLBACK (debug_game_last) },
1884 { "DebugGameNext", GTK_STOCK_GO_FORWARD, NULL, NULL, NULL,
1885 G_CALLBACK (debug_game_next) },
1886 { "DebugGamePrev", GTK_STOCK_GO_BACK, NULL, NULL, NULL,
1887 G_CALLBACK (debug_game_prev) },
1888 #endif /* ENABLE_DEBUG_UI */
1889 };
1890
1891 const GtkToggleActionEntry toggle_actions[] = {
1892 { "Fullscreen", AR_STOCK_FULLSCREEN, NULL, NULL, NULL,
1893 G_CALLBACK (fullscreen_toggled_cb),
1894 FALSE },
1895 { "Toolbar", NULL, N_("_Toolbar"), NULL,
1896 N_("Show or hide the toolbar"),
1897 G_CALLBACK (toolbar_toggled_cb),
1898 TRUE /* active by default since the UI manager creates the toolbar visible */
1899 },
1900 { "Statusbar", NULL, N_("_Statusbar"), NULL,
1901 N_("Show or hide statusbar"),
1902 G_CALLBACK (statusbar_toggled_cb),
1903 FALSE
1904 },
1905 { "ClickToMove", NULL, N_("_Click to Move"), NULL,
1906 N_("Pick up and drop cards by clicking"),
1907 G_CALLBACK (clickmove_toggle_cb),
1908 FALSE /* not active by default */ },
1909 { "Sound", NULL, N_("_Sound"), NULL,
1910 N_("Whether or not to play event sounds"),
1911 G_CALLBACK (sound_toggle_cb),
1912 FALSE /* not active by default */ },
1913 };
1914
1915 static const char names[][16] = {
1916 "UndoMove",
1917 "RedoMove",
1918 "RestartGame",
1919 "Fullscreen",
1920 "HelpGame",
1921 "OptionsMenu",
1922 "Deal",
1923 "Hint",
1924 "LeaveFullscreen",
1925 };
1926
1927 AisleriotWindowPrivate *priv;
1928 GtkWidget *main_vbox;
1929 GtkAccelGroup *accel_group;
1930 GtkAction *action;
1931 char *theme_name;
1932 ArCardTheme *theme;
1933 guint i;
1934 GtkStatusbar *statusbar;
1935 GtkWidget *statusbar_hbox, *label, *time_box;
1936 GError *error = NULL;
1937
1938 g_assert (G_N_ELEMENTS (names) == LAST_ACTION);
1939
1940 priv = window->priv = AISLERIOT_WINDOW_GET_PRIVATE (window);
1941
1942 priv->fullscreen = FALSE;
1943
1944 priv->game = aisleriot_game_new ();
1945
1946 priv->theme_manager = ar_card_themes_new ();
1947
1948 priv->board_style = ar_style_new ();
1949
1950 priv->board = AISLERIOT_BOARD (aisleriot_board_new (priv->board_style, priv->game));
1951
1952 theme_name = ar_conf_get_string (NULL, aisleriot_conf_get_key (CONF_THEME), NULL);
1953 theme = ar_card_themes_get_theme_by_name (priv->theme_manager, theme_name);
1954 g_free (theme_name);
1955 if (!theme) {
1956 /* Last-ditch fallback: try getting *any* theme */
1957 theme = ar_card_themes_get_theme_any (priv->theme_manager);
1958 }
1959 if (theme) {
1960 aisleriot_window_take_card_theme (window, theme /* adopts */);
1961 } else {
1962 /* FIXMEchpe: FUCK, what now? Panic! */
1963 /* Put up some UI, and exit! */
1964 g_assert_not_reached ();
1965 }
1966
1967 priv->action_group = gtk_action_group_new ("MenuActions");
1968 gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE);
1969 gtk_action_group_add_actions (priv->action_group,
1970 actions,
1971 G_N_ELEMENTS (actions),
1972 window);
1973 gtk_action_group_add_toggle_actions (priv->action_group,
1974 toggle_actions,
1975 G_N_ELEMENTS (toggle_actions),
1976 window);
1977
1978 priv->ui_manager = gtk_ui_manager_new ();
1979
1980 gtk_ui_manager_insert_action_group (priv->ui_manager, priv->action_group, -1);
1981
1982 for (i = 0; i < LAST_ACTION; ++i) {
1983 priv->action[i] = gtk_action_group_get_action (priv->action_group, names[i]);
1984 g_assert (priv->action[i]);
1985 }
1986
1987 /* Hide the "Deal" action initially, since not all games support it */
1988 gtk_action_set_visible (priv->action[ACTION_DEAL], FALSE);
1989
1990 /* Set labels for toolbar items */
1991 action = gtk_action_group_get_action (priv->action_group, "Select");
1992 g_object_set (action, "short-label", _("Select Game"), NULL);
1993
1994 statusbar = priv->statusbar = GTK_STATUSBAR (gtk_statusbar_new ());
1995 priv->game_message_id = gtk_statusbar_get_context_id (priv->statusbar, "game-message");
1996 ar_stock_prepare_for_statusbar_tooltips (priv->ui_manager,
1997 GTK_WIDGET (priv->statusbar));
1998
1999 priv->game_message_id = gtk_statusbar_get_context_id (priv->statusbar, "board-message");
2000
2001 g_signal_connect (priv->board, "status-message",
2002 G_CALLBACK (board_status_message_cb), window);
2003
2004 gtk_window_set_has_resize_grip (GTK_WINDOW (window), TRUE);
2005
2006 statusbar_hbox = gtk_statusbar_get_message_area (statusbar);
2007 gtk_box_set_spacing (GTK_BOX (statusbar_hbox), 24);
2008
2009 /* Score */
2010 priv->score_box = gtk_hbox_new (12, FALSE);
2011 label = gtk_label_new (_("Score:"));
2012 gtk_widget_show (label);
2013 gtk_box_pack_start (GTK_BOX (priv->score_box), label, FALSE, FALSE, 0);
2014 priv->score_label = gtk_label_new (" 0");
2015 gtk_widget_show (priv->score_label);
2016 gtk_box_pack_start (GTK_BOX (priv->score_box), priv->score_label, FALSE, FALSE, 0);
2017 gtk_box_pack_end (GTK_BOX (statusbar_hbox), priv->score_box, FALSE, FALSE, 0);
2018
2019 ar_atk_util_add_atk_relation (label, priv->score_label, ATK_RELATION_LABEL_FOR);
2020 ar_atk_util_add_atk_relation (priv->score_label, label, ATK_RELATION_LABELLED_BY);
2021
2022 time_box = gtk_hbox_new (12, FALSE);
2023 label = gtk_label_new (_("Time:"));
2024 gtk_box_pack_start (GTK_BOX (time_box), label, FALSE, FALSE, 0);
2025 priv->clock = ar_clock_new ();
2026 gtk_box_pack_start (GTK_BOX (time_box), priv->clock, FALSE, FALSE, 0);
2027 gtk_box_pack_end (GTK_BOX (statusbar_hbox), time_box, FALSE, FALSE, 0);
2028 gtk_widget_show_all (time_box);
2029
2030 ar_atk_util_add_atk_relation (label, priv->clock, ATK_RELATION_LABEL_FOR);
2031 ar_atk_util_add_atk_relation (priv->clock, label, ATK_RELATION_LABELLED_BY);
2032
2033 /* Load the UI after we've connected the statusbar,
2034 * otherwise not all actions will have statusbar help.
2035 */
2036 gtk_ui_manager_add_ui_from_resource (priv->ui_manager, "/org/gnome/aisleriot/ui/menus.xml", &error);
2037 g_assert_no_error (error);
2038 #ifdef ENABLE_DEBUG_UI
2039 gtk_ui_manager_add_ui_from_resource (priv->ui_manager, "/org/gnome/aisleriot/ui/debug-menus.xml", &error);
2040 g_assert_no_error (error);
2041 #endif /* ENABLE_DEBUG_UI */
2042
2043 priv->main_menu = gtk_ui_manager_get_widget (priv->ui_manager, MAIN_MENU_UI_PATH);
2044 priv->toolbar = gtk_ui_manager_get_widget (priv->ui_manager, TOOLBAR_UI_PATH);
2045
2046 gtk_style_context_add_class (gtk_widget_get_style_context (priv->toolbar),
2047 GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
2048
2049 /* Defer building the card themes menu until its parent's menu is opened */
2050 action = gtk_action_group_get_action (priv->action_group, "ViewMenu");
2051 g_signal_connect (action, "activate",
2052 G_CALLBACK (view_menu_activate_cb), window);
2053
2054 /* It's possible that the themes list has already been loaded (e.g.
2055 * if the theme loading above involved the fallback); in that case
2056 * we need to update the menu right now.
2057 */
2058 if (ar_card_themes_get_themes_loaded (priv->theme_manager))
2059 install_card_theme_menu (priv->theme_manager, window);
2060
2061 /* Rebuild the themes menu when the themes list changes */
2062 g_signal_connect (priv->theme_manager, "changed",
2063 G_CALLBACK (install_card_theme_menu), window);
2064
2065 /* The actions and menus are done. The
2066 * recent games menu will be updated when the initial game loads.
2067 */
2068
2069 accel_group = gtk_ui_manager_get_accel_group (priv->ui_manager);
2070 gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
2071
2072 action = gtk_action_group_get_action (priv->action_group, "Toolbar");
2073 priv->toolbar_visible = ar_conf_get_boolean (NULL, aisleriot_conf_get_key (CONF_SHOW_TOOLBAR), NULL) != FALSE;
2074 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
2075 priv->toolbar_visible);
2076 action = gtk_action_group_get_action (priv->action_group, "ClickToMove");
2077 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
2078 ar_conf_get_boolean (NULL, aisleriot_conf_get_key (CONF_CLICK_TO_MOVE), NULL));
2079
2080 action = gtk_action_group_get_action (priv->action_group, "RecentMenu");
2081 g_object_set (action, "hide-if-empty", FALSE, NULL);
2082
2083 #ifdef ENABLE_SOUND
2084 action = gtk_action_group_get_action (priv->action_group, "Sound");
2085 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
2086 ar_conf_get_boolean (NULL, aisleriot_conf_get_key (CONF_SOUND), NULL));
2087 gtk_action_set_visible (action, ar_sound_is_available ());
2088 #endif /* ENABLE_SOUND */
2089
2090 action = gtk_action_group_get_action (priv->action_group, "Statusbar");
2091 priv->statusbar_visible = ar_conf_get_boolean (NULL, aisleriot_conf_get_key (CONF_SHOW_STATUSBAR), NULL) != FALSE;
2092 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
2093 priv->statusbar_visible);
2094
2095 set_fullscreen_actions (window, FALSE);
2096
2097 #if defined(ENABLE_SOUND)
2098 /* Set the action visibility and listen for animation and sound mode changes */
2099 screen_changed_cb (GTK_WIDGET (window), NULL, window);
2100 g_signal_connect (window, "screen-changed",
2101 G_CALLBACK (screen_changed_cb), window);
2102 #endif /* ENABLE_SOUND */
2103
2104 /* Now set up the widgets */
2105 main_vbox = gtk_vbox_new (FALSE, 0);
2106 gtk_container_add (GTK_CONTAINER (window), main_vbox);
2107 gtk_widget_show (main_vbox);
2108
2109 gtk_box_pack_start (GTK_BOX (main_vbox), priv->main_menu, FALSE, FALSE, 0);
2110 gtk_box_pack_start (GTK_BOX (main_vbox), priv->toolbar, FALSE, FALSE, 0);
2111 gtk_box_pack_end (GTK_BOX (main_vbox), GTK_WIDGET (priv->statusbar), FALSE, FALSE, 0);
2112
2113 gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (priv->board), TRUE, TRUE, 0);
2114 gtk_widget_show (GTK_WIDGET (priv->board));
2115
2116 /* Synchronise */
2117 sync_game_score (priv->game, NULL, window);
2118 g_signal_connect (priv->game, "notify::score",
2119 G_CALLBACK (sync_game_score), window);
2120
2121 sync_game_state (priv->game, NULL, window);
2122 g_signal_connect (priv->game, "notify::state",
2123 G_CALLBACK (sync_game_state), window);
2124 sync_game_undoable (priv->game, NULL, window);
2125 g_signal_connect (priv->game, "notify::can-undo",
2126 G_CALLBACK (sync_game_undoable), window);
2127 sync_game_redoable (priv->game, NULL, window);
2128 g_signal_connect (priv->game, "notify::can-redo",
2129 G_CALLBACK (sync_game_redoable), window);
2130 sync_game_dealable (priv->game, NULL, window);
2131 g_signal_connect (priv->game, "notify::can-deal",
2132 G_CALLBACK (sync_game_dealable), window);
2133
2134 g_signal_connect (priv->game, "game-type",
2135 G_CALLBACK (game_type_changed_cb), window);
2136 g_signal_connect (priv->game, "game-new",
2137 G_CALLBACK (game_new_cb), window);
2138 g_signal_connect (priv->game, "message",
2139 G_CALLBACK (game_statusbar_message_cb), window);
2140 g_signal_connect (priv->game, "exception",
2141 G_CALLBACK (game_exception_cb), window);
2142
2143 /* Fallback, if there is no saved size */
2144 gtk_window_set_default_size (GTK_WINDOW (window), MIN_WIDTH, MIN_HEIGHT);
2145
2146 /* Restore window state */
2147 ar_gsettings_bind_window_state (AR_SETTINGS_WINDOW_STATE_PATH, GTK_WINDOW (window));
2148
2149 /* Initial focus is in the board */
2150 gtk_widget_grab_focus (GTK_WIDGET (priv->board));
2151 }
2152
2153 static void
aisleriot_window_dispose(GObject * object)2154 aisleriot_window_dispose (GObject *object)
2155 {
2156 AisleriotWindow *window = AISLERIOT_WINDOW (object);
2157 AisleriotWindowPrivate *priv = window->priv;
2158
2159 #ifdef ENABLE_SOUND
2160 g_signal_handlers_disconnect_by_func (gtk_widget_get_settings (GTK_WIDGET (window)),
2161 G_CALLBACK (settings_changed_cb),
2162 window);
2163 #endif /* ENABLE_SOUND */
2164
2165 if (priv->hint_dialog) {
2166 gtk_widget_destroy (priv->hint_dialog);
2167 g_assert (priv->hint_dialog == NULL);
2168 }
2169 if (priv->game_over_dialog) {
2170 gtk_widget_destroy (priv->game_over_dialog);
2171 g_assert (priv->game_over_dialog == NULL);
2172 }
2173 if (priv->game_choice_dialog) {
2174 gtk_widget_destroy (priv->game_choice_dialog);
2175 g_assert (priv->game_choice_dialog == NULL);
2176 }
2177 if (priv->stats_dialog) {
2178 gtk_widget_destroy (GTK_WIDGET (priv->stats_dialog));
2179 g_assert (priv->stats_dialog == NULL);
2180 }
2181
2182 #ifdef LEAVE_FULLSCREEN_BUTTON
2183 if (priv->fullscreen_button != NULL) {
2184 gtk_widget_destroy (GTK_WIDGET (priv->fullscreen_button));
2185 priv->fullscreen_button = NULL;
2186 }
2187 #endif
2188
2189 if (priv->load_idle_id != 0) {
2190 g_source_remove (priv->load_idle_id);
2191 priv->load_idle_id = 0;
2192 }
2193
2194 G_OBJECT_CLASS (aisleriot_window_parent_class)->dispose (object);
2195 }
2196
2197 static void
aisleriot_window_finalize(GObject * object)2198 aisleriot_window_finalize (GObject *object)
2199 {
2200 AisleriotWindow *window = AISLERIOT_WINDOW (object);
2201 AisleriotWindowPrivate *priv = window->priv;
2202
2203 if (priv->theme) {
2204 g_object_unref (priv->theme);
2205 }
2206
2207 g_object_unref (priv->theme_manager);
2208
2209 g_signal_handlers_disconnect_matched (priv->game,
2210 G_SIGNAL_MATCH_DATA,
2211 0, 0, NULL, NULL, window);
2212 g_object_unref (priv->game);
2213
2214 G_OBJECT_CLASS (aisleriot_window_parent_class)->finalize (object);
2215 }
2216
2217 static void
aisleriot_window_class_init(AisleriotWindowClass * klass)2218 aisleriot_window_class_init (AisleriotWindowClass *klass)
2219 {
2220 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
2221 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2222
2223 gobject_class->dispose = aisleriot_window_dispose;
2224 gobject_class->finalize = aisleriot_window_finalize;
2225
2226 widget_class->window_state_event = aisleriot_window_state_event;
2227 widget_class->style_set = aisleriot_window_style_set;
2228
2229 g_type_class_add_private (gobject_class, sizeof (AisleriotWindowPrivate));
2230 }
2231
2232 /* public API */
2233
2234 /**
2235 * aisleriot_window_new:
2236 *
2237 * Returns: a new #AisleriotWindow
2238 */
2239 GtkWidget *
aisleriot_window_new(GtkApplication * application)2240 aisleriot_window_new (GtkApplication *application)
2241 {
2242 return g_object_new (AISLERIOT_TYPE_WINDOW,
2243 "application", application,
2244 "show-menubar", FALSE,
2245 NULL);
2246 }
2247
2248 /**
2249 * aisleriot_window_get_ui_manager:
2250 * @window:
2251 *
2252 * Returns: (transfer none): the window's #GtkUIManager
2253 */
2254 GtkUIManager *
aisleriot_window_get_ui_manager(AisleriotWindow * window)2255 aisleriot_window_get_ui_manager (AisleriotWindow *window)
2256 {
2257 return window->priv->ui_manager;
2258 }
2259
2260 /**
2261 * aisleriot_window_get_ui_manager:
2262 * @window:
2263 *
2264 * Returns: (transfer none): the window's #GtkUIManager
2265 */
2266 GtkAction *
aisleriot_window_get_action(AisleriotWindow * window,const char * action_name)2267 aisleriot_window_get_action (AisleriotWindow *window,
2268 const char *action_name)
2269 {
2270 return gtk_action_group_get_action (window->priv->action_group, action_name);
2271 }
2272
2273 typedef struct {
2274 AisleriotWindow *window;
2275 char *game_module;
2276 GRand *rand;
2277 } LoadIdleData;
2278
2279 static void
load_error_response_cb(GtkWidget * dialog,int response,AisleriotWindow * window)2280 load_error_response_cb (GtkWidget *dialog,
2281 int response,
2282 AisleriotWindow *window)
2283 {
2284 /* Load the default game */
2285 aisleriot_window_set_game_module (window, DEFAULT_VARIATION, NULL);
2286
2287 gtk_widget_destroy (dialog);
2288 }
2289
2290 static gboolean
load_idle_cb(LoadIdleData * data)2291 load_idle_cb (LoadIdleData *data)
2292 {
2293 AisleriotWindowPrivate *priv = data->window->priv;
2294 GError *error = NULL;
2295 GRand *rand;
2296 char *pref;
2297
2298 if (!aisleriot_game_load_game (priv->game, data->game_module, &error)) {
2299 GtkWidget *dialog;
2300 char *name;
2301
2302 name = ar_filename_to_display_name (data->game_module);
2303
2304 dialog = gtk_message_dialog_new (GTK_WINDOW (data->window),
2305 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
2306 GTK_MESSAGE_ERROR,
2307 GTK_BUTTONS_OK,
2308 _("Cannot start the game “%s”"),
2309 name);
2310 g_free (name);
2311
2312 gtk_message_dialog_format_secondary_text
2313 (GTK_MESSAGE_DIALOG (dialog),
2314 "%s\n\n%s",
2315 _("Aisleriot cannot find the last game you played."),
2316 _("This usually occurs when you run an older version of Aisleriot "
2317 "which does not have the game you last played. "
2318 "The default game, Klondike, is being started instead."));
2319
2320 /* FIXME: add @error->message to a textview in a Detailed… expander */
2321 g_printerr ("Scheme exception:\n-- 8< --\n%s\n-- >8 --\n", error->message);
2322
2323 g_signal_connect (dialog, "response",
2324 G_CALLBACK (load_error_response_cb), data->window);
2325
2326 g_error_free (error);
2327
2328 gtk_window_present (GTK_WINDOW (dialog));
2329
2330 return FALSE;
2331 }
2332
2333 /* Now that we know we can successfully load this variation,
2334 * store it in conf
2335 */
2336 pref = g_strconcat (data->game_module, ".scm", NULL);
2337 ar_conf_set_string (NULL, aisleriot_conf_get_key (CONF_VARIATION), pref);
2338 g_free (pref);
2339
2340 rand = data->rand;
2341 data->rand = NULL;
2342
2343 aisleriot_game_new_game_with_rand (priv->game, rand /* adopted */);
2344
2345 gtk_widget_grab_focus (GTK_WIDGET (priv->board));
2346
2347 return FALSE;
2348 }
2349
2350 static void
free_load_idle_data(LoadIdleData * data)2351 free_load_idle_data (LoadIdleData *data)
2352 {
2353 data->window->priv->load_idle_id = 0;
2354
2355 if (data->rand)
2356 g_rand_free (data->rand);
2357
2358 g_free (data->game_module);
2359 g_slice_free (LoadIdleData, data);
2360 }
2361
2362 /**
2363 * aisleriot_window_set_game:
2364 * @window:
2365 * @game_module: a UTF-8 string
2366 * @rand: (allow-none) (transfer full): a #GRand, or %NULL
2367 *
2368 * Loads the game variation defined in the @game_module file.
2369 * Note that even though @game_module is used as a filename,
2370 * it must be in UTF-8!
2371 */
2372 void
aisleriot_window_set_game_module(AisleriotWindow * window,const char * game_module,GRand * rand)2373 aisleriot_window_set_game_module (AisleriotWindow *window,
2374 const char *game_module,
2375 GRand *rand)
2376 {
2377 AisleriotWindowPrivate *priv = window->priv;
2378 LoadIdleData *data;
2379
2380 /* We'll do this on idle */
2381 if (priv->load_idle_id != 0) {
2382 g_source_remove (priv->load_idle_id);
2383 }
2384
2385 data = g_slice_new (LoadIdleData);
2386 data->window = window;
2387 data->game_module = g_strdup (game_module);
2388 data->rand = rand; /* adopted */
2389
2390 priv->load_idle_id = g_idle_add_full (G_PRIORITY_LOW,
2391 (GSourceFunc) load_idle_cb,
2392 data,
2393 (GDestroyNotify) free_load_idle_data);
2394 }
2395
2396 /**
2397 * aisleriot_window_get_game_module:
2398 * @window:
2399 *
2400 * Returns: the name of the game running in @window
2401 */
2402 const char *
aisleriot_window_get_game_module(AisleriotWindow * window)2403 aisleriot_window_get_game_module (AisleriotWindow *window)
2404 {
2405 AisleriotWindowPrivate *priv = window->priv;
2406
2407 return aisleriot_game_get_game_module (priv->game);
2408 }
2409