20081227
[gdash.git] / src / gtkmain.c
blobdd9cf97d5db56d862336cc1e2c6c3c1724d91a67
1 /*
2 * Copyright (c) 2007, 2008 Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <gtk/gtk.h>
17 #include <gdk/gdkkeysyms.h>
18 #include <glib.h>
19 #include <glib/gi18n.h>
20 #include "cave.h"
21 #include "cavedb.h"
22 #include "caveengine.h"
23 #include "caveobject.h"
24 #include "cavesound.h"
25 #include "caveset.h"
26 #include "c64import.h"
27 #include "editor.h"
28 #include "config.h"
29 #include "gtkgfx.h"
30 #include "help.h"
31 #include "settings.h"
32 #include "gtkui.h"
33 #include "util.h"
34 #include "gameplay.h"
35 #include "editorexport.h"
36 #include "about.h"
37 #include "sound.h"
39 #include "gtkmain.h"
41 /* game info */
42 static gboolean paused=FALSE;
43 static gboolean fast_forward=FALSE;
44 static gboolean fullscreen=FALSE;
46 static gboolean quit_thread;
48 typedef struct _gd_main_window {
49 GtkWidget *window;
50 GtkActionGroup *actions_normal, *actions_title, *actions_game, *actions_snapshot;
51 GtkWidget *scroll_window;
52 GtkWidget *drawing_area, *title_image; /* two things that could be drawn in the main window */
53 GdkPixmap **title_pixmaps;
54 GtkWidget *labels; /* parts of main window which have to be shown or hidden */
55 GtkWidget *label_score, *label_value, *label_time, *label_cave_name, *label_diamonds, *label_skeletons, *label_lives; /* different text labels in main window */
56 GtkWidget *label_key1, *label_key2, *label_key3, *label_gravity_will_change;
57 GtkWidget *label_variables;
58 GtkWidget *error_hbox, *error_label;
59 GtkWidget *menubar, *toolbar;
60 GtkWidget *replay_image_align;
61 } GDMainWindow;
63 static GDMainWindow main_window;
65 static gboolean key_lctrl=FALSE, key_rctrl=FALSE, key_right=FALSE, key_up=FALSE, key_down=FALSE, key_left=FALSE, key_suicide=FALSE;
66 static gboolean restart; /* keys which control the game, but are handled differently than the above */
67 static int mouse_cell_x=-1, mouse_cell_y=-1, mouse_cell_click=0;
69 static void init_mainwindow(Cave *);
70 static void install_game_timer();
72 Cave *snapshot=NULL;
79 static gboolean
80 fullscreen_idle_func(gpointer data)
82 gtk_window_fullscreen(GTK_WINDOW(data));
83 return FALSE;
86 /* set or unset fullscreen if necessary */
87 /* hack: gtk-win32 does not correctly handle fullscreen & removing widgets.
88 so we put fullscreening call into a low priority idle function, which will be called
89 after all window resizing & the like did take place. */
90 void
91 set_fullscreen(void)
93 if (gd_gameplay.cave && fullscreen) {
94 gtk_widget_hide(main_window.menubar);
95 gtk_widget_hide(main_window.toolbar);
96 g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc) fullscreen_idle_func, main_window.window, NULL);
98 else {
99 g_idle_remove_by_data (main_window.window);
100 gtk_window_unfullscreen (GTK_WINDOW(main_window.window));
101 gtk_widget_show(main_window.menubar);
102 gtk_widget_show(main_window.toolbar);
108 uninstall game timers, if any installed.
110 static void
111 uninstall_game_timeout()
113 quit_thread=TRUE;
114 /* remove timeout associated to game play */
115 while (g_source_remove_by_user_data (main_window.window)) {
116 /* nothing */
120 void
121 gd_main_stop_game()
123 uninstall_game_timeout();
124 init_mainwindow(NULL);
125 gd_stop_game();
126 set_fullscreen();
127 /* if editor is active, go back to its window. */
128 if (editor_window)
129 gtk_window_present(GTK_WINDOW(editor_window));
132 void
133 gd_main_window_set_title()
135 if (!g_str_equal(gd_caveset_data->name, "")) {
136 char *text;
138 text=g_strdup_printf("GDash - %s", gd_caveset_data->name);
139 gtk_window_set_title (GTK_WINDOW(main_window.window), text);
140 g_free (text);
142 else
143 gtk_window_set_title (GTK_WINDOW(main_window.window), "GDash");
150 /* game over, and highscore is achieved. ask for name, and if given, record it! */
151 static void
152 game_over_highscore()
154 char *text;
155 int rank;
157 text=g_strdup_printf(_("You have %d points, and achieved a highscore."), gd_gameplay.player_score);
158 gd_infomessage(_("Game over!"), text);
159 g_free(text);
161 /* enter to highscore table */
162 rank=gd_add_highscore(gd_caveset_data->highscore, gd_gameplay.player_name, gd_gameplay.player_score);
163 gd_show_highscore(main_window.window, NULL, FALSE, NULL, rank);
166 static void
167 game_over_without_highscore()
169 gchar *text;
171 text=g_strdup_printf(_("You have %d points."), gd_gameplay.player_score);
172 gd_infomessage(_("Game over!"), text);
173 g_free(text);
179 /* this starts a new game */
180 static void
181 new_game(const char *player_name, const int cave, const int level)
183 gd_new_game(player_name, cave, level);
184 install_game_timer();
188 void
189 gd_main_new_game_snapshot(Cave *snapshot)
191 gd_new_game_snapshot(snapshot);
192 install_game_timer();
195 void
196 gd_main_new_game_test(Cave *test, int level)
198 gd_new_game_test(test, level);
199 install_game_timer();
203 static void
204 gd_main_new_game_replay(Cave *cave, GdReplay *replay)
206 gd_new_game_replay(cave, replay);
207 install_game_timer();
211 /************************************************
213 * EVENTS
217 /* closing the window by the window manager */
218 static gboolean
219 delete_event (GtkWidget * widget, GdkEvent * event, gpointer data)
221 if (gd_discard_changes(main_window.window))
222 gtk_main_quit ();
223 return TRUE;
226 /* keypress. key_* can be event_type==gdk_key_press, as we
227 connected this function to key press and key release.
229 static gboolean
230 keypress_event (GtkWidget * widget, GdkEventKey* event, gpointer data)
232 gboolean press=event->type==GDK_KEY_PRESS; /* true for press, false for release */
234 if (event->keyval==gd_gtk_key_left) {
235 key_left=press;
236 return TRUE;
238 if (event->keyval==gd_gtk_key_right) {
239 key_right=press;
240 return TRUE;
242 if (event->keyval==gd_gtk_key_up) {
243 key_up=press;
244 return TRUE;
246 if (event->keyval==gd_gtk_key_down) {
247 key_down=press;
248 return TRUE;
250 if (event->keyval==gd_gtk_key_fire_1) {
251 key_lctrl=press;
252 return TRUE;
254 if (event->keyval==gd_gtk_key_fire_2) {
255 key_rctrl=press;
256 return TRUE;
258 if (event->keyval==gd_gtk_key_suicide) {
259 key_suicide=press;
260 return TRUE;
263 return FALSE; /* if any other key, we did not process it. go on, let gtk handle it. */
266 /* for mouse play. */
267 /* mouse leaves drawing area event */
268 /* if pointer not inside window, no control of player. */
269 static gboolean
270 drawing_area_leave_event(GtkWidget * widget, GdkEventCrossing* event, gpointer data)
272 mouse_cell_x=-1;
273 mouse_cell_y=-1;
274 mouse_cell_click=0;
275 return TRUE;
278 /* mouse button press event */
279 static gboolean
280 drawing_area_button_event(GtkWidget * widget, GdkEventButton * event, gpointer data)
282 /* see if it is a click or a release. */
283 mouse_cell_click=event->type == GDK_BUTTON_PRESS;
284 return TRUE;
287 /* mouse motion event */
288 static gboolean
289 drawing_area_motion_event(GtkWidget * widget, GdkEventMotion * event, gpointer data)
291 int x, y;
292 GdkModifierType state;
294 if (event->is_hint)
295 gdk_window_get_pointer (event->window, &x, &y, &state);
296 else {
297 x=event->x;
298 y=event->y;
299 state=event->state;
302 mouse_cell_x=x / gd_cell_size_game;
303 mouse_cell_y=y / gd_cell_size_game;
304 return TRUE;
310 * draws the cave during game play
312 static void
313 drawcave()
315 int x, y, xd, yd;
317 if (!main_window.drawing_area->window)
318 return;
319 if (!gd_gameplay.cave) /* if already no cave, just return */
320 return;
321 if (!gd_gameplay.gfx_buffer) /* if already no cave, just return */
322 return;
324 /* x and y are cave coordinates, which might not start from 0, as the top or the left hand side of the cave might not be visible. */
325 /* on the other hand, xd and yd are screen coordinates (of course, multiply by cell size) */
326 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
327 /* top left part of cave:
328 * x=0 xd=0
329 * |
330 *y=0 wwww|wwwwwwwwwwwwww
331 *yd=0 ----+--------------
332 * w...|o p........ visible part
333 * w...|..d...........
335 for (y=gd_gameplay.cave->y1, yd=0; y<=gd_gameplay.cave->y2; y++, yd++) {
336 for (x=gd_gameplay.cave->x1, xd=0; x<=gd_gameplay.cave->x2; x++, xd++) {
337 if (gd_gameplay.gfx_buffer[y][x] & GD_REDRAW) {
338 gd_gameplay.gfx_buffer[y][x] &= ~GD_REDRAW;
339 gdk_draw_drawable(main_window.drawing_area->window, main_window.drawing_area->style->black_gc, gd_game_pixmap(gd_gameplay.gfx_buffer[y][x]),
340 0, 0, xd*gd_cell_size_game, yd*gd_cell_size_game, gd_cell_size_game, gd_cell_size_game);
348 static gboolean
349 drawing_area_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
351 int x1, y1, x2, y2, xd, yd;
353 if (!widget->window)
354 return FALSE;
355 if (!gd_gameplay.cave) /* if already no cave, just return */
356 return FALSE;
357 if (!gd_gameplay.gfx_buffer) /* if already no cave, just return */
358 return FALSE;
360 /* redraw entire area, not specific rectangles.
361 * this function gets called only when the main window gets exposed
362 * by the user removing another window. */
363 /* these are screen coordinates. */
364 x1=event->area.x / gd_cell_size_game;
365 y1=event->area.y / gd_cell_size_game;
366 x2=(event->area.x + event->area.width-1) / gd_cell_size_game;
367 y2=(event->area.y + event->area.height-1) / gd_cell_size_game;
369 /* run through screen coordinates to refresh. */
370 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
371 /* top left part of cave:
372 * x=0 xd=0
373 * |
374 *y=0 wwww|wwwwwwwwwwwwww
375 *yd=0 ----+--------------
376 * w...|o p........ visible part
377 * w...|..d...........
379 /* mark all cells to be redrawn with GD_REDRAW, and then call the normal drawcave routine. */
380 for (yd=y1; yd<=y2; yd++) {
381 int y=yd+gd_gameplay.cave->y1;
382 for (xd=x1; xd<=x2; xd++) {
383 int x=xd+gd_gameplay.cave->x1;
384 if (gd_gameplay.gfx_buffer[y][x]!=-1) {
385 if (!(gd_gameplay.gfx_buffer[y][x] & GD_REDRAW))
386 gd_gameplay.gfx_buffer[y][x] |= GD_REDRAW;
390 drawcave();
392 return TRUE;
395 /* focus leaves game play window. remember that keys are not pressed!
396 as we don't receive key released events along with focus out. */
397 static gboolean
398 focus_out_event(GtkWidget *widget, GdkEvent *event, gpointer data)
400 key_lctrl=FALSE;
401 key_rctrl=FALSE;
402 key_right=FALSE;
403 key_up=FALSE;
404 key_down=FALSE;
405 key_left=FALSE;
406 key_suicide=FALSE;
408 return FALSE;
411 /**********************************-
413 * CALLBACKS
417 static void
418 help_cb(GtkWidget * widget, gpointer data)
420 gd_show_game_help (((GDMainWindow *)data)->window);
423 static void
424 preferences_cb(GtkWidget *widget, gpointer data)
426 gd_preferences(((GDMainWindow *)data)->window);
429 static void
430 control_settings_cb(GtkWidget *widget, gpointer data)
432 gd_control_settings(((GDMainWindow *)data)->window);
435 static void
436 quit_cb(GtkWidget * widget, const gpointer data)
438 gtk_main_quit();
441 void
442 stop_game_cb(GtkWidget *widget, gpointer data)
444 gd_main_stop_game();
447 static void
448 save_snapshot_cb(GtkWidget * widget, gpointer data)
450 if (snapshot)
451 gd_cave_free(snapshot);
453 snapshot=gd_create_snapshot();
454 gtk_action_group_set_sensitive (main_window.actions_snapshot, snapshot!=NULL);
457 static void
458 load_snapshot_cb(GtkWidget * widget, gpointer data)
460 g_return_if_fail(snapshot!=NULL);
461 gd_main_new_game_snapshot(snapshot);
464 /* restart level button clicked */
465 static void
466 restart_level_cb(GtkWidget * widget, gpointer data)
468 g_return_if_fail(gd_gameplay.cave!=NULL);
469 /* sets the restart variable, which will be interpreted by the iterate routine */
470 restart=TRUE;
473 static void
474 about_cb(GtkWidget *widget, gpointer data)
476 gtk_show_about_dialog(GTK_WINDOW(main_window.window), "program-name", "GDash", "license", gd_about_license, "wrap-license", TRUE, "authors", gd_about_authors, "version", PACKAGE_VERSION, "comments", _(gd_about_comments), "translator-credits", _(gd_about_translator_credits), "website", gd_about_website, "artists", gd_about_artists, "documenters", gd_about_documenters, NULL);
479 static void
480 open_caveset_cb(GtkWidget * widget, gpointer data)
482 gd_open_caveset(main_window.window, NULL);
483 gd_main_window_set_title();
486 static void
487 open_caveset_dir_cb(GtkWidget * widget, gpointer data)
489 gd_open_caveset(main_window.window, gd_system_caves_dir);
490 gd_main_window_set_title();
493 static void
494 save_caveset_as_cb(GtkWidget * widget, gpointer data)
496 gd_save_caveset_as(main_window.window);
499 static void
500 save_caveset_cb(GtkWidget * widget, gpointer data)
502 gd_save_caveset(main_window.window);
505 /* load internal game from the executable. those are inlined in caveset.c. */
506 static void
507 load_internal_cb(GtkWidget * widget, gpointer data)
509 gd_load_internal(main_window.window, GPOINTER_TO_INT(data));
510 gd_main_window_set_title();
513 static void
514 toggle_pause_cb(GtkWidget * widget, gpointer data)
516 paused=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
518 if (paused)
519 gd_no_sound();
522 static void
523 toggle_fullscreen_cb (GtkWidget * widget, gpointer data)
525 fullscreen=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
526 set_fullscreen();
529 static void
530 toggle_fast_cb (GtkWidget * widget, gpointer data)
532 fast_forward=gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (widget));
535 static void
536 showheader()
538 const Cave *cave=gd_gameplay.cave;
539 int time;
541 /* show time */
542 time=gd_cave_time_show(cave, cave->time);
543 if (gd_time_min_sec)
544 gd_label_set_markup_printf(GTK_LABEL(main_window.label_time), _("Time: <b>%02d:%02d</b>"), time/60, time%60);
545 else
546 gd_label_set_markup_printf(GTK_LABEL(main_window.label_time), _("Time: <b>%03d</b>"), time);
548 /* lives reamining in game */
549 switch (gd_gameplay.type) {
550 case GD_GAMETYPE_NORMAL:
551 if (!cave->intermission)
552 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("Lives: <b>%d</b>"), gd_gameplay.player_lives);
553 else
554 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("<b>Bonus life</b>"));
555 break;
556 case GD_GAMETYPE_SNAPSHOT:
557 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("Continuing from <b>snapshot</b>"));
558 break;
559 case GD_GAMETYPE_TEST:
560 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("<b>Testing</b> cave"));
561 break;
562 case GD_GAMETYPE_REPLAY:
563 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("Playing <b>replay</b>"));
564 break;
565 case GD_GAMETYPE_CONTINUE_REPLAY:
566 gd_label_set_markup_printf(GTK_LABEL(main_window.label_lives), _("Continuing <b>replay</b>"));
567 break;
569 /* game score */
570 gd_label_set_markup_printf(GTK_LABEL(main_window.label_score), _("Score: <b>%d</b>"), gd_gameplay.player_score);
572 /* diamond value */
573 gd_label_set_markup_printf(GTK_LABEL(main_window.label_value), _("Value: <b>%d</b>"), cave->diamond_value);
575 /* diamonds needed */
576 if (cave->diamonds_needed>0)
577 gd_label_set_markup_printf(GTK_LABEL(main_window.label_diamonds), _("Diamonds: <b>%d</b>"), cave->diamonds_collected>=cave->diamonds_needed?0:cave->diamonds_needed-cave->diamonds_collected);
578 else
579 /* did not already count diamonds */
580 gd_label_set_markup_printf(GTK_LABEL(main_window.label_diamonds), _("Diamonds: <b>??""?</b>")); /* "" to avoid C trigraph ??< */
583 /* skeletons collected */
584 gd_label_set_markup_printf(GTK_LABEL(main_window.label_skeletons), _("Skeletons: <b>%d</b>"), cave->skeletons_collected);
586 /* keys label */
587 gd_label_set_markup_printf(GTK_LABEL(main_window.label_key1), _("Key 1: <b>%d</b>"), cave->key1);
588 gd_label_set_markup_printf(GTK_LABEL(main_window.label_key2), _("Key 2: <b>%d</b>"), cave->key2);
589 gd_label_set_markup_printf(GTK_LABEL(main_window.label_key3), _("Key 3: <b>%d</b>"), cave->key3);
591 /* gravity label */
592 gd_label_set_markup_printf(GTK_LABEL(main_window.label_gravity_will_change), _("Gravity change: <b>%d</b>"), gd_cave_time_show(cave, cave->gravity_will_change));
594 if (editor_window && gd_show_test_label) {
595 gd_label_set_markup_printf(GTK_LABEL(main_window.label_variables),
596 "Speed: %dms, Amoeba timer: %ds %d, %ds %d, Magic wall timer: %ds\n"
597 "Expanding wall: %s, Creatures: %ds, %s, Gravity: %s\n"
598 "Kill player: %s, Sweet eaten: %s, Diamond key: %s",
599 cave->speed,
600 gd_cave_time_show(cave, cave->amoeba_time),
601 cave->amoeba_state,
602 gd_cave_time_show(cave, cave->amoeba_2_time),
603 cave->amoeba_2_state,
604 gd_cave_time_show(cave, cave->magic_wall_time),
605 // XXX cave->magic_wall_state,
606 cave->expanding_wall_changed?"vertical":"horizontal",
607 gd_cave_time_show(cave, cave->creatures_direction_will_change),
608 cave->creatures_backwards?"backwards":"forwards",
609 gd_direction_get_visible_name(cave->gravity_disabled?MV_STILL:cave->gravity),
610 cave->kill_player?"yes":"no",
611 cave->sweet_eaten?"yes":"no",
612 cave->diamond_key_collected?"yes":"no"
619 * init mainwindow
620 * creates title screen or drawing area
623 gboolean title_animation_func(gpointer data)
625 static int animcycle=0;
626 int count;
628 /* count the number of frames. */
629 count=0;
630 while (main_window.title_pixmaps[count]!=NULL)
631 count++;
633 if (gtk_window_has_toplevel_focus (GTK_WINDOW(main_window.window))) {
634 animcycle=(animcycle+1)%count;
635 gtk_image_set_from_pixmap(GTK_IMAGE(main_window.title_image), main_window.title_pixmaps[animcycle], NULL);
637 return TRUE;
640 void title_animation_remove()
642 int i;
644 g_source_remove_by_user_data(title_animation_func);
645 i=0;
646 while (main_window.title_pixmaps[i]!=NULL) {
647 g_object_unref(main_window.title_pixmaps[i]);
648 i++;
650 g_free(main_window.title_pixmaps);
651 main_window.title_pixmaps=NULL;
654 static void
655 init_mainwindow(Cave *cave)
657 if (cave) {
658 char *name_escaped;
660 /* cave drawing */
661 if (main_window.title_image)
662 gtk_widget_destroy (main_window.title_image->parent); /* bit tricky, destroy the viewport which was automatically added */
664 if (!main_window.drawing_area) {
665 GtkWidget *align;
667 /* put drawing area in an alignment, so window can be any large w/o problems */
668 align=gtk_alignment_new(0.5, 0.5, 0, 0);
669 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), align);
671 main_window.drawing_area=gtk_drawing_area_new();
672 gtk_widget_set_events (main_window.drawing_area, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_LEAVE_NOTIFY_MASK);
673 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_press_event", G_CALLBACK(drawing_area_button_event), NULL);
674 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_release_event", G_CALLBACK(drawing_area_button_event), NULL);
675 g_signal_connect (G_OBJECT(main_window.drawing_area), "motion_notify_event", G_CALLBACK(drawing_area_motion_event), NULL);
676 g_signal_connect (G_OBJECT(main_window.drawing_area), "leave_notify_event", G_CALLBACK(drawing_area_leave_event), NULL);
677 g_signal_connect (G_OBJECT(main_window.drawing_area), "expose_event", G_CALLBACK(drawing_area_expose_event), NULL);
678 g_signal_connect (G_OBJECT(main_window.drawing_area), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.drawing_area);
679 gtk_container_add (GTK_CONTAINER (align), main_window.drawing_area);
680 if (gd_mouse_play)
681 gdk_window_set_cursor (main_window.drawing_area->window, gdk_cursor_new(GDK_CROSSHAIR));
683 /* set the minimum size of the scroll window: 20*12 cells */
684 /* XXX adding some pixels for the scrollbars-here we add 24 */
685 gtk_widget_set_size_request(main_window.scroll_window, 20*gd_cell_size_game+24, 12*gd_cell_size_game+24);
686 gtk_widget_set_size_request(main_window.drawing_area, (cave->x2-cave->x1+1)*gd_cell_size_game, (cave->y2-cave->y1+1)*gd_cell_size_game);
688 /* show cave data */
689 gtk_widget_show(main_window.labels);
690 if (editor_window && gd_show_test_label)
691 gtk_widget_show(main_window.label_variables);
692 else
693 gtk_widget_hide(main_window.label_variables);
694 gtk_widget_hide(main_window.error_hbox);
696 name_escaped=g_markup_escape_text(cave->name, -1);
697 gd_label_set_markup_printf(GTK_LABEL(main_window.label_cave_name), _("<b>%s</b>, level %d"), name_escaped, cave->rendered);
698 g_free(name_escaped);
700 else {
701 if (main_window.drawing_area)
702 /* parent is the align, parent of align is the viewport automatically added. */
703 gtk_widget_destroy (main_window.drawing_area->parent->parent);
705 if (!main_window.title_image) {
706 int w, h;
708 /* title screen */
709 main_window.title_pixmaps=gd_create_title_animation();
710 main_window.title_image=gtk_image_new();
711 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.title_image);
712 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(title_animation_remove), NULL);
713 g_timeout_add(40, title_animation_func, title_animation_func);
714 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), main_window.title_image);
716 /* resize the scrolling window so the image fits - a bit larger than the image so scroll bars do not appear*/
717 gdk_drawable_get_size(GDK_DRAWABLE(main_window.title_pixmaps[0]), &w, &h);
718 gtk_widget_set_size_request(main_window.scroll_window, w+24, h+24);
721 /* hide cave data */
722 gtk_widget_hide(main_window.labels);
723 gtk_widget_hide(main_window.label_variables);
724 if (gd_has_new_error()) {
725 gtk_widget_show(main_window.error_hbox);
726 gtk_label_set(GTK_LABEL(main_window.error_label), ((GdErrorMessage *)(g_list_last(gd_errors)->data))->message);
727 } else {
728 gtk_widget_hide(main_window.error_hbox);
732 /* show newly created widgets */
733 gtk_widget_show_all(main_window.scroll_window);
735 /* set or unset fullscreen if necessary */
736 set_fullscreen ();
738 /* enable menus and buttons of game */
739 gtk_action_group_set_sensitive(main_window.actions_title, cave==NULL && !editor_window);
740 gtk_action_group_set_sensitive(main_window.actions_game, cave!=NULL);
741 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
742 /* if playing a cave or editor window exists, no music. */
743 if (cave || editor_window)
744 gd_music_stop();
745 else
746 gd_music_play_random();
747 if (editor_window)
748 gtk_widget_set_sensitive(editor_window, cave==NULL);
749 gtk_widget_hide(main_window.replay_image_align); /* it will be shown if needed. */
752 /* SCROLLING
754 * scrolls to the player during game play.
756 static void
757 scroll()
759 static int scroll_desired_x=0, scroll_desired_y=0;
760 static int scroll_speed_x=0, scroll_speed_y=0;
761 GtkAdjustment *adjustment;
762 int scroll_center_x, scroll_center_y;
763 gboolean out_of_window=FALSE;
764 gboolean changed;
765 int i;
766 int player_x, player_y;
767 const Cave *cave=gd_gameplay.cave;
768 gboolean exact_scroll;
769 int scroll_divisor;
771 scroll_divisor=12; /* some sort of scrolling speed */
772 if (gd_fine_scroll)
773 scroll_divisor*=2; /* as fine scrolling is 50hz, whereas normal is 25hz only */
775 /* if cave not yet rendered, return. (might be the case for 50hz scrolling */
776 if (gd_gameplay.cave==NULL)
777 return;
778 /* no scrolling when pause button is pressed */
779 if (paused)
780 return;
782 /* check player state. */
783 switch (gd_gameplay.cave->player_state) {
784 case GD_PL_NOT_YET:
785 exact_scroll=TRUE;
786 break;
788 case GD_PL_LIVING:
789 case GD_PL_EXITED:
790 exact_scroll=FALSE;
791 break;
793 case GD_PL_TIMEOUT:
794 case GD_PL_DIED:
795 /* do not scroll when the player is dead or cave time is over. */
796 return; /* return from function */
799 player_x=cave->player_x-cave->x1;
800 player_y=cave->player_y-cave->y1;
801 /* hystheresis size is this, multiplied by two.
802 * so player can move half the window without scrolling. */
803 int scroll_start_x=main_window.scroll_window->allocation.width/4;
804 int scroll_to_x=main_window.scroll_window->allocation.width/8;
805 int scroll_start_y=main_window.scroll_window->allocation.height/4;
806 int scroll_to_y=main_window.scroll_window->allocation.height/8;
808 /* get the size of the window so we know where to place player.
809 * first guess is the middle of the screen.
810 * main_window.drawing_area->parent->parent is the viewport.
811 * +cellsize/2 gets the stomach of player :) so the very center */
812 scroll_center_x=player_x*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.width/2;
813 scroll_center_y=player_y*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.height/2;
815 /* HORIZONTAL */
816 /* hystheresis function.
817 * when scrolling left, always go a bit less left than player being at the middle.
818 * when scrolling right, always go a bit less to the right. */
819 adjustment=gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
820 if (exact_scroll)
821 scroll_desired_x=scroll_center_x;
822 else {
823 if (adjustment->value+scroll_start_x<scroll_center_x)
824 scroll_desired_x=scroll_center_x-scroll_to_x;
825 if (adjustment->value-scroll_start_x>scroll_center_x)
826 scroll_desired_x=scroll_center_x+scroll_to_x;
828 scroll_desired_x=CLAMP(scroll_desired_x, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
829 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
830 if ((player_x*gd_cell_size_game)<adjustment->value || (player_x*gd_cell_size_game+gd_cell_size_game-1)>adjustment->value+main_window.drawing_area->parent->parent->allocation.width)
831 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
832 if (cave->player_x>=cave->x1 && cave->player_x<=cave->x2)
833 out_of_window=TRUE;
835 /* adaptive scrolling speed.
836 * gets faster with distance.
837 * minimum speed is 1, to allow scrolling precisely to the desired positions (important at borders).
839 if (scroll_speed_x<ABS(scroll_desired_x-adjustment->value)/scroll_divisor+1)
840 scroll_speed_x++;
841 if (scroll_speed_x>ABS(scroll_desired_x-adjustment->value)/scroll_divisor+1)
842 scroll_speed_x--;
843 changed=FALSE;
844 if (adjustment->value<scroll_desired_x) {
845 for (i=0; i<scroll_speed_x; i++)
846 if ((int)adjustment->value<scroll_desired_x)
847 adjustment->value++;
848 changed=TRUE;
850 if (adjustment->value > scroll_desired_x) {
851 for (i=0; i < scroll_speed_x; i++)
852 if ((int)adjustment->value>scroll_desired_x)
853 adjustment->value--;
854 changed=TRUE;
856 if (changed)
857 gtk_adjustment_value_changed (adjustment);
859 /* VERTICAL */
860 adjustment=gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
861 if (exact_scroll)
862 scroll_desired_y=scroll_center_y;
863 else {
864 if (adjustment->value + scroll_start_y < scroll_center_y)
865 scroll_desired_y=scroll_center_y-scroll_to_y;
866 if (adjustment->value-scroll_start_y > scroll_center_y)
867 scroll_desired_y=scroll_center_y + scroll_to_y;
869 scroll_desired_y=CLAMP(scroll_desired_y, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
870 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
871 if ((player_y*gd_cell_size_game)<adjustment->value || (player_y*gd_cell_size_game+gd_cell_size_game-1)>adjustment->value+main_window.drawing_area->parent->parent->allocation.height)
872 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
873 if (cave->player_y>=cave->y1 && cave->player_y<=cave->y2)
874 out_of_window=TRUE;
876 if (scroll_speed_y<ABS(scroll_desired_y-adjustment->value)/scroll_divisor+1)
877 scroll_speed_y++;
878 if (scroll_speed_y>ABS(scroll_desired_y-adjustment->value)/scroll_divisor+1)
879 scroll_speed_y--;
880 changed=FALSE;
881 if (adjustment->value < scroll_desired_y) {
882 for (i=0; i < scroll_speed_y; i++)
883 if ((int)adjustment->value < scroll_desired_y)
884 adjustment->value++;
885 changed=TRUE;
887 if (adjustment->value > scroll_desired_y) {
888 for (i=0; i < scroll_speed_y; i++)
889 if ((int)adjustment->value > scroll_desired_y)
890 adjustment->value--;
891 changed=TRUE;
893 if (changed)
894 gtk_adjustment_value_changed (adjustment);
896 /* remember if player is visible inside window */
897 gd_gameplay.out_of_window=out_of_window;
899 /* if not yet born, we treat as visible. so cave will run. the user is unable to control an unborn player, so this is the right behaviour. */
900 if (gd_gameplay.cave->player_state==GD_PL_NOT_YET)
901 gd_gameplay.out_of_window=FALSE;
912 * START NEW GAME DIALOG
914 * show a dialog to the user so he can select the cave to start game at.
918 typedef struct _gd_jump_dialog {
919 GtkWidget *dialog;
920 GtkWidget *combo_cave;
921 GtkWidget *spin_level;
922 GtkWidget *entry_name;
924 GtkWidget *image;
925 } GDJumpDialog;
927 /* keypress. key_* can be event_type==gdk_key_press, as we
928 connected this function to key press and key release.
930 static gboolean
931 new_game_keypress_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
933 GDJumpDialog *jump_dialog=(GDJumpDialog *)data;
934 int level=gtk_range_get_value (GTK_RANGE(jump_dialog->spin_level));
936 switch (event->keyval) {
937 case GDK_Left:
938 level--;
939 if (level<1)
940 level=1;
941 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
942 return TRUE;
943 case GDK_Right:
944 level++;
945 if (level>5)
946 level=5;
947 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
948 return TRUE;
949 case GDK_Return:
950 gtk_dialog_response(GTK_DIALOG(jump_dialog->dialog), GTK_RESPONSE_ACCEPT);
951 return TRUE;
953 return FALSE; /* if any other key, we did not process it. go on, let gtk handle it. */
957 /* update pixbuf */
958 static void
959 jump_cave_changed_signal(GtkWidget *widget, gpointer data)
961 GDJumpDialog *jump_dialog=(GDJumpDialog *)data;
962 GdkPixbuf *cave_image;
963 Cave *cave;
965 /* loading cave, draw cave and scale to specified size. seed=0 */
966 cave=gd_cave_new_from_caveset(gtk_combo_box_get_active(GTK_COMBO_BOX (jump_dialog->combo_cave)), gtk_range_get_value (GTK_RANGE(jump_dialog->spin_level))-1, 0);
967 cave_image=gd_drawcave_to_pixbuf(cave, 320, 240, TRUE);
968 gtk_image_set_from_pixbuf(GTK_IMAGE (jump_dialog->image), cave_image);
969 g_object_unref(cave_image);
971 /* freeing temporary cave data */
972 gd_cave_free(cave);
975 static void
976 new_game_cb (const GtkWidget * widget, const gpointer data)
978 static GdString player_name="";
979 GtkWidget *table, *expander, *eventbox;
980 GDJumpDialog jump_dialog;
981 GtkCellRenderer *renderer;
982 GtkListStore *store;
983 GList *iter;
985 /* check if caveset is empty! */
986 if (gd_caveset_count()==0) {
987 gd_warningmessage(_("There are no caves in this cave set!"), NULL);
988 return;
991 jump_dialog.dialog=gtk_dialog_new_with_buttons(_("Select cave to play"), GTK_WINDOW(main_window.window), GTK_DIALOG_NO_SEPARATOR | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, GTK_STOCK_JUMP_TO, GTK_RESPONSE_ACCEPT, NULL);
992 gtk_dialog_set_default_response (GTK_DIALOG(jump_dialog.dialog), GTK_RESPONSE_ACCEPT);
993 gtk_window_set_resizable (GTK_WINDOW(jump_dialog.dialog), FALSE);
995 table=gtk_table_new(0, 0, FALSE);
996 gtk_box_pack_start(GTK_BOX (GTK_DIALOG(jump_dialog.dialog)->vbox), table, FALSE, FALSE, 0);
997 gtk_container_set_border_width(GTK_CONTAINER (table), 6);
998 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
999 gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1001 /* name, which will be used for highscore & the like */
1002 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Name:")), 0, 1, 0, 1);
1003 if (g_str_equal(player_name, ""))
1004 gd_strcpy(player_name, g_get_real_name());
1005 jump_dialog.entry_name=gtk_entry_new();
1006 /* little inconsistency below: max length has unicode characters, while gdstring will have utf-8.
1007 however this does not make too much difference */
1008 gtk_entry_set_max_length(GTK_ENTRY(jump_dialog.entry_name), sizeof(GdString));
1009 gtk_entry_set_activates_default(GTK_ENTRY(jump_dialog.entry_name), TRUE);
1010 gtk_entry_set_text(GTK_ENTRY(jump_dialog.entry_name), player_name);
1011 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.entry_name, 1, 2, 0, 1);
1013 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Cave:")), 0, 1, 1, 2);
1015 /* store of caves: cave pointer, cave name, selectable */
1016 store=gtk_list_store_new(3, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1017 for (iter=gd_caveset; iter; iter=g_list_next (iter)) {
1018 Cave *cave=iter->data;
1019 GtkTreeIter treeiter;
1021 gtk_list_store_insert_with_values (store, &treeiter, -1, 0, iter->data, 1, cave->name, 2, cave->selectable || gd_all_caves_selectable, -1);
1023 jump_dialog.combo_cave=gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
1024 g_object_unref(store);
1026 renderer=gtk_cell_renderer_text_new();
1027 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, TRUE);
1028 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, "text", 1, "sensitive", 2, NULL);
1029 /* we put the combo in an event box, so we can receive keypresses on our own */
1030 eventbox=gtk_event_box_new();
1031 gtk_container_add(GTK_CONTAINER(eventbox), jump_dialog.combo_cave);
1032 gtk_table_attach_defaults(GTK_TABLE(table), eventbox, 1, 2, 1, 2);
1034 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Level:")), 0, 1, 2, 3);
1035 jump_dialog.spin_level=gtk_hscale_new_with_range(1.0, 5.0, 1.0);
1036 gtk_scale_set_value_pos(GTK_SCALE(jump_dialog.spin_level), GTK_POS_LEFT);
1037 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.spin_level, 1, 2, 2, 3);
1039 g_signal_connect(G_OBJECT(jump_dialog.combo_cave), "changed", G_CALLBACK(jump_cave_changed_signal), &jump_dialog);
1040 gtk_widget_add_events(eventbox, GDK_KEY_PRESS_MASK);
1041 g_signal_connect(G_OBJECT(eventbox), "key_press_event", G_CALLBACK(new_game_keypress_event), &jump_dialog);
1042 g_signal_connect(G_OBJECT(jump_dialog.spin_level), "value-changed", G_CALLBACK(jump_cave_changed_signal), &jump_dialog);
1044 /* this allows the user to select if he wants to see a preview of the cave */
1045 expander=gtk_expander_new(_("Preview"));
1046 gtk_expander_set_expanded(GTK_EXPANDER (expander), gd_show_preview);
1047 gtk_table_attach_defaults(GTK_TABLE(table), expander, 0, 2, 3, 4);
1048 jump_dialog.image=gtk_image_new();
1049 gtk_container_add(GTK_CONTAINER (expander), jump_dialog.image);
1051 gtk_widget_show_all(jump_dialog.dialog);
1052 gtk_widget_grab_focus(jump_dialog.combo_cave);
1053 gtk_editable_select_region(GTK_EDITABLE(jump_dialog.entry_name), 0, 0);
1054 /* set default and also trigger redrawing */
1055 gtk_combo_box_set_active(GTK_COMBO_BOX(jump_dialog.combo_cave), gd_caveset_last_selected);
1056 gtk_range_set_value(GTK_RANGE(jump_dialog.spin_level), 1);
1058 if (gtk_dialog_run (GTK_DIALOG(jump_dialog.dialog)) == GTK_RESPONSE_ACCEPT) {
1059 gd_strcpy(player_name, gtk_entry_get_text(GTK_ENTRY(jump_dialog.entry_name)));
1060 gd_caveset_last_selected=gtk_combo_box_get_active(GTK_COMBO_BOX(jump_dialog.combo_cave));
1061 gd_caveset_last_selected_level=gtk_range_get_value(GTK_RANGE(jump_dialog.spin_level))-1;
1062 new_game (player_name, gd_caveset_last_selected, gd_caveset_last_selected_level);
1064 gd_show_preview=gtk_expander_get_expanded(GTK_EXPANDER (expander)); /* remember expander state-even if cancel pressed */
1065 gtk_widget_destroy(jump_dialog.dialog);
1070 /* THE MAIN GAME TIMER */
1071 /* called at 50hz */
1072 /* for the gtk version, it seems nicer if we first draw, then scroll. */
1073 /* this is because there is an expose event; scrolling the "old" drawing would draw the old, and then the new. */
1074 /* (this is not true for the sdl version) */
1075 #if 0
1076 static GTimer *timer=NULL;
1077 static int called=0;
1078 #endif
1080 static gboolean
1081 main_int(gpointer data)
1083 static gboolean toggle=FALSE; /* value irrelevant */
1084 int up, down, left, right;
1085 GdDirection player_move;
1086 gboolean fire;
1087 GdGameState state;
1089 #if 0
1090 called++;
1091 if (called%100==0)
1092 g_message("%8d. call, avg %gms", called, g_timer_elapsed(timer, NULL)*1000/called);
1093 #endif
1095 if (gd_gameplay.type==GD_GAMETYPE_REPLAY)
1096 gtk_widget_show(main_window.replay_image_align);
1097 else
1098 gtk_widget_hide(main_window.replay_image_align);
1100 up=key_up;
1101 down=key_down;
1102 left=key_left;
1103 right=key_right;
1104 fire=key_lctrl || key_rctrl;
1106 /* compare mouse coordinates to player coordinates, and make up movements */
1107 if (gd_mouse_play && mouse_cell_x>=0) {
1108 down=down || (gd_gameplay.cave->player_y<mouse_cell_y);
1109 up=up || (gd_gameplay.cave->player_y>mouse_cell_y);
1110 left=left || (gd_gameplay.cave->player_x>mouse_cell_x);
1111 right=right || (gd_gameplay.cave->player_x<mouse_cell_x);
1112 fire=fire || mouse_cell_click;
1115 /* call the game "interrupt" to do all things. */
1116 player_move=gd_direction_from_keypress(up, down, left, right);
1117 /* tell the interrupt that 20ms has passed. */
1118 state=gd_game_main_int(20, player_move, fire, key_suicide, restart, !paused && !gd_gameplay.out_of_window, paused, fast_forward);
1120 /* the game "interrupt" gives signals to us, which we act upon: update status bar, resize the drawing area... */
1121 switch (state) {
1122 case GD_GAME_INVALID_STATE:
1123 g_assert_not_reached();
1124 break;
1126 case GD_GAME_CAVE_LOADED:
1127 gd_select_pixbuf_colors(gd_gameplay.cave->color0, gd_gameplay.cave->color1, gd_gameplay.cave->color2, gd_gameplay.cave->color3, gd_gameplay.cave->color4, gd_gameplay.cave->color5);
1128 init_mainwindow(gd_gameplay.cave);
1129 showheader();
1130 restart=FALSE; /* so we do not remember the restart key from a previous cave run */
1131 break;
1133 case GD_GAME_NO_MORE_LIVES: /* <- only used by sdl version */
1134 case GD_GAME_NOTHING:
1135 /* normally continue. */
1136 break;
1138 case GD_GAME_LABELS_CHANGED:
1139 case GD_GAME_TIMEOUT_NOW: /* <- maybe we should do something else for this */
1140 /* normal, but we are told that the labels (score, ...) might have changed. */
1141 showheader();
1142 break;
1144 case GD_GAME_STOP:
1145 gd_main_stop_game();
1146 quit_thread=TRUE;
1147 return FALSE; /* do not call again - it will be created later */
1149 case GD_GAME_GAME_OVER:
1150 gd_main_stop_game();
1151 if (gd_is_highscore(gd_caveset_data->highscore, gd_gameplay.player_score))
1152 game_over_highscore(); /* achieved a high score! */
1153 else
1154 game_over_without_highscore(); /* no high score */
1155 quit_thread=TRUE;
1156 return FALSE; /* do not call again - it will be created later */
1159 /* if fine scrolling, drawing is called at a 50hz rate. */
1160 /* if not, only at a 25hz rate */
1161 toggle=!toggle;
1163 /* do the scrolling at the given interval. */
1164 /* but only if the drawing area already exists. */
1165 if (main_window.drawing_area && (gd_fine_scroll || toggle)) {
1166 drawcave();
1167 scroll();
1170 return TRUE; /* call again */
1173 /* this is a simple wrapper which makes the main_int to be callable as an idle func. */
1174 /* when used as an idle func by the thread routine, it should run only once for every g_idle_add */
1175 static gboolean
1176 main_int_return_false_wrapper(gpointer data)
1178 main_int(data);
1179 return FALSE;
1182 /* this function will run in its own thread, and add the main int as an idle func in every 20ms */
1183 static gpointer
1184 timer_thread(gpointer data)
1186 int interval_msec=20;
1188 /* wait before we first call it */
1189 g_usleep(interval_msec*1000);
1190 while (!quit_thread) {
1191 /* add processing as an idle func */
1192 /* no need to lock the main loop context, as glib does that automatically */
1193 g_idle_add(main_int_return_false_wrapper, data);
1195 g_usleep(interval_msec*1000);
1197 return NULL;
1200 static void
1201 install_game_timer()
1203 GThread *thread;
1204 GError *error=NULL;
1206 /* remove timer, if it is installed for some reason */
1207 uninstall_game_timeout();
1209 if (!paused)
1210 gtk_window_present(GTK_WINDOW(main_window.window));
1212 #if 0
1213 if (!timer)
1214 timer=g_timer_new();
1215 #endif
1217 /* this makes the main int load the first cave, and then we do the drawing. */
1218 main_int(main_window.window);
1219 gdk_window_process_all_updates();
1220 /* after that, install timer. create a thread with higher priority than normal: */
1221 /* so its priority will be higher than the main thread, which does the drawing etc. */
1222 /* if the scheduling thread wants to do something, it gets processed first. this makes */
1223 /* the intervals more even. */
1224 quit_thread=FALSE;
1225 #ifdef G_THREADS_ENABLED
1226 thread=g_thread_create_full(timer_thread, main_window.window, 0, FALSE, FALSE, G_THREAD_PRIORITY_HIGH, &error);
1227 #else
1228 thread=NULL; /* if glib without thread support, we will use timeout source. */
1229 #endif
1230 if (!thread) {
1231 /* if unable to create thread */
1232 if (error) {
1233 g_critical("%s", error->message);
1234 g_error_free(error);
1236 /* use the main int as a timeout routine. */
1237 g_timeout_add(20, main_int, main_window.window);
1240 #if 0
1241 g_timer_start(timer);
1242 called=0;
1243 #endif
1246 static void
1247 highscore_cb(GtkWidget *widget, gpointer data)
1249 gd_show_highscore(main_window.window, NULL, FALSE, NULL, -1);
1254 static void
1255 show_errors_cb(GtkWidget *widget, gpointer data)
1257 gtk_widget_hide(main_window.error_hbox); /* if the user is presented the error list, the label is to be hidden */
1258 gd_show_errors(main_window.window);
1261 static void
1262 cave_editor_cb()
1264 gd_open_cave_editor();
1265 /* to be sure no cave is playing. */
1266 /* this will also stop music. to be called after opening editor window, so the music stops. */
1267 init_mainwindow(NULL);
1270 /* called from the menu when a recent file is activated. */
1271 static void
1272 recent_chooser_activated_cb(GtkRecentChooser *chooser, gpointer data)
1274 GtkRecentInfo *current;
1275 char *filename_utf8, *filename;
1277 current=gtk_recent_chooser_get_current_item(chooser);
1278 /* we do not support non-local files */
1279 if (!gtk_recent_info_is_local(current)) {
1280 char *display_name;
1282 display_name=gtk_recent_info_get_uri_display(current);
1283 gd_errormessage(_("GDash cannot load file from a network link."), display_name);
1284 g_free(display_name);
1285 return;
1288 /* if the edited caveset is to be saved, but user cancels */
1289 if (!gd_discard_changes(main_window.window))
1290 return;
1292 filename_utf8=gtk_recent_info_get_uri_display(current);
1293 filename=g_filename_from_utf8(filename_utf8, -1, NULL, NULL, NULL);
1294 /* ask for save first? */
1295 gd_open_caveset_in_ui(filename, gd_use_bdcff_highscore);
1297 /* things to do after loading. */
1298 gd_main_window_set_title();
1299 if (gd_has_new_error())
1300 gd_show_last_error(main_window.window);
1301 else
1302 gd_infomessage(_("Loaded caveset from file:"), filename_utf8);
1304 g_free(filename);
1305 g_free(filename_utf8);
1309 enum _replay_fields {
1310 COL_REPLAY_CAVE_POINTER,
1311 COL_REPLAY_REPLAY_POINTER,
1312 COL_REPLAY_NAME, /* cave or player name */
1313 COL_REPLAY_DATE,
1314 COL_REPLAY_SCORE,
1315 COL_REPLAY_SUCCESS,
1316 COL_REPLAY_SAVED,
1317 COL_REPLAY_COMMENT,
1318 COL_REPLAY_VISIBLE, /* set to true for replay lines, false for cave lines. so "saved" toggle and comment are not visible. */
1319 COL_REPLAY_MAX,
1322 static void
1323 show_replays_tree_view_row_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1325 GtkTreeModel *model=gtk_tree_view_get_model(view);
1326 GtkTreeIter iter;
1327 Cave *cave;
1328 GdReplay *replay;
1330 gtk_tree_model_get_iter(model, &iter, path);
1331 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1332 if (cave!=NULL && replay!=NULL)
1333 gd_main_new_game_replay(cave, replay);
1336 static void
1337 show_replays_response_cb(GtkDialog *dialog, int response_id, gpointer data)
1339 gtk_widget_destroy(GTK_WIDGET(dialog));
1342 static void
1343 replay_comment_edited(GtkCellRendererText *cell, const gchar *path_string, const gchar *new_text, gpointer data)
1345 GtkTreeModel *model=(GtkTreeModel *)data;
1346 GtkTreePath *path=gtk_tree_path_new_from_string(path_string);
1347 GtkTreeIter iter;
1348 GdReplay *replay;
1350 /* get replay. */
1351 gtk_tree_model_get_iter (model, &iter, path);
1352 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1353 /* if not available, maybe the user edited a cave line? do nothing. */
1354 if (!replay)
1355 return;
1357 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_COMMENT, new_text, -1);
1358 gd_strcpy(replay->comment, new_text);
1359 /* if this is a saved replay, now the caveset is edited. */
1360 if (replay->saved)
1361 gd_caveset_edited=TRUE;
1364 static void
1365 replay_saved_toggled(GtkCellRendererText *cell, const gchar *path_string, gpointer data)
1367 GtkTreeModel *model=(GtkTreeModel *)data;
1368 GtkTreePath *path=gtk_tree_path_new_from_string(path_string);
1369 GtkTreeIter iter;
1370 GdReplay *replay;
1372 /* get replay. */
1373 gtk_tree_model_get_iter (model, &iter, path);
1374 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1375 /* if not available, maybe the user edited a cave line? do nothing. */
1376 if (!replay)
1377 return;
1379 replay->saved=!replay->saved;
1380 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_SAVED, replay->saved, -1);
1381 /* we selected or unselected a replay for saving - the caveset is now edited. */
1382 gd_caveset_edited=TRUE;
1385 static void
1386 replay_play_button_clicked_cb(GtkWidget *widget, gpointer data)
1388 GtkTreeView *view=GTK_TREE_VIEW(data);
1389 GtkTreeSelection *selection=gtk_tree_view_get_selection(view);
1390 GtkTreeModel *model;
1391 gboolean got_selected;
1392 GtkTreeIter iter;
1393 GdReplay *replay;
1394 Cave *cave;
1396 got_selected=gtk_tree_selection_get_selected(selection, &model, &iter);
1397 if (!got_selected) /* if nothing selected, return */
1398 return;
1400 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1401 if (cave!=NULL && replay!=NULL)
1402 gd_main_new_game_replay(cave, replay);
1405 /* enables or disables play button on selection change */
1406 static void
1407 replay_tree_view_selection_changed(GtkTreeSelection *selection, gpointer data)
1409 GtkWidget *button=GTK_WIDGET(data);
1410 GtkTreeModel *model;
1411 gboolean enable;
1412 GtkTreeIter iter;
1414 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1415 GdReplay *replay;
1417 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1418 enable=replay!=NULL;
1419 } else
1420 enable=FALSE;
1422 gtk_widget_set_sensitive(button, enable);
1427 static void
1428 show_replays_cb(GtkWidget *widget, gpointer data)
1430 static GtkWidget *dialog=NULL;
1431 GtkWidget *scroll, *view, *button;
1432 GList *iter;
1433 GtkTreeStore *model;
1434 GtkCellRenderer *renderer;
1436 /* if window already open, just show it and return */
1437 if (dialog) {
1438 gtk_window_present(GTK_WINDOW(dialog));
1439 return;
1442 dialog=gtk_dialog_new_with_buttons(_("Replays"), GTK_WINDOW(main_window.window), 0, NULL);
1443 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(gtk_widget_destroyed), &dialog);
1444 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(show_replays_response_cb), NULL);
1445 gtk_window_set_default_size(GTK_WINDOW(dialog), 480, 360);
1446 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 6);
1448 gd_dialog_add_hint(GTK_DIALOG(dialog), _("Hint: When watching a replay, you can use the usual movement keys (left, right...) to "
1449 "stop the replay and immediately continue the playing of the cave yourself."));
1451 /* scrolled window to show replays tree view */
1452 scroll=gtk_scrolled_window_new(NULL, NULL);
1453 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_ETCHED_IN);
1454 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), scroll);
1456 /* create store containing replays */
1457 model=gtk_tree_store_new(COL_REPLAY_MAX, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_BOOLEAN);
1458 for (iter=gd_caveset; iter!=NULL; iter=iter->next) {
1459 GList *replayiter;
1460 Cave *cave=(Cave *)iter->data;
1462 /* if the cave has replays */
1463 if (cave->replays) {
1464 GtkTreeIter caveiter;
1466 gtk_tree_store_append(model, &caveiter, NULL);
1467 gtk_tree_store_set(model, &caveiter, COL_REPLAY_NAME, cave->name, -1);
1469 /* add each replay */
1470 for (replayiter=cave->replays; replayiter!=NULL; replayiter=replayiter->next) {
1471 GtkTreeIter riter;
1472 GdReplay *replay=(GdReplay *)replayiter->data;
1473 char score[20];
1475 /* we have to store the score as string, as for the cave lines the unset score field would also show zero */
1476 g_snprintf(score, sizeof(score), "%d", replay->score);
1477 gtk_tree_store_append(model, &riter, &caveiter);
1478 gtk_tree_store_set(model, &riter, COL_REPLAY_CAVE_POINTER, cave, COL_REPLAY_REPLAY_POINTER, replay, COL_REPLAY_NAME, replay->player_name,
1479 COL_REPLAY_DATE, replay->date, COL_REPLAY_SCORE, score, COL_REPLAY_SUCCESS, replay->success?GTK_STOCK_YES:GTK_STOCK_NO,
1480 COL_REPLAY_COMMENT, replay->comment, COL_REPLAY_SAVED, replay->saved, COL_REPLAY_VISIBLE, TRUE, -1);
1485 view=gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); /* create tree view which will show data */
1486 gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
1487 gtk_container_add(GTK_CONTAINER(scroll), view);
1489 renderer=gtk_cell_renderer_text_new();
1490 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 0, _("Name"), renderer, "text", COL_REPLAY_NAME, NULL); /* 0 = column number */
1491 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0), TRUE); /* name column expands */
1493 renderer=gtk_cell_renderer_text_new();
1494 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 1, _("Date"), renderer, "text", COL_REPLAY_DATE, NULL); /* 1 = column number */
1496 renderer=gtk_cell_renderer_pixbuf_new();
1497 g_object_set(G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, NULL);
1498 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 2, NULL, renderer, "stock-id", COL_REPLAY_SUCCESS, NULL);
1500 renderer=gtk_cell_renderer_text_new();
1501 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 3, _("Score"), renderer, "text", COL_REPLAY_SCORE, NULL);
1503 renderer=gtk_cell_renderer_text_new();
1504 g_object_set(G_OBJECT(renderer), "editable", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1505 g_signal_connect(renderer, "edited", G_CALLBACK(replay_comment_edited), model);
1506 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 4, _("Comment"), renderer, "text", COL_REPLAY_COMMENT, "visible", COL_REPLAY_VISIBLE, NULL);
1507 /* doubleclick will play the replay */
1508 g_signal_connect(G_OBJECT(view), "row-activated", G_CALLBACK(show_replays_tree_view_row_activated_cb), NULL);
1509 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 4), TRUE); /* name column expands */
1511 renderer=gtk_cell_renderer_toggle_new();
1512 g_signal_connect(renderer, "toggled", G_CALLBACK(replay_saved_toggled), model);
1513 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 5, _("Saved"), renderer, "active", COL_REPLAY_SAVED, "visible", COL_REPLAY_VISIBLE, NULL);
1515 /* play button */
1516 button=gtk_button_new_from_stock(GTK_STOCK_MEDIA_PLAY);
1517 gtk_widget_set_sensitive(button, FALSE); /* not sensitive by default. when the user selects a line, it will be enabled */
1518 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(replay_play_button_clicked_cb), view);
1519 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(view))), "changed", G_CALLBACK(replay_tree_view_selection_changed), button);
1520 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->action_area), button);
1522 /* this must be added after the play button, so it is the rightmost one */
1523 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1524 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1526 gtk_widget_show_all(dialog);
1534 /* a cave name is selected, update the text boxt with current cave's data */
1535 static void
1536 cave_info_combo_changed(GtkComboBox *widget, gpointer data)
1538 GtkTextBuffer *buffer=GTK_TEXT_BUFFER(data);
1539 GtkTextIter iter;
1540 int i;
1542 /* clear text buffer */
1543 gtk_text_buffer_set_text(buffer, "", -1);
1544 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
1546 i=gtk_combo_box_get_active(widget);
1547 if (i==0) {
1548 /* cave set data */
1549 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, gd_caveset_data->name, -1, "heading", NULL);
1550 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1552 /* show properties with title only if they are not empty string */
1553 if (!g_str_equal(gd_caveset_data->description, "")) {
1554 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1555 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->description, -1);
1556 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1558 if (!g_str_equal(gd_caveset_data->author, "")) {
1559 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1560 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->author, -1);
1561 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1563 if (!g_str_equal(gd_caveset_data->date, "")) {
1564 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1565 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->date, -1);
1566 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1568 if (!g_str_equal(gd_caveset_data->difficulty, "")) {
1569 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Diffuculty: "), -1, "name", NULL);
1570 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->difficulty, -1);
1571 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1573 if (!g_str_equal(gd_caveset_data->remark, "")) {
1574 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark: "), -1, "name", NULL);
1575 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->remark, -1);
1576 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1578 if (!g_str_equal(gd_caveset_data->notes->str, "")) {
1579 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Notes:\n"), -1, "name", NULL);
1580 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->notes->str, -1);
1581 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1584 else {
1585 /* cave data */
1586 Cave *cave=gd_return_nth_cave(i-1);
1588 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, cave->name, -1, "heading", NULL);
1589 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1591 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Type: "), -1, "name", NULL);
1592 gtk_text_buffer_insert(buffer, &iter, cave->intermission?_("Intermission"):_("Normal cave"), -1);
1593 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1595 /* show properties with title only if they are not empty string */
1596 if (!g_str_equal(cave->description, "")) {
1597 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1598 gtk_text_buffer_insert(buffer, &iter, cave->description, -1);
1599 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1601 if (!g_str_equal(cave->author, "")) {
1602 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1603 gtk_text_buffer_insert(buffer, &iter, cave->author, -1);
1604 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1606 if (!g_str_equal(cave->date, "")) {
1607 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1608 gtk_text_buffer_insert(buffer, &iter, cave->date, -1);
1609 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1611 if (!g_str_equal(cave->difficulty, "")) {
1612 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Difficulty: "), -1, "name", NULL);
1613 gtk_text_buffer_insert(buffer, &iter, cave->difficulty, -1);
1614 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1616 if (!g_str_equal(cave->remark, "")) {
1617 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark: "), -1, "name", NULL);
1618 gtk_text_buffer_insert(buffer, &iter, cave->remark, -1);
1619 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1621 if (!g_str_equal(cave->notes->str, "")) {
1622 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Notes:\n"), -1, "name", NULL);
1623 gtk_text_buffer_insert(buffer, &iter, cave->notes->str, -1);
1624 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1630 /* show info about cave or caveset */
1631 static void
1632 cave_info_cb(GtkWidget *widget, gpointer data)
1634 GtkWidget *dialog, *view, *sw, *combo;
1635 char *text;
1636 GtkTextIter iter;
1637 GtkTextBuffer *buffer;
1638 GList *citer;
1639 gboolean paused_save;
1641 /* dialog window */
1642 dialog=gtk_dialog_new_with_buttons(_("Caveset information"), GTK_WINDOW(main_window.window),
1643 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
1644 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1645 NULL);
1646 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1647 gtk_window_set_default_size(GTK_WINDOW(dialog), 360, 480);
1649 /* create a combo box. 0=caveset, 1, 2, 3... = caves */
1650 combo=gtk_combo_box_new_text();
1651 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), combo, FALSE, FALSE, 6);
1652 text=g_strdup_printf("[%s]", gd_caveset_data->name); /* caveset name = line 0 */
1653 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
1654 g_free(text);
1655 for (citer=gd_caveset; citer!=NULL; citer=citer->next) {
1656 Cave *c=citer->data;
1658 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), c->name);
1661 /* create text buffer */
1662 buffer=gtk_text_buffer_new(NULL);
1663 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
1664 gtk_text_buffer_create_tag (buffer, "heading", "weight", PANGO_WEIGHT_BOLD, "scale", PANGO_SCALE_X_LARGE, NULL);
1665 gtk_text_buffer_create_tag (buffer, "name", "weight", PANGO_WEIGHT_BOLD, NULL);
1666 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, "GDash " PACKAGE_VERSION "\n\n", -1, "heading", NULL);
1668 sw=gtk_scrolled_window_new(NULL, NULL);
1669 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1670 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1671 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
1672 view=gtk_text_view_new_with_buffer(buffer);
1673 gtk_container_add(GTK_CONTAINER (sw), view);
1674 g_object_unref(buffer);
1675 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
1676 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
1677 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD);
1678 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view), 3);
1679 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 6);
1680 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 6);
1682 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(cave_info_combo_changed), buffer);
1683 /* select current cave. if no current cave, or not in the list, g_list_index will returns -1. */
1684 /* we must add +1 anyway, so in that cave it will be 0 -> caveset info. :) */
1685 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), g_list_index(gd_caveset, gd_gameplay.original_cave)+1);
1687 gtk_widget_show_all(dialog);
1688 paused_save=paused;
1689 paused=TRUE; /* set paused game, so it stops while the users sees the message box */
1690 gtk_dialog_run(GTK_DIALOG(dialog));
1691 paused=paused_save;
1692 gtk_widget_destroy(dialog);
1698 * Creates main window
1702 static void
1703 gd_create_main_window(void)
1705 /* Menu UI */
1706 static GtkActionEntry action_entries_normal[]={
1707 {"PlayMenu", NULL, N_("_Play")},
1708 {"FileMenu", NULL, N_("_File")},
1709 {"SettingsMenu", NULL, N_("_Settings")},
1710 {"HelpMenu", NULL, N_("_Help")},
1711 {"Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK(quit_cb)},
1712 {"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_cb)},
1713 {"Errors", GTK_STOCK_DIALOG_ERROR, N_("_Error console"), NULL, NULL, G_CALLBACK(show_errors_cb)},
1714 {"Help", GTK_STOCK_HELP, NULL, NULL, NULL, G_CALLBACK(help_cb)},
1715 {"CaveInfo", GTK_STOCK_DIALOG_INFO, N_("Caveset _information"), NULL, N_("Show information about the game and its caves"), G_CALLBACK(cave_info_cb)},
1718 static GtkActionEntry action_entries_title[]={
1719 {"GamePreferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, G_CALLBACK(preferences_cb)},
1720 {"GameControlSettings", GD_ICON_KEYBOARD, N_("_Control keys"), NULL, NULL, G_CALLBACK(control_settings_cb)},
1721 {"NewGame", GTK_STOCK_MEDIA_PLAY, N_("_New game"), "<control>N", N_("Start new game"), G_CALLBACK(new_game_cb)},
1722 {"CaveEditor", GD_ICON_CAVE_EDITOR, N_("Cave _editor"), NULL, NULL, G_CALLBACK(cave_editor_cb)},
1723 {"OpenFile", GTK_STOCK_OPEN, NULL, NULL, NULL, G_CALLBACK(open_caveset_cb)},
1724 {"LoadInternal", GTK_STOCK_INDEX, N_("Load _internal game")},
1725 {"LoadRecent", GTK_STOCK_DIALOG_INFO, N_("Open _recent")},
1726 {"OpenCavesDir", GTK_STOCK_CDROM, N_("O_pen shipped"), NULL, NULL, G_CALLBACK(open_caveset_dir_cb)},
1727 {"SaveFile", GTK_STOCK_SAVE, NULL, NULL, NULL, G_CALLBACK(save_caveset_cb)},
1728 {"SaveAsFile", GTK_STOCK_SAVE_AS, NULL, NULL, NULL, G_CALLBACK(save_caveset_as_cb)},
1729 {"HighScore", GD_ICON_AWARD, N_("Hi_ghscores"), NULL, NULL, G_CALLBACK(highscore_cb)},
1730 {"ShowReplays", GD_ICON_REPLAY, N_("Show _replays"), NULL, NULL, G_CALLBACK(show_replays_cb)},
1733 static GtkActionEntry action_entries_game[]={
1734 {"TakeSnapshot", GD_ICON_SNAPSHOT, N_("_Take snapshot"), "F5", NULL, G_CALLBACK(save_snapshot_cb)},
1735 {"Restart", GD_ICON_RESTART_LEVEL, N_("Re_start level"), "Escape", N_("Restart current level"), G_CALLBACK(restart_level_cb)},
1736 {"EndGame", GTK_STOCK_STOP, N_("_End game"), "F1", N_("End current game"), G_CALLBACK(stop_game_cb)},
1739 static GtkActionEntry action_entries_snapshot[]={
1740 {"RevertToSnapshot", GTK_STOCK_UNDO, N_("_Revert to snapshot"), "F6", NULL, G_CALLBACK(load_snapshot_cb)},
1743 static GtkToggleActionEntry action_entries_toggle[]={
1744 {"PauseGame", GTK_STOCK_MEDIA_PAUSE, NULL, "space", N_("Pause game"), G_CALLBACK(toggle_pause_cb), FALSE},
1745 {"FullScreen", GTK_STOCK_FULLSCREEN, NULL, "F11", N_("Fullscreen mode during play"), G_CALLBACK(toggle_fullscreen_cb), FALSE},
1746 {"FastForward", GTK_STOCK_MEDIA_FORWARD, N_("Fast for_ward"), "<control>F", N_("Fast forward"), G_CALLBACK(toggle_fast_cb), FALSE},
1749 static const char *ui_info =
1750 "<ui>"
1751 "<menubar name='MenuBar'>"
1752 "<menu action='FileMenu'>"
1753 "<separator/>"
1754 "<menuitem action='OpenFile'/>"
1755 "<menuitem action='LoadRecent'/>"
1756 "<menuitem action='OpenCavesDir'/>"
1757 "<menuitem action='LoadInternal'/>"
1758 "<separator/>"
1759 "<menuitem action='CaveEditor'/>"
1760 "<separator/>"
1761 "<menuitem action='SaveFile'/>"
1762 "<menuitem action='SaveAsFile'/>"
1763 "<separator/>"
1764 "<menuitem action='Quit'/>"
1765 "</menu>"
1766 "<menu action='PlayMenu'>"
1767 "<menuitem action='NewGame'/>"
1768 "<menuitem action='PauseGame'/>"
1769 "<menuitem action='FastForward'/>"
1770 "<menuitem action='TakeSnapshot'/>"
1771 "<menuitem action='RevertToSnapshot'/>"
1772 "<menuitem action='Restart'/>"
1773 "<menuitem action='EndGame'/>"
1774 "<separator/>"
1775 "<menuitem action='CaveInfo'/>"
1776 "<menuitem action='HighScore'/>"
1777 "<menuitem action='ShowReplays'/>"
1778 "<separator/>"
1779 "<menuitem action='FullScreen'/>"
1780 "<menuitem action='GameControlSettings'/>"
1781 "<menuitem action='GamePreferences'/>"
1782 "</menu>"
1783 "<menu action='HelpMenu'>"
1784 "<menuitem action='Help'/>"
1785 "<separator/>"
1786 "<menuitem action='Errors'/>"
1787 "<menuitem action='About'/>"
1788 "</menu>"
1789 "</menubar>"
1791 "<toolbar name='ToolBar'>"
1792 "<toolitem action='NewGame'/>"
1793 "<toolitem action='EndGame'/>"
1794 "<toolitem action='FullScreen'/>"
1795 "<separator/>"
1796 "<toolitem action='PauseGame'/>"
1797 "<toolitem action='FastForward'/>"
1798 "<toolitem action='Restart'/>"
1799 "<separator/>"
1800 "<toolitem action='CaveInfo'/>"
1801 "</toolbar>"
1802 "</ui>";
1804 GtkWidget *vbox, *hbox, *recent_chooser;
1805 GtkRecentFilter *recent_filter;
1806 GdkPixbuf *logo;
1807 GtkUIManager *ui;
1808 int i;
1809 const gchar **names;
1810 GtkWidget *menu;
1812 logo=gd_icon();
1813 gtk_window_set_default_icon (logo);
1814 g_object_unref(logo);
1816 main_window.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
1817 gtk_window_set_default_size(GTK_WINDOW(main_window.window), 360, 300);
1818 g_signal_connect(G_OBJECT(main_window.window), "focus_out_event", G_CALLBACK(focus_out_event), NULL);
1819 g_signal_connect(G_OBJECT(main_window.window), "delete_event", G_CALLBACK(delete_event), NULL);
1820 g_signal_connect(G_OBJECT(main_window.window), "key_press_event", G_CALLBACK(keypress_event), NULL);
1821 g_signal_connect(G_OBJECT(main_window.window), "key_release_event", G_CALLBACK(keypress_event), NULL);
1823 /* vertical box */
1824 vbox=gtk_vbox_new(FALSE, 0);
1825 gtk_container_add (GTK_CONTAINER (main_window.window), vbox);
1827 /* menu */
1828 main_window.actions_normal=gtk_action_group_new("main_window.actions_normal");
1829 gtk_action_group_set_translation_domain (main_window.actions_normal, PACKAGE);
1830 gtk_action_group_add_actions (main_window.actions_normal, action_entries_normal, G_N_ELEMENTS (action_entries_normal), &main_window);
1831 gtk_action_group_add_toggle_actions (main_window.actions_normal, action_entries_toggle, G_N_ELEMENTS (action_entries_toggle), NULL);
1832 main_window.actions_title=gtk_action_group_new("main_window.actions_title");
1833 gtk_action_group_set_translation_domain (main_window.actions_title, PACKAGE);
1834 gtk_action_group_add_actions (main_window.actions_title, action_entries_title, G_N_ELEMENTS (action_entries_title), &main_window);
1835 /* make this toolbar button always have a title */
1836 g_object_set (gtk_action_group_get_action (main_window.actions_title, "NewGame"), "is_important", TRUE, NULL);
1837 main_window.actions_game=gtk_action_group_new("main_window.actions_game");
1838 gtk_action_group_set_translation_domain (main_window.actions_game, PACKAGE);
1839 gtk_action_group_add_actions (main_window.actions_game, action_entries_game, G_N_ELEMENTS (action_entries_game), &main_window);
1840 main_window.actions_snapshot=gtk_action_group_new("main_window.actions_snapshot");
1841 gtk_action_group_set_translation_domain (main_window.actions_snapshot, PACKAGE);
1842 gtk_action_group_add_actions (main_window.actions_snapshot, action_entries_snapshot, G_N_ELEMENTS (action_entries_snapshot), &main_window);
1844 /* build the ui */
1845 ui=gtk_ui_manager_new();
1846 gtk_ui_manager_insert_action_group (ui, main_window.actions_normal, 0);
1847 gtk_ui_manager_insert_action_group (ui, main_window.actions_title, 0);
1848 gtk_ui_manager_insert_action_group (ui, main_window.actions_game, 0);
1849 gtk_ui_manager_insert_action_group (ui, main_window.actions_snapshot, 0);
1850 gtk_window_add_accel_group (GTK_WINDOW(main_window.window), gtk_ui_manager_get_accel_group (ui));
1851 gtk_ui_manager_add_ui_from_string (ui, ui_info, -1, NULL);
1852 main_window.menubar=gtk_ui_manager_get_widget (ui, "/MenuBar");
1853 gtk_box_pack_start(GTK_BOX (vbox), main_window.menubar, FALSE, FALSE, 0);
1854 main_window.toolbar=gtk_ui_manager_get_widget (ui, "/ToolBar");
1855 gtk_toolbar_set_style(GTK_TOOLBAR(main_window.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1856 gtk_box_pack_start(GTK_BOX (vbox), main_window.toolbar, FALSE, FALSE, 0);
1857 gtk_toolbar_set_tooltips (GTK_TOOLBAR (main_window.toolbar), TRUE);
1859 /* make a submenu, which contains the games compiled in. */
1860 i=0;
1861 menu=gtk_menu_new();
1862 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadInternal")), menu);
1863 names=gd_caveset_get_internal_game_names ();
1864 while (names[i]) {
1865 GtkWidget *menuitem=gtk_menu_item_new_with_label (names[i]);
1867 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
1868 gtk_widget_show(menuitem);
1869 g_signal_connect (G_OBJECT(menuitem), "activate", G_CALLBACK(load_internal_cb), GINT_TO_POINTER (i));
1870 i++;
1873 /* recent file chooser */
1874 recent_chooser=gtk_recent_chooser_menu_new();
1875 recent_filter=gtk_recent_filter_new();
1876 /* gdash file extensions */
1877 for (i=0; gd_caveset_extensions[i]!=NULL; i++)
1878 gtk_recent_filter_add_pattern(recent_filter, gd_caveset_extensions[i]);
1879 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_chooser), recent_filter);
1880 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadRecent")), recent_chooser);
1881 g_signal_connect(G_OBJECT(recent_chooser), "item-activated", G_CALLBACK(recent_chooser_activated_cb), NULL);
1883 g_object_unref(ui);
1885 /* this hbox will contain the replay logo and the labels */
1886 hbox=gtk_hbox_new(FALSE, 6);
1887 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1889 /* replay logo... */
1890 main_window.replay_image_align=gtk_alignment_new(0.5, 0.5, 0, 0);
1891 gtk_box_pack_start(GTK_BOX(hbox), main_window.replay_image_align, FALSE, FALSE, 0);
1892 gtk_container_add(GTK_CONTAINER(main_window.replay_image_align), gtk_image_new_from_stock(GD_ICON_REPLAY, GTK_ICON_SIZE_LARGE_TOOLBAR));
1894 /* ... labels. */
1895 main_window.labels=gtk_vbox_new(FALSE, 0);
1896 gtk_box_pack_start_defaults(GTK_BOX(hbox), main_window.labels);
1898 /* first hbox for labels ABOVE drawing */
1899 hbox=gtk_hbox_new(FALSE, 12);
1900 gtk_box_pack_start(GTK_BOX (main_window.labels), hbox, FALSE, FALSE, 0);
1901 main_window.label_cave_name=gtk_label_new(NULL); /* NAME label */
1902 gtk_label_set_ellipsize (GTK_LABEL(main_window.label_cave_name), PANGO_ELLIPSIZE_END);
1903 gtk_box_pack_start_defaults(GTK_BOX (hbox), main_window.label_cave_name); /* NAME label */
1904 gtk_misc_set_alignment(GTK_MISC(main_window.label_cave_name), 0, 0.5);
1905 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_key3=gtk_label_new(NULL), FALSE, FALSE, 0); /* key3 label */
1906 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_key2=gtk_label_new(NULL), FALSE, FALSE, 0); /* key2 label */
1907 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_key1=gtk_label_new(NULL), FALSE, FALSE, 0); /* key1 label */
1909 /* second row of labels */
1910 hbox=gtk_hbox_new(FALSE, 12);
1911 gtk_box_pack_start(GTK_BOX(main_window.labels), hbox, FALSE, FALSE, 0);
1912 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_lives=gtk_label_new(NULL), FALSE, FALSE, 0); /* LIVES label */
1913 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_diamonds=gtk_label_new(NULL), FALSE, FALSE, 0); /* DIAMONDS label */
1914 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_skeletons=gtk_label_new(NULL), FALSE, FALSE, 0); /* SKELETONS label */
1916 /* third row */
1917 hbox=gtk_hbox_new(FALSE, 12);
1918 gtk_box_pack_start(GTK_BOX(main_window.labels), hbox, FALSE, FALSE, 0);
1919 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_value=gtk_label_new(NULL), FALSE, FALSE, 0); /* VALUE label */
1920 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_score=gtk_label_new(NULL), FALSE, FALSE, 0); /* SCORE label */
1921 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_time=gtk_label_new(NULL), FALSE, FALSE, 0); /* TIME label */
1922 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_gravity_will_change=gtk_label_new(NULL), FALSE, FALSE, 0); /* gravity label */
1924 main_window.scroll_window=gtk_scrolled_window_new(NULL, NULL);
1925 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(main_window.scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1926 gtk_box_pack_start_defaults(GTK_BOX (vbox), main_window.scroll_window);
1928 main_window.label_variables=gtk_label_new(NULL);
1929 gtk_box_pack_start(GTK_BOX (vbox), main_window.label_variables, FALSE, FALSE, 0);
1931 hbox=gtk_hbox_new(FALSE, 6);
1932 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1933 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU), FALSE, FALSE, 0);
1934 gtk_box_pack_start(GTK_BOX(hbox), main_window.error_label=gtk_label_new(NULL), FALSE, FALSE, 0); /* error label */
1935 main_window.error_hbox=hbox;
1937 gtk_widget_show_all(main_window.window);
1938 gtk_window_present(GTK_WINDOW(main_window.window));
1942 main()
1943 function
1947 main(int argc, char *argv[])
1949 int quit=0;
1950 gboolean editor=FALSE;
1951 char *gallery_filename=NULL;
1952 char *png_filename=NULL, *png_size=NULL;
1953 char *save_cave_name=NULL;
1955 GError *error=NULL;
1956 GOptionContext *context;
1957 GOptionEntry entries[]={
1958 {"editor", 'e', 0, G_OPTION_ARG_NONE, &editor, N_("Start editor")},
1959 {"gallery", 'g', 0, G_OPTION_ARG_FILENAME, &gallery_filename, N_("Save caveset in a HTML gallery")},
1960 {"png", 'p', 0, G_OPTION_ARG_FILENAME, &png_filename, N_("Save cave C, level L in a PNG image. If no cave selected, uses a random one")},
1961 {"png_size", 's', 0, G_OPTION_ARG_STRING, &png_size, N_("Set PNG image size. Default is 128x96, set to 0x0 for unscaled")},
1962 {"save", 'S', 0, G_OPTION_ARG_FILENAME, &save_cave_name, N_("Save caveset in a BDCFF file")},
1963 {"quit", 'q', 0, G_OPTION_ARG_NONE, &quit, N_("Batch mode: quit after specified tasks")},
1964 {NULL}
1967 context=gd_option_context_new();
1968 g_option_context_add_main_entries (context, entries, PACKAGE); /* gdash (gtk version) parameters */
1969 g_option_context_add_group (context, gtk_get_option_group (TRUE)); /* add gtk parameters */
1970 g_option_context_parse (context, &argc, &argv, &error);
1971 g_option_context_free (context);
1972 if (error) {
1973 g_warning("%s", error->message);
1974 g_error_free(error);
1977 /* show license? */
1978 if (gd_param_license) {
1979 char *wrapped=gd_wrap_text(gd_about_license, 72);
1981 g_print("%s", wrapped);
1982 g_free(wrapped);
1983 return 0;
1986 gd_install_log_handler();
1988 gd_settings_init_dirs();
1990 gd_load_settings();
1992 gtk_set_locale();
1993 gd_settings_set_locale();
1995 gtk_init(&argc, &argv);
1997 gd_settings_init_translation();
1999 gd_cave_init();
2000 gd_cave_db_init();
2001 gd_cave_sound_db_init();
2002 gd_c64_import_init_tables();
2004 gd_caveset_clear(); /* this also creates the default cave */
2006 gd_clear_error_flag();
2008 gd_wait_before_game_over=FALSE;
2010 /* LOAD A CAVESET FROM A FILE, OR AN INTERNAL ONE */
2011 /* if remaining arguments, they are filenames */
2012 if (gd_param_cavenames && gd_param_cavenames[0]) {
2013 /* load caveset, "ignore" errors. */
2014 if (!gd_open_caveset_in_ui(gd_param_cavenames[0], gd_use_bdcff_highscore))
2015 g_critical (_("Errors during loading caveset from file '%s'"), gd_param_cavenames[0]);
2017 else if (gd_param_internal) {
2018 /* if specified an internal caveset; if error, halt now */
2019 if (!gd_caveset_load_from_internal (gd_param_internal-1, gd_user_config_dir))
2020 g_critical (_("%d: no such internal caveset"), gd_param_internal);
2023 /* if failed or nothing requested, load default */
2024 if (gd_caveset==NULL)
2025 gd_caveset_load_from_internal (0, gd_user_config_dir);
2027 /* always load c64 graphics, as it is the builtin, and we need an icon for the theme selector. */
2028 gd_loadcells_default();
2029 gd_create_pixbuf_for_builtin_gfx();
2031 /* load other theme, if specified in config. */
2032 if (gd_theme!=NULL && !g_str_equal(gd_theme, "")) {
2033 if (!gd_loadcells_file(gd_theme)) {
2034 /* forget the theme if we are unable to load */
2035 g_warning("Cannot load theme %s, switching to built-in theme", gd_theme);
2036 g_free(gd_theme);
2037 gd_theme=NULL;
2038 gd_loadcells_default(); /* load default gfx */
2042 /* after loading cells... see if generating a gallery. */
2043 if (gallery_filename)
2044 gd_save_html (gallery_filename, NULL);
2046 /* if cave or level values given, check range */
2047 if (gd_param_cave)
2048 if (gd_param_cave<1 || gd_param_cave>=gd_caveset_count () || gd_param_level<1 || gd_param_level>5)
2049 g_critical (_("Invalid cave or level number!\n"));
2051 /* save cave png */
2052 if (png_filename) {
2053 GError *error=NULL;
2054 GdkPixbuf *pixbuf;
2055 Cave *renderedcave;
2056 unsigned int size_x=128, size_y=96; /* default size */
2058 if (gd_param_cave == 0)
2059 gd_param_cave=g_random_int_range(0, gd_caveset_count ())+1;
2061 if (png_size && (sscanf (png_size, "%ux%u", &size_x, &size_y) != 2))
2062 g_critical (_("Invalid image size: %s"), png_size);
2063 if (size_x<1 || size_y<1) {
2064 size_x=0;
2065 size_y=0;
2068 /* rendering cave for png: seed=0 */
2069 renderedcave=gd_cave_new_from_caveset (gd_param_cave-1, gd_param_level-1, 0);
2070 pixbuf=gd_drawcave_to_pixbuf(renderedcave, size_x, size_y, TRUE);
2071 if (!gdk_pixbuf_save (pixbuf, png_filename, "png", &error, "compression", "9", NULL))
2072 g_critical ("Error saving PNG image %s: %s", png_filename, error->message);
2073 g_object_unref(pixbuf);
2074 gd_cave_free (renderedcave);
2076 /* avoid starting game */
2077 gd_param_cave=0;
2080 if (save_cave_name)
2081 gd_caveset_save(save_cave_name, FALSE);
2083 /* if batch mode, quit now */
2084 if (quit)
2085 return 0;
2087 /* create window */
2088 gd_create_stock_icons();
2089 gd_create_main_window();
2090 gd_main_window_set_title();
2092 gd_sound_init();
2094 init_mainwindow(NULL);
2096 #ifdef G_THREADS_ENABLED
2097 if (!g_thread_supported())
2098 g_thread_init(NULL);
2099 #endif
2101 if (gd_param_cave) {
2102 /* if cave number given, start game */
2103 new_game(g_get_real_name(), gd_param_cave-1, gd_param_level-1);
2105 else if (editor)
2106 cave_editor_cb(NULL, &main_window);
2108 gtk_main();
2110 gd_save_highscore(gd_user_config_dir);
2112 gd_save_settings();
2114 return 0;