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                                 &current_stats);
269 
270   aisleriot_stats_dialog_update (priv->stats_dialog, &current_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                                   &current_stats);
285     aisleriot_stats_dialog_update (priv->stats_dialog, &current_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