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