1 /*
2   GNUbik -- A 3 dimensional magic cube game.
3   Copyright (C) 2003, 2004, 2011  John Darrington
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 3 of the License,  or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <config.h>
20 #include <string.h>
21 
22 #include "cube.h"
23 #include "menus.h"
24 #include "dialogs.h"
25 #include "guile-hooks.h"
26 #include "colour-dialog.h"
27 #include "drwBlock.h"
28 #include "glarea.h"
29 
30 #include "game.h"
31 
32 #include <gtk/gtk.h>
33 
34 #include <libintl.h>
35 #define _(String) gettext (String)
36 #define N_(String) (String)
37 
38 
39 static void
new_view(GbkGame * game,const gchar * description,const gfloat * aspect)40 new_view (GbkGame * game, const gchar * description, const gfloat * aspect)
41 {
42   gchar *title;
43   GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
44   GtkWidget *view = gbk_cubeview_new (game->cube);
45 
46   /* Copy all the properties from the master view to the new one */
47   {
48     guint n, i;
49     GParamSpec **specs =
50       g_object_class_list_properties (G_OBJECT_GET_CLASS (view), &n);
51     for (i = 0; i < n; ++i)
52       {
53 	GValue value = { 0 };
54 
55 	if (!(specs[i]->flags & G_PARAM_WRITABLE))
56 	  continue;
57 
58 	if (!(specs[i]->flags & G_PARAM_READABLE))
59 	  continue;
60 
61 	if (specs[i]->owner_type != G_OBJECT_TYPE (view))
62 	  continue;
63 
64 	g_value_init (&value, specs[i]->value_type);
65 	g_object_get_property (G_OBJECT (game->masterview), specs[i]->name,
66 			       &value);
67 	g_object_set_property (G_OBJECT (view), specs[i]->name, &value);
68 	g_value_unset (&value);
69       }
70     g_free (specs);
71   }
72 
73   gtk_window_set_icon_name (GTK_WINDOW (window), "gnubik");
74 
75   title = g_strdup_printf ("%s %s", _(PACKAGE_NAME), description);
76 
77   gtk_window_set_title (GTK_WINDOW (window), title);
78 
79   g_free (title);
80 
81   gtk_container_add (GTK_CONTAINER (window), view);
82 
83   g_object_set (view, "aspect", aspect, NULL);
84 
85   gbk_game_add_view (game, GBK_CUBEVIEW (view), FALSE);
86 
87   gtk_widget_show_all (window);
88 }
89 
90 static void
view_rear(GtkAction * act,GbkGame * game)91 view_rear (GtkAction * act, GbkGame * game)
92 {
93   gfloat aspect[4] = { 180, 0, 1, 0 };
94   new_view (game, _("Rear View"), aspect);
95 }
96 
97 
98 static void
view_bottom(GtkAction * act,GbkGame * game)99 view_bottom (GtkAction * act, GbkGame * game)
100 {
101   gfloat aspect[4] = { 90, 1, 0, 0 };
102   new_view (game, _("Bottom View"), aspect);
103 }
104 
105 static void
view_top(GtkAction * act,GbkGame * game)106 view_top (GtkAction * act, GbkGame * game)
107 {
108   gfloat aspect[4] = { -90, 1, 0, 0 };
109   new_view (game, _("Top View"), aspect);
110 }
111 
112 
113 static void
view_left(GtkAction * act,GbkGame * game)114 view_left (GtkAction * act, GbkGame * game)
115 {
116   gfloat aspect[4] = { -90, 0, 1, 0 };
117   new_view (game, _("Left View"), aspect);
118 }
119 
120 static void
view_right(GtkAction * act,GbkGame * game)121 view_right (GtkAction * act, GbkGame * game)
122 {
123   gfloat aspect[4] = { 90, 0, 1, 0 };
124   new_view (game, _("Right View"), aspect);
125 }
126 
127 
128 
129 #define MSGLEN 100
130 static void
update_statusbar_mark(GbkGame * game,gint x,GtkStatusbar * statusbar)131 update_statusbar_mark (GbkGame * game, gint x, GtkStatusbar * statusbar)
132 {
133   gchar mesg[MSGLEN];
134 
135   int context = gtk_statusbar_get_context_id (statusbar, "marks");
136 
137   g_snprintf (mesg, MSGLEN,
138 	      _("A mark is now set at position %d."), game->posn);
139 
140   gtk_statusbar_pop (statusbar, context);
141 
142   game->mesg_id = gtk_statusbar_push (statusbar, context, mesg);
143 }
144 
145 static void
update_statusbar_moves(GbkGame * game,GtkStatusbar * statusbar)146 update_statusbar_moves (GbkGame * game, GtkStatusbar * statusbar)
147 {
148   gchar mesg[MSGLEN];
149 
150   int context = gtk_statusbar_get_context_id (statusbar, "move-count");
151 
152   g_snprintf (mesg, MSGLEN, _("Moves: %d / %d"), game->posn, game->total);
153 
154   gtk_statusbar_pop (statusbar, context);
155 
156   game->mesg_id = gtk_statusbar_push (statusbar, context, mesg);
157 
158 
159 
160   /* Now check if the cube is solved and update statusbar accordingly */
161 
162   context = gtk_statusbar_get_context_id (statusbar, "cube-state");
163 
164   enum cube_status status = gbk_cube_get_status (game->cube);
165 
166   if (NOT_SOLVED == status)
167     {
168       gtk_statusbar_pop (statusbar, context);
169     }
170   else if (SOLVED == status)
171     {
172       g_snprintf (mesg, MSGLEN,
173 		  ngettext ("Cube solved in %d move",
174 			    "Cube solved in %d moves", game->posn),
175 		  game->posn);
176       game->mesg_id = gtk_statusbar_push (statusbar, context, mesg);
177     }
178   else if (HALF_SOLVED == status)
179     {
180       g_snprintf (mesg,
181 		  MSGLEN,
182 		  _
183 		  ("Cube is NOT solved! The colours are correct,  but have incorrect orientation"));
184       game->mesg_id = gtk_statusbar_push (statusbar, context, mesg);
185     }
186 }
187 
188 static void
update_statusbar_animation(GbkCubeview * view,GParamSpec * sp,GtkStatusbar * statusbar)189 update_statusbar_animation (GbkCubeview * view, GParamSpec * sp,
190 			    GtkStatusbar * statusbar)
191 {
192   int context;
193 
194   gchar mesg[MSGLEN];
195   int n;
196 
197   context = gtk_statusbar_get_context_id (statusbar, "animation");
198 
199   g_object_get (view, "animation-frames", &n, NULL);
200   g_snprintf (mesg, MSGLEN,
201 	      ngettext ("Animation rate set to %d frame per turn.",
202 			"Animation rate set to %d frames per turn.", n), n);
203 
204   gtk_statusbar_pop (statusbar, context);
205   gtk_statusbar_push (statusbar, context, mesg);
206 }
207 
208 
209 GtkWidget *
create_statusbar(GbkGame * game)210 create_statusbar (GbkGame * game)
211 {
212   GtkWidget *statusbar = gtk_statusbar_new ();
213 
214   g_signal_connect (game, "queue-changed",
215 		    G_CALLBACK (update_statusbar_moves), statusbar);
216 
217   g_signal_connect (game, "mark-set",
218 		    G_CALLBACK (update_statusbar_mark), statusbar);
219 
220   g_signal_connect (game->masterview, "notify::animation-frames",
221 		    G_CALLBACK (update_statusbar_animation), statusbar);
222 
223   return statusbar;
224 }
225 
226 
227 static const GtkActionEntry action_entries[] = {
228   {"game-menu-action", NULL, N_("_Game")},
229   {"view-menu-action", NULL, N_("_View")},
230   /* TRANSLATORS: Lets the user see the cube from a different angle */
231   {"add-view-menu-action", NULL, N_("Add _View"),
232    NULL, N_("Add an auxiliary view of the cube")},
233 
234 
235   {"help-menu-action", NULL, N_("_Help")},
236   {"show-hide-menu-action", NULL, N_("Sho_w/Hide")},
237   {"scripts-menu-action", NULL, N_("_Scripts")},
238 
239   {
240    "about-action", GTK_STOCK_ABOUT, NULL,
241    NULL, "about", G_CALLBACK (about_dialog)},
242 
243   {
244    "quit-action", GTK_STOCK_QUIT, NULL,
245    "<control>Q", "quit", G_CALLBACK (gtk_main_quit)}
246 };
247 
248 
249 static void
restart_game(GtkWidget * w,GbkGame * game)250 restart_game (GtkWidget * w, GbkGame * game)
251 {
252   gbk_cube_scramble (game->cube);
253   gbk_game_reset (game);
254 }
255 
256 void
start_new_game(GbkGame * game,int size0,int size1,int size2,gboolean scramble)257 start_new_game (GbkGame * game, int size0, int size1, int size2,
258 		gboolean scramble)
259 {
260   gbk_cube_set_size (game->cube, size0, size1, size2);
261 
262   if (scramble)
263     gbk_cube_scramble (game->cube);
264 
265   gbk_game_reset (game);
266 }
267 
268 
269 static void
animate_faster(GtkWidget * w,GbkGame * game)270 animate_faster (GtkWidget * w, GbkGame * game)
271 {
272   GSList *v;
273   int frames;
274 
275   g_object_get (game->masterview, "animation-frames", &frames, NULL);
276   frames *= 2 / 3.0;
277 
278   /* Iterate over all the views and set the new properties */
279   for (v = game->views; v != NULL; v = g_slist_next (v))
280     {
281       g_object_set (v->data, "animation-frames", frames, NULL);
282     }
283 }
284 
285 static void
animate_slower(GtkWidget * w,GbkGame * game)286 animate_slower (GtkWidget * w, GbkGame * game)
287 {
288   int frames;
289   GSList *v;
290   g_object_get (game->masterview, "animation-frames", &frames, NULL);
291   frames /= 2 / 3.0;
292   if (frames == 0)
293     frames = 1;
294 
295   if (frames == 1)
296     frames = 2;
297 
298   /* Iterate over all the views and set the new properties */
299   for (v = game->views; v != NULL; v = g_slist_next (v))
300     {
301       g_object_set (v->data, "animation-frames", frames, NULL);
302     }
303 }
304 
305 static const GtkActionEntry game_action_entries[] = {
306   {
307    "restart-game-action", NULL, N_("_Restart Game"),
308    NULL, "restart-game", G_CALLBACK (restart_game)},
309 
310   {
311    "new-game-action", GTK_STOCK_NEW, N_("_New Game"),
312    "<control>N", "new-game", G_CALLBACK (new_game_dialog)},
313 
314 
315   {"add-view-rear-action", NULL, N_("_Rear"), NULL, NULL,
316    G_CALLBACK (view_rear)},
317   {"add-view-left-action", NULL, N_("_Left"), NULL, NULL,
318    G_CALLBACK (view_left)},
319   {"add-view-right-action", NULL, N_("Ri_ght"), NULL, NULL,
320    G_CALLBACK (view_right)},
321   {"add-view-top-action", NULL, N_("_Top"), NULL, NULL,
322    G_CALLBACK (view_top)},
323   {"add-view-bottom-action", NULL, N_("_Bottom"), NULL, NULL,
324    G_CALLBACK (view_bottom)},
325 
326 
327   {
328    "colours-action", GTK_STOCK_SELECT_COLOR, N_("_Colours"),
329    NULL, "colours", G_CALLBACK (colour_select_menu)},
330 
331   {
332    "animation-action", NULL, N_("_Animation"),
333    NULL, "animation", NULL},
334 
335   {
336    "animate-faster-action", NULL, N_("_Faster"),
337    "<control>plus", "animate-faster", G_CALLBACK (animate_faster)},
338 
339   {
340    "animate-slower-action", NULL, N_("_Slower"),
341    "<control>minus", "animate-slower", G_CALLBACK (animate_slower)},
342 
343 };
344 
345 
346 static const char menu_tree[] = "<ui>\
347   <menubar name=\"MainMenu\">\
348     <menu name=\"game-menu\" action=\"game-menu-action\">\
349      <menuitem name=\"restart-game\" action=\"restart-game-action\"/> \
350      <menuitem name=\"new-game\" action=\"new-game-action\"/> \
351      <menuitem name=\"quit\" action=\"quit-action\"/>\
352     </menu>\
353     <menu name=\"view-menu\" action=\"view-menu-action\"> \
354      <menuitem name=\"colours\" action=\"colours-action\"/> \
355      <menu name=\"add view\"   action=\"add-view-menu-action\"> \
356       <menuitem name=\"rear\"  action=\"add-view-rear-action\"/> \
357       <menuitem name=\"left\"  action=\"add-view-left-action\"/> \
358       <menuitem name=\"right\" action=\"add-view-right-action\"/> \
359       <menuitem name=\"top\"   action=\"add-view-top-action\"/> \
360       <menuitem name=\"bottom\" action=\"add-view-bottom-action\"/> \
361      </menu> \
362      <menu name=\"animation-menu\" action=\"animation-action\"> \
363        <menuitem name=\"faster\"   action=\"animate-faster-action\"/>	\
364        <menuitem name=\"slower\"   action=\"animate-slower-action\"/>	\
365      </menu> \
366     </menu> \
367     <menu name=\"scripts-menu\" action=\"scripts-menu-action\">\
368     </menu> \
369     <menu name=\"help-menu\" action=\"help-menu-action\">\
370      <menuitem name=\"about\" action=\"about-action\"/>\
371     </menu>\
372   </menubar>\
373 </ui>";
374 
375 
376 GtkWidget *
create_menubar(GbkGame * game)377 create_menubar (GbkGame * game)
378 {
379   GtkWidget *menubar;
380   GtkUIManager *menu_manager = gtk_ui_manager_new ();
381   GtkActionGroup *action_group = gtk_action_group_new ("dialog-actions");
382   GtkActionGroup *game_action_group = gtk_action_group_new ("game-actions");
383 
384   gtk_action_group_set_translation_domain (action_group, PACKAGE);
385   gtk_action_group_set_translation_domain (game_action_group, PACKAGE);
386 
387   gtk_action_group_add_actions (action_group, action_entries,
388 				sizeof (action_entries) /
389 				sizeof (action_entries[0]), game->toplevel);
390 
391 
392   gtk_action_group_add_actions (game_action_group, game_action_entries,
393 				sizeof (game_action_entries) /
394 				sizeof (game_action_entries[0]), game);
395 
396 
397   gtk_ui_manager_insert_action_group (menu_manager, action_group, 0);
398   gtk_ui_manager_insert_action_group (menu_manager, game_action_group, 0);
399 
400   if (0 ==
401       gtk_ui_manager_add_ui_from_string (menu_manager, menu_tree,
402 					 strlen (menu_tree), NULL))
403     g_return_val_if_reached (NULL);
404 
405   startup_guile_scripts (menu_manager);
406 
407   menubar = gtk_ui_manager_get_widget (menu_manager, "/ui/MainMenu");
408 
409   gtk_window_add_accel_group (GTK_WINDOW (game->toplevel),
410 			      gtk_ui_manager_get_accel_group (menu_manager));
411 
412   gtk_widget_show (menubar);
413 
414   return menubar;
415 }
416 
417 enum
418 {
419   ACT_REWIND = 0,
420   ACT_PREV,
421   ACT_STOP,
422   ACT_MARK,
423   ACT_NEXT,
424   ACT_PLAY,
425   n_ACTS
426 };
427 
428 
429 static void
set_playbar_sensitivities(GbkGame * g,GtkAction ** acts)430 set_playbar_sensitivities (GbkGame * g, GtkAction ** acts)
431 {
432   gboolean play_state = (g->mode == MODE_PLAY);
433 
434   gtk_action_set_sensitive (acts[ACT_REWIND], !play_state
435 			    && !gbk_game_at_start (g));
436   gtk_action_set_sensitive (acts[ACT_PREV], !play_state
437 			    && !gbk_game_at_start (g));
438 
439   gtk_action_set_sensitive (acts[ACT_PLAY], !play_state
440 			    && !gbk_game_at_end (g));
441   gtk_action_set_sensitive (acts[ACT_NEXT], !play_state
442 			    && !gbk_game_at_end (g));
443 
444   gtk_action_set_sensitive (acts[ACT_STOP], play_state);
445 }
446 
447 GtkWidget *
create_play_toolbar(GbkGame * game)448 create_play_toolbar (GbkGame * game)
449 {
450   int i;
451   static GtkAction *acts[n_ACTS];
452 
453   acts[ACT_REWIND] =
454     gtk_action_new ("rewind",
455 		    _("Rewind"),
456 		    _("Go to the previous mark (or the beginning of the sequence of moves)"),
457 		    GTK_STOCK_MEDIA_REWIND);
458 
459 
460   acts[ACT_PREV] =
461     gtk_action_new ("previous",
462 		    _("Back"),
463 		    _("Make one step backwards"), GTK_STOCK_MEDIA_PREVIOUS);
464 
465 
466   acts[ACT_STOP] =
467     gtk_action_new ("stop",
468 		    _("Stop"),
469 		    _("Stop running the sequence of moves"),
470 		    GTK_STOCK_MEDIA_STOP);
471 
472 
473   acts[ACT_MARK] =
474     gtk_action_new ("mark",
475 		    _("Mark"),
476 		    _("Mark the current place in the sequence of moves"),
477 		    GTK_STOCK_MEDIA_STOP);
478 
479 
480   acts[ACT_NEXT] =
481     gtk_action_new ("next",
482 		    _("Forward"),
483 		    _("Make one step forwards"), GTK_STOCK_MEDIA_NEXT);
484 
485 
486   acts[ACT_PLAY] =
487     gtk_action_new ("forward",
488 		    _("Play"),
489 		    _("Run forward through the sequence of moves"),
490 		    GTK_STOCK_MEDIA_PLAY);
491 
492 
493   GtkWidget *play_toolbar = gtk_toolbar_new ();
494 
495   set_playbar_sensitivities (game, acts);
496 
497   g_signal_connect (game, "queue-changed",
498 		    G_CALLBACK (set_playbar_sensitivities), acts);
499 
500   gtk_toolbar_set_style (GTK_TOOLBAR (play_toolbar), GTK_TOOLBAR_BOTH);
501 
502 
503   for (i = 0; i < n_ACTS; ++i)
504     {
505       gtk_toolbar_insert (GTK_TOOLBAR (play_toolbar),
506 			  GTK_TOOL_ITEM (gtk_action_create_tool_item
507 					 (acts[i])), -1);
508     }
509 
510   g_signal_connect_swapped (acts[ACT_REWIND], "activate",
511 			    G_CALLBACK (gbk_game_rewind), game);
512 
513   g_signal_connect_swapped (acts[ACT_STOP], "activate",
514 			    G_CALLBACK (gbk_game_stop_replay), game);
515 
516   g_signal_connect_swapped (acts[ACT_NEXT], "activate",
517 			    G_CALLBACK (gbk_game_next_move), game);
518 
519   g_signal_connect_swapped (acts[ACT_PREV], "activate",
520 			    G_CALLBACK (gbk_game_prev_move), game);
521 
522   g_signal_connect_swapped (acts[ACT_MARK], "activate",
523 			    G_CALLBACK (gbk_game_set_mark), game);
524 
525 
526   g_signal_connect_swapped (acts[ACT_PLAY], "activate",
527 			    G_CALLBACK (gbk_game_replay), game);
528 
529   gtk_widget_show_all (play_toolbar);
530 
531   return play_toolbar;
532 }
533 
534 
535 /* Popup an error dialog box */
536 void
error_dialog(GtkWindow * parent,const gchar * format,...)537 error_dialog (GtkWindow * parent, const gchar * format, ...)
538 {
539   va_list ap;
540   GtkWidget *dialog;
541   gchar *message;
542 
543   va_start (ap, format);
544 
545   message = g_strdup_vprintf (format, ap);
546 
547   va_end (ap);
548 
549   dialog = gtk_message_dialog_new (parent,
550 				   GTK_DIALOG_MODAL,
551 				   GTK_MESSAGE_ERROR,
552 				   GTK_BUTTONS_CLOSE, "%s", message);
553   g_free (message);
554 
555   gtk_window_set_title (GTK_WINDOW (dialog), _("Gnubik error"));
556 
557   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
558 
559   gtk_dialog_run (GTK_DIALOG (dialog));
560 
561   gtk_widget_destroy (dialog);
562 }
563