20090122
[gdash.git] / src / gtkmain.c
blob3ef4096d505401bf2f3943e59fc0f5e6e8ebd8df
1 /*
2 * Copyright (c) 2007, 2008, 2009, 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"
38 #include "gfxutil.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 typedef struct _gd_main_window {
47 GtkWidget *window;
48 GtkActionGroup *actions_normal, *actions_title, *actions_title_replay, *actions_game, *actions_snapshot;
49 GtkWidget *scroll_window;
50 GtkWidget *drawing_area, *title_image, *story_label; /* three things that could be drawn in the main window */
51 GdkPixmap **title_pixmaps;
52 GtkWidget *labels; /* parts of main window which have to be shown or hidden */
53 GtkWidget *label_topleft, *label_topright;
54 GtkWidget *label_bottomleft, *label_bottomright;
55 GtkWidget *label_variables;
56 GtkWidget *error_hbox, *error_label;
57 GtkWidget *menubar, *toolbar;
58 GtkWidget *replay_image_align;
60 GdGame *game;
61 } GdMainWindow;
63 static GdMainWindow main_window;
65 static gboolean key_fire_1=FALSE, key_fire_2=FALSE, key_right=FALSE, key_up=FALSE, key_down=FALSE, key_left=FALSE, key_suicide=FALSE;
66 static gboolean key_alt_status_bar=FALSE;
67 static gboolean restart; /* keys which control the game, but are handled differently than the above */
68 static int mouse_cell_x=-1, mouse_cell_y=-1, mouse_cell_click=0;
71 static GdCave *snapshot=NULL;
77 static void main_stop_game_but_maybe_highscore();
78 static void main_free_game();
83 /************************************************
85 * EVENTS
89 /* closing the window by the window manager.
90 * ask the user if he want to save the cave first, and
91 * only then do quit the main loop. */
92 static gboolean
93 main_window_delete_event (GtkWidget * widget, GdkEvent * event, gpointer data)
95 if (gd_discard_changes(main_window.window))
96 gtk_main_quit();
97 return TRUE;
100 /* keypress. key_* can be (event_type==gdk_key_press), as we
101 * connected this function to key press and key release.
103 static gboolean
104 main_window_keypress_event (GtkWidget * widget, GdkEventKey* event, gpointer data)
106 gboolean press=event->type==GDK_KEY_PRESS; /* true for press, false for release */
108 if (event->keyval==gd_gtk_key_left) {
109 key_left=press;
110 return TRUE;
112 if (event->keyval==gd_gtk_key_right) {
113 key_right=press;
114 return TRUE;
116 if (event->keyval==gd_gtk_key_up) {
117 key_up=press;
118 return TRUE;
120 if (event->keyval==gd_gtk_key_down) {
121 key_down=press;
122 return TRUE;
124 if (event->keyval==gd_gtk_key_fire_1) {
125 key_fire_1=press;
126 return TRUE;
128 if (event->keyval==GDK_Shift_L) {
129 key_alt_status_bar=press;
130 return TRUE;
132 if (event->keyval==gd_gtk_key_fire_2) {
133 key_fire_2=press;
134 return TRUE;
136 if (event->keyval==gd_gtk_key_suicide) {
137 key_suicide=press;
138 return TRUE;
141 return FALSE; /* if any other key, we did not process it. go on, let gtk handle it. */
144 /* focus leaves game play window. remember that keys are not pressed!
145 as we don't receive key released events along with focus out. */
146 static gboolean
147 main_window_focus_out_event(GtkWidget *widget, GdkEvent *event, gpointer data)
149 key_fire_1=FALSE;
150 key_fire_2=FALSE;
151 key_right=FALSE;
152 key_up=FALSE;
153 key_down=FALSE;
154 key_left=FALSE;
155 key_suicide=FALSE;
156 key_alt_status_bar=FALSE;
158 return FALSE;
161 /* for mouse play. */
162 /* mouse leaves drawing area event */
163 /* if pointer not inside window, no control of player. */
164 static gboolean
165 drawing_area_leave_event(GtkWidget * widget, GdkEventCrossing* event, gpointer data)
167 mouse_cell_x=-1;
168 mouse_cell_y=-1;
169 mouse_cell_click=0;
170 return TRUE;
173 /* mouse button press event */
174 static gboolean
175 drawing_area_button_event(GtkWidget * widget, GdkEventButton * event, gpointer data)
177 /* see if it is a click or a release. */
178 mouse_cell_click=event->type == GDK_BUTTON_PRESS;
179 return TRUE;
182 /* mouse motion event */
183 static gboolean
184 drawing_area_motion_event(GtkWidget * widget, GdkEventMotion * event, gpointer data)
186 int x, y;
187 GdkModifierType state;
189 if (event->is_hint)
190 gdk_window_get_pointer (event->window, &x, &y, &state);
191 else {
192 x=event->x;
193 y=event->y;
194 state=event->state;
197 mouse_cell_x=x / gd_cell_size_game;
198 mouse_cell_y=y / gd_cell_size_game;
199 return TRUE;
205 * draws the cave during game play. also called by the timers,
206 * and the drawing area expose event.
208 static void
209 drawing_area_draw_cave()
211 int x, y, xd, yd;
213 if (!main_window.drawing_area->window)
214 return;
215 if (!main_window.game)
216 return;
217 if (!main_window.game->cave) /* if already no cave, just return */
218 return;
219 if (!main_window.game->gfx_buffer) /* if already no cave, just return */
220 return;
222 /* 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. */
223 /* on the other hand, xd and yd are screen coordinates (of course, multiply by cell size) */
224 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
225 /* top left part of cave:
226 * x=0 xd=0
227 * |
228 *y=0 wwww|wwwwwwwwwwwwww
229 *yd=0 ----+--------------
230 * w...|o p........ visible part
231 * w...|..d...........
233 /* before drawing, process all pending events for the drawing area, so no confusion occurs. */
234 for (y=main_window.game->cave->y1, yd=0; y<=main_window.game->cave->y2; y++, yd++) {
235 for (x=main_window.game->cave->x1, xd=0; x<=main_window.game->cave->x2; x++, xd++) {
236 if (main_window.game->gfx_buffer[y][x] & GD_REDRAW) {
237 main_window.game->gfx_buffer[y][x] &= ~GD_REDRAW;
238 gdk_draw_drawable(main_window.drawing_area->window, main_window.drawing_area->style->black_gc, gd_game_pixmap(main_window.game->gfx_buffer[y][x]),
239 0, 0, xd*gd_cell_size_game, yd*gd_cell_size_game, gd_cell_size_game, gd_cell_size_game);
247 static gboolean
248 drawing_area_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
250 int x1, y1, x2, y2, xd, yd;
252 if (!widget->window)
253 return FALSE;
254 if (!main_window.game)
255 return FALSE;
256 if (!main_window.game->cave) /* if already no cave, just return */
257 return FALSE;
258 if (!main_window.game->gfx_buffer) /* if already no cave, just return */
259 return FALSE;
261 /* redraw entire area, not specific rectangles.
262 * this function gets called only when the main window gets exposed
263 * by the user removing another window. */
264 /* these are screen coordinates. */
265 x1=event->area.x/gd_cell_size_game;
266 y1=event->area.y/gd_cell_size_game;
267 x2=(event->area.x+event->area.width-1)/gd_cell_size_game;
268 y2=(event->area.y+event->area.height-1)/gd_cell_size_game;
269 /* when resizing the cave, we may get events which store the old size, if the drawing area is not yet resized. */
270 if (x1<0) x1=0;
271 if (y1<0) x1=0;
272 if (x2>=main_window.game->cave->w) x2=main_window.game->cave->w-1;
273 if (y2>=main_window.game->cave->h) y2=main_window.game->cave->h-1;
275 /* run through screen coordinates to refresh. */
276 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
277 /* top left part of cave:
278 * x=0 xd=0
279 * |
280 *y=0 wwww|wwwwwwwwwwwwww
281 *yd=0 ----+--------------
282 * w...|o p........ visible part
283 * w...|..d...........
285 /* mark all cells to be redrawn with GD_REDRAW, and then call the normal drawcave routine. */
286 for (yd=y1; yd<=y2; yd++) {
287 int y=yd+main_window.game->cave->y1;
288 for (xd=x1; xd<=x2; xd++) {
289 int x=xd+main_window.game->cave->x1;
290 if (main_window.game->gfx_buffer[y][x]!=-1)
291 main_window.game->gfx_buffer[y][x] |= GD_REDRAW;
294 drawing_area_draw_cave();
296 return TRUE;
305 * functions for the main window
306 * - fullscreen
307 * - game
308 * - title animation
312 static gboolean
313 main_window_set_fullscreen_idle_func(gpointer data)
315 gtk_window_fullscreen(GTK_WINDOW(data));
316 return FALSE;
319 /* set or unset fullscreen if necessary */
320 /* hack: gtk-win32 does not correctly handle fullscreen & removing widgets.
321 so we put fullscreening call into a low priority idle function, which will be called
322 after all window resizing & the like did take place. */
323 static void
324 main_window_set_fullscreen(gboolean ingame)
326 if (ingame && fullscreen) {
327 gtk_widget_hide(main_window.menubar);
328 gtk_widget_hide(main_window.toolbar);
329 g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc) main_window_set_fullscreen_idle_func, main_window.window, NULL);
331 else {
332 g_idle_remove_by_data(main_window.window);
333 gtk_window_unfullscreen(GTK_WINDOW(main_window.window));
334 gtk_widget_show(main_window.menubar);
335 gtk_widget_show(main_window.toolbar);
341 * init mainwindow
342 * creates title screen or drawing area
345 static gboolean
346 main_window_title_animation_func(gpointer data)
348 static int animcycle=0;
349 int count;
351 /* if title image widget does not exist for some reason, we quit now */
352 if (main_window.title_image==NULL)
353 return FALSE;
355 /* count the number of frames. */
356 count=0;
357 while (main_window.title_pixmaps[count]!=NULL)
358 count++;
360 if (gtk_window_has_toplevel_focus(GTK_WINDOW(main_window.window))) {
361 animcycle=(animcycle+1)%count;
362 gtk_image_set_from_pixmap(GTK_IMAGE(main_window.title_image), main_window.title_pixmaps[animcycle], NULL);
364 return TRUE;
367 static void
368 main_window_title_animation_remove()
370 int i;
372 g_source_remove_by_user_data(main_window_title_animation_func);
373 i=0;
374 /* free animation frames, if any */
375 if (main_window.title_pixmaps!=NULL) {
376 while (main_window.title_pixmaps[i]!=NULL) {
377 g_object_unref(main_window.title_pixmaps[i]);
378 i++;
380 g_free(main_window.title_pixmaps);
381 main_window.title_pixmaps=NULL;
385 void
386 gd_main_window_set_title_animation()
388 int w, h;
390 /* if main window does not exist, ignore. */
391 if (main_window.window==NULL)
392 return;
393 /* if exists but not showing a title image, that is an error. */
394 g_return_if_fail(main_window.title_image!=NULL);
396 main_window_title_animation_remove();
397 main_window.title_pixmaps=gd_create_title_animation();
398 /* set first frame immediately */
399 gtk_image_set_from_pixmap(GTK_IMAGE(main_window.title_image), main_window.title_pixmaps[0], NULL);
400 /* and if more frames, add animation timeout */
401 if (main_window.title_pixmaps[1]!=NULL)
402 g_timeout_add(40, main_window_title_animation_func, main_window_title_animation_func); /* 25fps animation */
404 /* resize the scrolling window so the image fits - a bit larger than the image so scroll bars do not appear*/
405 gdk_drawable_get_size(GDK_DRAWABLE(main_window.title_pixmaps[0]), &w, &h);
406 /* also some minimum size... 320x200 is some adhoc value only. */
407 gtk_widget_set_size_request(main_window.scroll_window, MAX(w, 320)+24, MAX(h, 200)+24);
409 /* with the title screen, it is usually smaller than in the game. shrink it. */
410 gtk_window_resize(GTK_WINDOW(main_window.window), 1, 1);
413 static void
414 main_window_init_title()
416 if (!main_window.title_image) {
417 /* destroy current widget in main window */
418 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
419 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
421 /* title screen */
422 main_window.title_image=gtk_image_new();
423 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.title_image);
424 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(main_window_title_animation_remove), NULL);
425 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), main_window.title_image);
427 gd_main_window_set_title_animation();
429 /* show newly created widgets */
430 gtk_widget_show_all(main_window.scroll_window);
433 /* hide cave data */
434 gtk_widget_hide(main_window.labels);
435 gtk_widget_hide(main_window.label_variables);
436 if (gd_has_new_error()) {
437 gtk_widget_show(main_window.error_hbox);
438 gtk_label_set(GTK_LABEL(main_window.error_label), ((GdErrorMessage *)(g_list_last(gd_errors)->data))->message);
439 } else {
440 gtk_widget_hide(main_window.error_hbox);
443 /* enable menus and buttons of game */
444 gtk_action_group_set_sensitive(main_window.actions_title, !gd_editor_window);
445 gtk_action_group_set_sensitive(main_window.actions_title_replay, !gd_editor_window && gd_caveset_has_replays());
446 gtk_action_group_set_sensitive(main_window.actions_game, FALSE);
447 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
448 /* if editor window exists, no music. */
449 if (gd_editor_window)
450 gd_music_stop();
451 else
452 gd_music_play_random();
453 if (gd_editor_window)
454 gtk_widget_set_sensitive(gd_editor_window, TRUE);
455 gtk_widget_hide(main_window.replay_image_align);
457 /* set or unset fullscreen if necessary */
458 main_window_set_fullscreen(FALSE);
463 static void
464 main_window_init_cave(GdCave *cave)
466 char *name_escaped;
468 g_assert(cave!=NULL);
470 /* cave drawing */
471 if (!main_window.drawing_area) {
472 GtkWidget *align;
474 /* destroy current widget in main window */
475 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
476 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
478 /* put drawing area in an alignment, so window can be any large w/o problems */
479 align=gtk_alignment_new(0.5, 0.5, 0, 0);
480 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), align);
482 main_window.drawing_area=gtk_drawing_area_new();
483 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);
484 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_press_event", G_CALLBACK(drawing_area_button_event), NULL);
485 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_release_event", G_CALLBACK(drawing_area_button_event), NULL);
486 g_signal_connect (G_OBJECT(main_window.drawing_area), "motion_notify_event", G_CALLBACK(drawing_area_motion_event), NULL);
487 g_signal_connect (G_OBJECT(main_window.drawing_area), "leave_notify_event", G_CALLBACK(drawing_area_leave_event), NULL);
488 g_signal_connect (G_OBJECT(main_window.drawing_area), "expose_event", G_CALLBACK(drawing_area_expose_event), NULL);
489 g_signal_connect (G_OBJECT(main_window.drawing_area), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.drawing_area);
490 gtk_container_add (GTK_CONTAINER (align), main_window.drawing_area);
491 if (gd_mouse_play)
492 gdk_window_set_cursor (main_window.drawing_area->window, gdk_cursor_new(GDK_CROSSHAIR));
494 /* show newly created widgets */
495 gtk_widget_show_all(main_window.scroll_window);
497 /* set the minimum size of the scroll window: 20*12 cells */
498 /* XXX adding some pixels for the scrollbars-here we add 24 */
499 gtk_widget_set_size_request(main_window.scroll_window, 20*gd_cell_size_game+24, 12*gd_cell_size_game+24);
500 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);
502 /* show cave data */
503 gtk_widget_show(main_window.labels);
504 if (main_window.game->type==GD_GAMETYPE_TEST && gd_show_test_label)
505 gtk_widget_show(main_window.label_variables);
506 else
507 gtk_widget_hide(main_window.label_variables);
508 gtk_widget_hide(main_window.error_hbox);
510 name_escaped=g_markup_escape_text(cave->name, -1);
511 /* TRANSLATORS: cave name, level x */
512 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topleft), _("<b>%s</b>, level %d"), name_escaped, cave->rendered);
513 g_free(name_escaped);
515 /* enable menus and buttons of game */
516 gtk_action_group_set_sensitive(main_window.actions_title, FALSE);
517 gtk_action_group_set_sensitive(main_window.actions_title_replay, FALSE);
518 gtk_action_group_set_sensitive(main_window.actions_game, TRUE);
519 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
521 gd_music_stop();
522 if (gd_editor_window)
523 gtk_widget_set_sensitive(gd_editor_window, FALSE);
524 gtk_widget_hide(main_window.replay_image_align); /* it will be shown if needed. */
526 /* set or unset fullscreen if necessary */
527 main_window_set_fullscreen(TRUE);
530 static void
531 main_window_init_story(GdCave *cave)
533 char *escaped_name, *escaped_story;
535 if (!main_window.story_label) {
536 /* destroy current widget in main window */
537 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
538 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
540 /* title screen */
541 main_window.story_label=gtk_label_new(NULL);
542 gtk_label_set_line_wrap(GTK_LABEL(main_window.story_label), TRUE);
543 g_signal_connect(G_OBJECT(main_window.story_label), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.story_label);
544 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), main_window.story_label);
546 /* show newly created widgets */
547 gtk_widget_show_all(main_window.scroll_window);
549 escaped_name=g_markup_escape_text(cave->name, -1);
550 escaped_story=g_markup_escape_text(cave->story->str, -1);
551 gd_label_set_markup_printf(GTK_LABEL(main_window.story_label), _("<big><b>%s</b></big>\n\n%s\n\n<i>Press fire to continue</i>"), escaped_name, escaped_story);
552 g_free(escaped_name);
553 g_free(escaped_story);
554 /* the width of the label is 320, as the windows is usually not smaller than 320x200 */
555 gtk_widget_set_size_request(main_window.story_label, 320, -1);
557 /* hide cave data */
558 gtk_widget_hide(main_window.labels);
559 gtk_widget_hide(main_window.label_variables);
560 if (gd_has_new_error()) {
561 gtk_widget_show(main_window.error_hbox);
562 gtk_label_set(GTK_LABEL(main_window.error_label), ((GdErrorMessage *)(g_list_last(gd_errors)->data))->message);
563 } else {
564 gtk_widget_hide(main_window.error_hbox);
567 /* enable menus and buttons of game */
568 gtk_action_group_set_sensitive(main_window.actions_title, FALSE);
569 gtk_action_group_set_sensitive(main_window.actions_title_replay, FALSE);
570 gtk_action_group_set_sensitive(main_window.actions_game, TRUE);
571 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
572 /* if editor window exists, no music. */
573 gd_music_stop();
574 if (gd_editor_window)
575 gtk_widget_set_sensitive(gd_editor_window, FALSE);
576 gtk_widget_hide(main_window.replay_image_align);
578 /* set or unset fullscreen if necessary */
579 main_window_set_fullscreen(TRUE);
584 /* game over, and highscore is achieved.
585 * ask for name, and if given, record it! */
586 static void
587 game_over_highscore()
589 char *text;
590 int rank;
592 text=g_strdup_printf(_("You have %d points, and achieved a highscore."), main_window.game->player_score);
593 gd_infomessage(_("Game over!"), text);
594 g_free(text);
596 /* enter to highscore table */
597 rank=gd_add_highscore(gd_caveset_data->highscore, main_window.game->player_name, main_window.game->player_score);
598 gd_show_highscore(main_window.window, NULL, FALSE, NULL, rank);
601 /* game over, but no highscore.
602 * only pops up a simple dialog box. */
603 static void
604 game_over_without_highscore()
606 gchar *text;
608 text=g_strdup_printf(_("You have %d points."), main_window.game->player_score);
609 gd_infomessage(_("Game over!"), text);
610 g_free(text);
617 static void
618 main_int_set_labels()
620 const GdCave *cave=main_window.game->cave;
621 int time;
623 /* lives reamining in game. */
624 /* not trivial, but this sometimes DOES change during the running of a cave. */
625 /* as when playing a replay, it might change from playing replay to continuing replay. */
626 switch (main_window.game->type) {
627 /* for a normal cave, show number of lives or "bonus life" if it is an intermission */
628 case GD_GAMETYPE_NORMAL:
629 if (!cave->intermission)
630 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Lives: <b>%d</b>"), main_window.game->player_lives);
631 else
632 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("<b>Bonus life</b>"));
633 break;
635 /* for other game types, the number of lives is zero. so show the game type */
636 case GD_GAMETYPE_SNAPSHOT:
637 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Continuing from <b>snapshot</b>"));
638 break;
639 case GD_GAMETYPE_TEST:
640 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("<b>Testing</b> cave"));
641 break;
642 case GD_GAMETYPE_REPLAY:
643 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Playing <b>replay</b>"));
644 break;
645 case GD_GAMETYPE_CONTINUE_REPLAY:
646 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Continuing <b>replay</b>"));
647 break;
651 /* SECOND ROW OF STATUS BAR. */
652 /* if the user is not pressing the left shift, show the normal status bar. otherwise, show the alternative. */
653 if (!key_alt_status_bar) {
654 /* NORMAL STATUS BAR */
655 /* diamond value */
656 if (cave->diamonds_needed>0)
657 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomleft), _("Diamonds: <b>%03d</b> Value: <b>%02d</b>"), cave->diamonds_collected>=cave->diamonds_needed?0:cave->diamonds_needed-cave->diamonds_collected, cave->diamond_value);
658 else
659 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomleft), _("Diamonds: <b>??""?</b> Value: <b>%02d</b>"), cave->diamond_value);/* "" to avoid C trigraph ??< */
661 /* show time & score */
662 time=gd_cave_time_show(cave, cave->time);
663 if (gd_time_min_sec)
664 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomright), "Time: <b>%02d:%02d</b> Score: <b>%06d</b>", time/60, time%60, main_window.game->player_score);
665 else
666 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomright), "Time: <b>%03d</b> Score: <b>%06d</b>", time, main_window.game->player_score);
667 } else {
668 /* ALTERNATIVE STATUS BAR */
669 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomleft), _("Keys: <b>%d</b>, <b>%d</b>, <b>%d</b>"), cave->key1, cave->key2, cave->key3);
670 gd_label_set_markup_printf(GTK_LABEL(main_window.label_bottomright), _("Skeletons: <b>%d</b> Gravity change: <b>%d</b>"), cave->skeletons_collected, gd_cave_time_show(cave, cave->gravity_will_change));
673 if (gd_editor_window && gd_show_test_label) {
674 gd_label_set_markup_printf(GTK_LABEL(main_window.label_variables),
675 _("Speed: %dms, Amoeba timer: %ds %d, %ds %d, Magic wall timer: %ds\n"
676 "Expanding wall: %s, Creatures: %ds, %s, Gravity: %s\n"
677 "Kill player: %s, Sweet eaten: %s, Diamond key: %s, Diamonds: %d"),
678 cave->speed,
679 gd_cave_time_show(cave, cave->amoeba_time),
680 cave->amoeba_state,
681 gd_cave_time_show(cave, cave->amoeba_2_time),
682 cave->amoeba_2_state,
683 gd_cave_time_show(cave, cave->magic_wall_time),
684 // XXX cave->magic_wall_state,
685 cave->expanding_wall_changed?_("vertical"):_("horizontal"),
686 gd_cave_time_show(cave, cave->creatures_direction_will_change),
687 cave->creatures_backwards?_("backwards"):_("forwards"),
688 gettext(gd_direction_get_visible_name(cave->gravity_disabled?MV_STILL:cave->gravity)),
689 cave->kill_player?_("yes"):_("no"),
690 cave->sweet_eaten?_("yes"):_("no"),
691 cave->diamond_key_collected?_("yes"):_("no"),
692 cave->diamonds_collected
698 /* THE MAIN GAME TIMER */
699 /* called at 50hz */
700 /* for the gtk version, it seems nicer if we first draw, then scroll. */
701 /* this is because there is an expose event; scrolling the "old" drawing would draw the old, and then the new. */
702 /* (this is not true for the sdl version) */
703 #if 0
704 static GTimer *timer=NULL;
705 static int called=0;
706 #endif
708 /* SCROLLING
710 * scrolls to the player during game play.
712 static void
713 main_int_scroll()
715 static int scroll_desired_x=0, scroll_desired_y=0;
716 GtkAdjustment *adjustment;
717 int scroll_center_x, scroll_center_y;
718 gboolean out_of_window=FALSE;
719 gboolean changed;
720 int i;
721 int player_x, player_y;
722 const GdCave *cave=main_window.game->cave;
723 gboolean exact_scroll=FALSE; /* to avoid compiler warning */
724 /* hystheresis size is this, multiplied by two.
725 * so player can move half the window without scrolling. */
726 int scroll_start_x=main_window.scroll_window->allocation.width/4;
727 int scroll_to_x=main_window.scroll_window->allocation.width/8;
728 int scroll_start_y=main_window.scroll_window->allocation.height/5; /* window usually smaller vertically, so let the region be also smaller than the horizontal one */
729 int scroll_to_y=main_window.scroll_window->allocation.height/10;
730 int scroll_speed;
732 /* if cave not yet rendered, return. (might be the case for 50hz scrolling */
733 if (main_window.game==NULL || main_window.game->cave==NULL)
734 return;
735 if (paused && main_window.game->cave->player_state!=GD_PL_NOT_YET)
736 return; /* no scrolling when pause button is pressed, BUT ALLOW SCROLLING when the player is not yet born */
738 /* max scrolling speed depends on the speed of the cave. */
739 /* game moves cell_size_game* 1s/cave time pixels in a second. */
740 /* scrolling moves scroll speed * 1s/scroll_time in a second. */
741 /* these should be almost equal; scrolling speed a little slower. */
742 /* that way, the player might reach the border with a small probability, */
743 /* but the scrolling will not "oscillate", ie. turn on for little intervals as it has */
744 /* caught up with the desired position. smaller is better. */
745 scroll_speed=gd_cell_size_game*(gd_fine_scroll?20:40)/cave->speed;
747 /* check player state. */
748 switch (main_window.game->cave->player_state) {
749 case GD_PL_NOT_YET:
750 exact_scroll=TRUE;
751 break;
753 case GD_PL_LIVING:
754 case GD_PL_EXITED:
755 exact_scroll=FALSE;
756 break;
758 case GD_PL_TIMEOUT:
759 case GD_PL_DIED:
760 /* do not scroll when the player is dead or cave time is over. */
761 return; /* return from function */
764 player_x=cave->player_x-cave->x1;
765 player_y=cave->player_y-cave->y1;
767 /* get the size of the window so we know where to place player.
768 * first guess is the middle of the screen.
769 * main_window.drawing_area->parent->parent is the viewport.
770 * +cellsize/2 gets the stomach of player :) so the very center */
771 scroll_center_x=player_x*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.width/2;
772 scroll_center_y=player_y*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.height/2;
774 /* HORIZONTAL */
775 /* hystheresis function.
776 * when scrolling left, always go a bit less left than player being at the middle.
777 * when scrolling right, always go a bit less to the right. */
778 adjustment=gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
779 if (exact_scroll)
780 scroll_desired_x=scroll_center_x;
781 else {
782 if (adjustment->value+scroll_start_x<scroll_center_x)
783 scroll_desired_x=scroll_center_x-scroll_to_x;
784 if (adjustment->value-scroll_start_x>scroll_center_x)
785 scroll_desired_x=scroll_center_x+scroll_to_x;
787 scroll_desired_x=CLAMP(scroll_desired_x, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
789 changed=FALSE;
790 if (adjustment->value<scroll_desired_x) {
791 for (i=0; i<scroll_speed; i++)
792 if ((int)adjustment->value<scroll_desired_x)
793 adjustment->value++;
794 changed=TRUE;
796 if (adjustment->value>scroll_desired_x) {
797 for (i=0; i<scroll_speed; i++)
798 if ((int)adjustment->value>scroll_desired_x)
799 adjustment->value--;
800 changed=TRUE;
802 if (changed)
803 gtk_adjustment_value_changed (adjustment);
805 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
806 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)
807 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
808 if (cave->player_x>=cave->x1 && cave->player_x<=cave->x2)
809 out_of_window=TRUE;
814 /* VERTICAL */
815 adjustment=gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
816 if (exact_scroll)
817 scroll_desired_y=scroll_center_y;
818 else {
819 if (adjustment->value+scroll_start_y<scroll_center_y)
820 scroll_desired_y=scroll_center_y-scroll_to_y;
821 if (adjustment->value-scroll_start_y>scroll_center_y)
822 scroll_desired_y=scroll_center_y+scroll_to_y;
824 scroll_desired_y=CLAMP(scroll_desired_y, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
826 changed=FALSE;
827 if (adjustment->value<scroll_desired_y) {
828 for (i=0; i<scroll_speed; i++)
829 if ((int)adjustment->value<scroll_desired_y)
830 adjustment->value++;
831 changed=TRUE;
833 if (adjustment->value > scroll_desired_y) {
834 for (i=0; i<scroll_speed; i++)
835 if ((int)adjustment->value>scroll_desired_y)
836 adjustment->value--;
837 changed=TRUE;
839 if (changed)
840 gtk_adjustment_value_changed(adjustment);
842 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
843 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)
844 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
845 if (cave->player_y>=cave->y1 && cave->player_y<=cave->y2)
846 out_of_window=TRUE;
848 /* remember if player is visible inside window */
849 main_window.game->out_of_window=out_of_window;
851 /* 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. */
852 if (main_window.game->cave->player_state==GD_PL_NOT_YET)
853 main_window.game->out_of_window=FALSE;
855 gdk_window_process_updates(main_window.drawing_area->window, TRUE);
858 /* the timing thread runs in a separate thread. this variable is set to true,
859 * then the function exits (and also the thread.) */
860 static gboolean main_int_quit_thread;
862 static gboolean
863 main_int(gpointer data)
865 static gboolean toggle=FALSE; /* value irrelevant */
866 int up, down, left, right;
867 GdDirection player_move;
868 gboolean fire;
869 GdGameState state;
871 #if 0
872 called++;
873 if (called%100==0)
874 g_message("%8d. call, avg %gms", called, g_timer_elapsed(timer, NULL)*1000/called);
875 #endif
877 if (main_window.game->type==GD_GAMETYPE_REPLAY)
878 gtk_widget_show(main_window.replay_image_align);
879 else
880 gtk_widget_hide(main_window.replay_image_align);
882 up=key_up;
883 down=key_down;
884 left=key_left;
885 right=key_right;
886 fire=key_fire_1 || key_fire_2;
888 /* compare mouse coordinates to player coordinates, and make up movements */
889 if (gd_mouse_play && mouse_cell_x>=0) {
890 down=down || (main_window.game->cave->player_y<mouse_cell_y);
891 up=up || (main_window.game->cave->player_y>mouse_cell_y);
892 left=left || (main_window.game->cave->player_x>mouse_cell_x);
893 right=right || (main_window.game->cave->player_x<mouse_cell_x);
894 fire=fire || mouse_cell_click;
897 /* call the game "interrupt" to do all things. */
898 player_move=gd_direction_from_keypress(up, down, left, right);
899 /* tell the interrupt that 20ms has passed. */
900 state=gd_game_main_int(main_window.game, 20, player_move, fire, key_suicide, restart, !paused && !main_window.game->out_of_window, paused, fast_forward);
902 /* the game "interrupt" gives signals to us, which we act upon: update status bar, resize the drawing area... */
903 switch (state) {
904 case GD_GAME_INVALID_STATE:
905 g_assert_not_reached();
906 break;
908 case GD_GAME_SHOW_STORY:
909 main_window_init_story(main_window.game->cave);
910 main_int_set_labels();
911 break;
913 case GD_GAME_CAVE_LOADED:
914 gd_select_pixbuf_colors(main_window.game->cave->color0, main_window.game->cave->color1, main_window.game->cave->color2, main_window.game->cave->color3, main_window.game->cave->color4, main_window.game->cave->color5);
915 main_window_init_cave(main_window.game->cave);
916 main_int_set_labels();
917 restart=FALSE; /* so we do not remember the restart key from a previous cave run */
918 break;
920 case GD_GAME_NO_MORE_LIVES: /* <- only used by sdl version */
921 case GD_GAME_NOTHING:
922 /* normally continue. */
923 break;
925 case GD_GAME_LABELS_CHANGED:
926 case GD_GAME_TIMEOUT_NOW: /* <- maybe we should do something else for this */
927 /* normal, but we are told that the labels (score, ...) might have changed. */
928 main_int_set_labels();
929 break;
931 case GD_GAME_STOP:
932 gd_main_stop_game();
933 return FALSE; /* do not call again - it will be created later */
935 case GD_GAME_GAME_OVER:
936 main_stop_game_but_maybe_highscore();
937 if (gd_is_highscore(gd_caveset_data->highscore, main_window.game->player_score))
938 game_over_highscore(); /* achieved a high score! */
939 else
940 game_over_without_highscore(); /* no high score */
941 main_free_game();
942 return FALSE; /* do not call again - it will be created later */
945 /* if drawing area already exists, draw cave. */
946 /* remember that the drawings are cached, so if we did no change, this will barely do anything - so will not slow down. */
947 if (main_window.drawing_area)
948 drawing_area_draw_cave();
949 /* do the scrolling at the given interval. */
950 /* but only if the drawing area already exists. */
951 /* if fine scrolling, drawing is called at a 50hz rate. */
952 /* if not, only at a 25hz rate */
953 toggle=!toggle;
954 if (main_window.drawing_area && (gd_fine_scroll || toggle)) {
955 main_int_scroll();
958 return TRUE; /* call again */
961 /* this is a simple wrapper which makes the main_int to be callable as an idle func. */
962 /* when used as an idle func by the thread routine, it should run only once for every g_idle_add */
963 static gboolean
964 main_int_return_false_wrapper(gpointer data)
966 main_int(data);
967 return FALSE;
970 /* this function will run in its own thread, and add the main int as an idle func in every 20ms */
971 static gpointer
972 main_int_timer_thread(gpointer data)
974 int interval_msec=20;
976 /* wait before we first call it */
977 g_usleep(interval_msec*1000);
978 while (!main_int_quit_thread) {
979 /* add processing as an idle func */
980 /* no need to lock the main loop context, as glib does that automatically */
981 g_idle_add(main_int_return_false_wrapper, data);
983 g_usleep(interval_msec*1000);
985 return NULL;
989 uninstall game timers, if any installed.
991 static void
992 main_int_uninstall_timer()
994 main_int_quit_thread=TRUE;
995 /* remove timeout associated to game play */
996 while (g_source_remove_by_user_data (main_window.window)) {
997 /* nothing */
1002 static void
1003 main_int_install_timer()
1005 GThread *thread;
1006 GError *error=NULL;
1008 /* remove timer, if it is installed for some reason */
1009 main_int_uninstall_timer();
1011 if (!paused)
1012 gtk_window_present(GTK_WINDOW(main_window.window));
1014 #if 0
1015 if (!timer)
1016 timer=g_timer_new();
1017 #endif
1019 /* this makes the main int load the first cave, and then we do the drawing. */
1020 main_int(main_window.window);
1021 gdk_window_process_all_updates();
1022 /* after that, install timer. create a thread with higher priority than normal: */
1023 /* so its priority will be higher than the main thread, which does the drawing etc. */
1024 /* if the scheduling thread wants to do something, it gets processed first. this makes */
1025 /* the intervals more even. */
1026 main_int_quit_thread=FALSE;
1027 #ifdef G_THREADS_ENABLED
1028 thread=g_thread_create_full(main_int_timer_thread, main_window.window, 0, FALSE, FALSE, G_THREAD_PRIORITY_HIGH, &error);
1029 #else
1030 thread=NULL; /* if glib without thread support, we will use timeout source. */
1031 #endif
1032 if (!thread) {
1033 /* if unable to create thread */
1034 if (error) {
1035 g_critical("%s", error->message);
1036 g_error_free(error);
1038 /* use the main int as a timeout routine. */
1039 g_timeout_add(20, main_int, main_window.window);
1042 #if 0
1043 g_timer_start(timer);
1044 called=0;
1045 #endif
1048 static void
1049 main_stop_game_but_maybe_highscore()
1051 main_int_uninstall_timer();
1052 gd_sound_off(); /* hack for game over dialog */
1054 main_window_init_title();
1055 /* if editor is active, go back to its window. */
1056 if (gd_editor_window)
1057 gtk_window_present(GTK_WINDOW(gd_editor_window));
1060 static void
1061 main_free_game()
1063 if (main_window.game) {
1064 gd_game_free(main_window.game);
1065 main_window.game=NULL;
1069 void
1070 gd_main_stop_game()
1072 main_stop_game_but_maybe_highscore();
1073 main_free_game();
1078 /* this starts a new game */
1079 static void
1080 main_new_game(const char *player_name, const int cave, const int level)
1082 if (main_window.game)
1083 gd_main_stop_game();
1085 main_window.game=gd_game_new(player_name, cave, level);
1086 main_int_install_timer();
1090 static void
1091 main_new_game_snapshot(GdCave *snapshot)
1093 if (main_window.game)
1094 gd_main_stop_game();
1096 main_window.game=gd_game_new_snapshot(snapshot);
1097 main_int_install_timer();
1100 /* the new game for testing a level is global, not static.
1101 * it is used by the editor. */
1102 void
1103 gd_main_new_game_test(GdCave *test, int level)
1105 if (main_window.game)
1106 gd_main_stop_game();
1108 main_window.game=gd_game_new_test(test, level);
1109 main_int_install_timer();
1113 static void
1114 main_new_game_replay(GdCave *cave, GdReplay *replay)
1116 if (main_window.game)
1117 gd_main_stop_game();
1119 main_window.game=gd_game_new_replay(cave, replay);
1120 main_int_install_timer();
1131 * set the main window title from the caveset name.
1132 * made global, as the editor might ocassionally call it.
1135 void
1136 gd_main_window_set_title()
1138 if (!g_str_equal(gd_caveset_data->name, "")) {
1139 char *text;
1141 text=g_strdup_printf("GDash - %s", gd_caveset_data->name);
1142 gtk_window_set_title (GTK_WINDOW(main_window.window), text);
1143 g_free (text);
1145 else
1146 gtk_window_set_title (GTK_WINDOW(main_window.window), "GDash");
1153 /**********************************-
1155 * CALLBACKS
1159 static void
1160 help_cb(GtkWidget * widget, gpointer data)
1162 gd_show_game_help (((GdMainWindow *)data)->window);
1165 static void
1166 preferences_cb(GtkWidget *widget, gpointer data)
1168 gd_preferences(((GdMainWindow *)data)->window);
1171 static void
1172 control_settings_cb(GtkWidget *widget, gpointer data)
1174 gd_control_settings(((GdMainWindow *)data)->window);
1177 static void
1178 quit_cb(GtkWidget * widget, const gpointer data)
1180 if (gd_discard_changes(main_window.window))
1181 gtk_main_quit ();
1184 static void
1185 stop_game_cb(GtkWidget *widget, gpointer data)
1187 gd_main_stop_game();
1190 static void
1191 save_snapshot_cb(GtkWidget * widget, gpointer data)
1193 if (snapshot)
1194 gd_cave_free(snapshot);
1196 snapshot=gd_create_snapshot(main_window.game);
1197 gtk_action_group_set_sensitive (main_window.actions_snapshot, snapshot!=NULL);
1200 static void
1201 load_snapshot_cb(GtkWidget * widget, gpointer data)
1203 g_return_if_fail(snapshot!=NULL);
1204 main_new_game_snapshot(snapshot);
1207 /* restart level button clicked */
1208 static void
1209 restart_level_cb(GtkWidget * widget, gpointer data)
1211 g_return_if_fail(main_window.game!=NULL);
1212 g_return_if_fail(main_window.game->cave!=NULL);
1213 /* sets the restart variable, which will be interpreted by the iterate routine */
1214 restart=TRUE;
1217 static void
1218 about_cb(GtkWidget *widget, gpointer data)
1220 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);
1223 static void
1224 open_caveset_cb(GtkWidget * widget, gpointer data)
1226 gd_open_caveset(main_window.window, NULL);
1227 gd_main_window_set_title();
1230 static void
1231 open_caveset_dir_cb(GtkWidget * widget, gpointer data)
1233 gd_open_caveset(main_window.window, gd_system_caves_dir);
1234 gd_main_window_set_title();
1237 static void
1238 save_caveset_as_cb(GtkWidget * widget, gpointer data)
1240 gd_save_caveset_as(main_window.window);
1243 static void
1244 save_caveset_cb(GtkWidget * widget, gpointer data)
1246 gd_save_caveset(main_window.window);
1249 /* load internal game from the executable. those are inlined in caveset.c. */
1250 static void
1251 load_internal_cb(GtkWidget * widget, gpointer data)
1253 gd_load_internal(main_window.window, GPOINTER_TO_INT(data));
1254 gd_main_window_set_title();
1257 static void
1258 highscore_cb(GtkWidget *widget, gpointer data)
1260 gd_show_highscore(main_window.window, NULL, FALSE, NULL, -1);
1265 static void
1266 show_errors_cb(GtkWidget *widget, gpointer data)
1268 gtk_widget_hide(main_window.error_hbox); /* if the user is presented the error list, the label is to be hidden */
1269 gd_show_errors(main_window.window);
1272 static void
1273 cave_editor_cb()
1275 gd_open_cave_editor();
1276 /* to be sure no cave is playing. */
1277 /* this will also stop music. to be called after opening editor window, so the music stops. */
1278 main_window_init_title();
1281 /* called from the menu when a recent file is activated. */
1282 static void
1283 recent_chooser_activated_cb(GtkRecentChooser *chooser, gpointer data)
1285 GtkRecentInfo *current;
1286 char *filename_utf8, *filename;
1288 current=gtk_recent_chooser_get_current_item(chooser);
1289 /* we do not support non-local files */
1290 if (!gtk_recent_info_is_local(current)) {
1291 char *display_name;
1293 display_name=gtk_recent_info_get_uri_display(current);
1294 gd_errormessage(_("GDash cannot load file from a network link."), display_name);
1295 g_free(display_name);
1296 return;
1299 /* if the edited caveset is to be saved, but user cancels */
1300 if (!gd_discard_changes(main_window.window))
1301 return;
1303 filename_utf8=gtk_recent_info_get_uri_display(current);
1304 filename=g_filename_from_utf8(filename_utf8, -1, NULL, NULL, NULL);
1305 /* ask for save first? */
1306 gd_open_caveset_in_ui(filename, gd_use_bdcff_highscore);
1308 /* things to do after loading. */
1309 gd_main_window_set_title();
1310 if (gd_has_new_error())
1311 gd_show_last_error(main_window.window);
1312 else
1313 gd_infomessage(_("Loaded caveset from file:"), filename_utf8);
1315 g_free(filename);
1316 g_free(filename_utf8);
1321 static void
1322 toggle_pause_cb(GtkWidget * widget, gpointer data)
1324 paused=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
1326 if (paused)
1327 gd_sound_off();
1330 static void
1331 toggle_fullscreen_cb (GtkWidget * widget, gpointer data)
1333 fullscreen=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
1334 main_window_set_fullscreen(main_window.game!=NULL); /* we do not exactly know if in game, but try to guess */
1337 static void
1338 toggle_fast_cb (GtkWidget * widget, gpointer data)
1340 fast_forward=gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (widget));
1352 * START NEW GAME DIALOG
1354 * show a dialog to the user so he can select the cave to start game at.
1358 typedef struct _new_game_dialog {
1359 GtkWidget *dialog;
1360 GtkWidget *combo_cave;
1361 GtkWidget *spin_level;
1362 GtkWidget *entry_name;
1364 GtkWidget *image;
1365 } NewGameDialog;
1367 /* keypress. key_* can be event_type==gdk_key_press, as we
1368 connected this function to key press and key release.
1370 static gboolean
1371 new_game_keypress_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1373 NewGameDialog *jump_dialog=(NewGameDialog *)data;
1374 int level=gtk_range_get_value (GTK_RANGE(jump_dialog->spin_level));
1376 switch (event->keyval) {
1377 case GDK_Left:
1378 level--;
1379 if (level<1)
1380 level=1;
1381 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
1382 return TRUE;
1383 case GDK_Right:
1384 level++;
1385 if (level>5)
1386 level=5;
1387 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
1388 return TRUE;
1389 case GDK_Return:
1390 gtk_dialog_response(GTK_DIALOG(jump_dialog->dialog), GTK_RESPONSE_ACCEPT);
1391 return TRUE;
1393 return FALSE; /* if any other key, we did not process it. go on, let gtk handle it. */
1397 /* update pixbuf */
1398 static void
1399 new_game_update_preview(GtkWidget *widget, gpointer data)
1401 NewGameDialog *jump_dialog=(NewGameDialog *)data;
1402 GdkPixbuf *cave_image;
1403 GdCave *cave;
1405 /* loading cave, draw cave and scale to specified size. seed=0 */
1406 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);
1407 cave_image=gd_drawcave_to_pixbuf(cave, 320, 240, TRUE, TRUE);
1408 gtk_image_set_from_pixbuf(GTK_IMAGE (jump_dialog->image), cave_image);
1409 g_object_unref(cave_image);
1411 /* freeing temporary cave data */
1412 gd_cave_free(cave);
1415 static void
1416 new_game_cb (const GtkWidget * widget, const gpointer data)
1418 static GdString player_name="";
1419 GtkWidget *table, *expander, *eventbox;
1420 NewGameDialog jump_dialog;
1421 GtkCellRenderer *renderer;
1422 GtkListStore *store;
1423 GList *iter;
1425 /* check if caveset is empty! */
1426 if (gd_caveset_count()==0) {
1427 gd_warningmessage(_("There are no caves in this cave set!"), NULL);
1428 return;
1431 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);
1432 gtk_dialog_set_default_response (GTK_DIALOG(jump_dialog.dialog), GTK_RESPONSE_ACCEPT);
1433 gtk_window_set_resizable (GTK_WINDOW(jump_dialog.dialog), FALSE);
1435 table=gtk_table_new(0, 0, FALSE);
1436 gtk_box_pack_start(GTK_BOX (GTK_DIALOG(jump_dialog.dialog)->vbox), table, FALSE, FALSE, 0);
1437 gtk_container_set_border_width(GTK_CONTAINER (table), 6);
1438 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1439 gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1441 /* name, which will be used for highscore & the like */
1442 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Name:")), 0, 1, 0, 1);
1443 if (g_str_equal(player_name, ""))
1444 gd_strcpy(player_name, g_get_real_name());
1445 jump_dialog.entry_name=gtk_entry_new();
1446 /* little inconsistency below: max length has unicode characters, while gdstring will have utf-8.
1447 however this does not make too much difference */
1448 gtk_entry_set_max_length(GTK_ENTRY(jump_dialog.entry_name), sizeof(GdString));
1449 gtk_entry_set_activates_default(GTK_ENTRY(jump_dialog.entry_name), TRUE);
1450 gtk_entry_set_text(GTK_ENTRY(jump_dialog.entry_name), player_name);
1451 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.entry_name, 1, 2, 0, 1);
1453 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Cave:")), 0, 1, 1, 2);
1455 /* store of caves: cave pointer, cave name, selectable */
1456 store=gtk_list_store_new(3, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1457 for (iter=gd_caveset; iter; iter=g_list_next (iter)) {
1458 GdCave *cave=iter->data;
1459 GtkTreeIter treeiter;
1461 gtk_list_store_insert_with_values (store, &treeiter, -1, 0, iter->data, 1, cave->name, 2, cave->selectable || gd_all_caves_selectable, -1);
1463 jump_dialog.combo_cave=gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
1464 g_object_unref(store);
1466 renderer=gtk_cell_renderer_text_new();
1467 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, TRUE);
1468 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, "text", 1, "sensitive", 2, NULL);
1469 /* we put the combo in an event box, so we can receive keypresses on our own */
1470 eventbox=gtk_event_box_new();
1471 gtk_container_add(GTK_CONTAINER(eventbox), jump_dialog.combo_cave);
1472 gtk_table_attach_defaults(GTK_TABLE(table), eventbox, 1, 2, 1, 2);
1474 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Level:")), 0, 1, 2, 3);
1475 jump_dialog.spin_level=gtk_hscale_new_with_range(1.0, 5.0, 1.0);
1476 gtk_range_set_increments(GTK_RANGE(jump_dialog.spin_level), 1.0, 1.0);
1477 gtk_scale_set_value_pos(GTK_SCALE(jump_dialog.spin_level), GTK_POS_LEFT);
1478 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.spin_level, 1, 2, 2, 3);
1480 g_signal_connect(G_OBJECT(jump_dialog.combo_cave), "changed", G_CALLBACK(new_game_update_preview), &jump_dialog);
1481 gtk_widget_add_events(eventbox, GDK_KEY_PRESS_MASK);
1482 g_signal_connect(G_OBJECT(eventbox), "key_press_event", G_CALLBACK(new_game_keypress_event), &jump_dialog);
1483 g_signal_connect(G_OBJECT(jump_dialog.spin_level), "value-changed", G_CALLBACK(new_game_update_preview), &jump_dialog);
1485 /* this allows the user to select if he wants to see a preview of the cave */
1486 expander=gtk_expander_new(_("Preview"));
1487 gtk_expander_set_expanded(GTK_EXPANDER (expander), gd_show_preview);
1488 gtk_table_attach_defaults(GTK_TABLE(table), expander, 0, 2, 3, 4);
1489 jump_dialog.image=gtk_image_new();
1490 gtk_container_add(GTK_CONTAINER (expander), jump_dialog.image);
1492 gtk_widget_show_all(jump_dialog.dialog);
1493 gtk_widget_grab_focus(jump_dialog.combo_cave);
1494 gtk_editable_select_region(GTK_EDITABLE(jump_dialog.entry_name), 0, 0);
1495 /* set default and also trigger redrawing */
1496 gtk_combo_box_set_active(GTK_COMBO_BOX(jump_dialog.combo_cave), gd_caveset_last_selected);
1497 gtk_range_set_value(GTK_RANGE(jump_dialog.spin_level), 1);
1499 if (gtk_dialog_run (GTK_DIALOG(jump_dialog.dialog))==GTK_RESPONSE_ACCEPT) {
1500 gd_strcpy(player_name, gtk_entry_get_text(GTK_ENTRY(jump_dialog.entry_name)));
1501 gd_caveset_last_selected=gtk_combo_box_get_active(GTK_COMBO_BOX(jump_dialog.combo_cave));
1502 gd_caveset_last_selected_level=gtk_range_get_value(GTK_RANGE(jump_dialog.spin_level))-1;
1503 main_new_game (player_name, gd_caveset_last_selected, gd_caveset_last_selected_level);
1505 gd_show_preview=gtk_expander_get_expanded(GTK_EXPANDER(expander)); /* remember expander state-even if cancel pressed */
1506 gtk_widget_destroy(jump_dialog.dialog);
1511 enum _replay_fields {
1512 COL_REPLAY_CAVE_POINTER,
1513 COL_REPLAY_REPLAY_POINTER,
1514 COL_REPLAY_NAME, /* cave or player name */
1515 COL_REPLAY_LEVEL, /* level the replay is played on */
1516 COL_REPLAY_DATE,
1517 COL_REPLAY_SCORE,
1518 COL_REPLAY_SUCCESS,
1519 COL_REPLAY_SAVED,
1520 COL_REPLAY_COMMENT,
1521 COL_REPLAY_VISIBLE, /* set to true for replay lines, false for cave lines. so "saved" toggle and comment are not visible. */
1522 COL_REPLAY_MAX,
1525 static void
1526 show_replays_tree_view_row_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1528 GtkTreeModel *model=gtk_tree_view_get_model(view);
1529 GtkTreeIter iter;
1530 GdCave *cave;
1531 GdReplay *replay;
1533 gtk_tree_model_get_iter(model, &iter, path);
1534 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1535 if (cave!=NULL && replay!=NULL)
1536 main_new_game_replay(cave, replay);
1539 static void
1540 show_replays_dialog_response(GtkDialog *dialog, int response_id, gpointer data)
1542 gtk_widget_destroy(GTK_WIDGET(dialog));
1545 static void
1546 show_replays_comment_edited(GtkCellRendererText *cell, const gchar *path_string, const gchar *new_text, gpointer data)
1548 GtkTreeModel *model=(GtkTreeModel *)data;
1549 GtkTreePath *path=gtk_tree_path_new_from_string(path_string);
1550 GtkTreeIter iter;
1551 GdReplay *replay;
1553 /* get replay. */
1554 gtk_tree_model_get_iter (model, &iter, path);
1555 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1556 /* if not available, maybe the user edited a cave line? do nothing. */
1557 if (!replay)
1558 return;
1560 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_COMMENT, new_text, -1);
1561 gd_strcpy(replay->comment, new_text);
1562 /* if this is a saved replay, now the caveset is edited. */
1563 if (replay->saved)
1564 gd_caveset_edited=TRUE;
1567 static void
1568 show_replays_saved_toggled(GtkCellRendererText *cell, const gchar *path_string, gpointer data)
1570 GtkTreeModel *model=(GtkTreeModel *)data;
1571 GtkTreePath *path=gtk_tree_path_new_from_string(path_string);
1572 GtkTreeIter iter;
1573 GdReplay *replay;
1575 /* get replay. */
1576 gtk_tree_model_get_iter (model, &iter, path);
1577 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1578 /* if not available, maybe the user edited a cave line? do nothing. */
1579 if (!replay)
1580 return;
1582 replay->saved=!replay->saved;
1583 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_SAVED, replay->saved, -1);
1584 /* we selected or unselected a replay for saving - the caveset is now edited. */
1585 gd_caveset_edited=TRUE;
1588 static void
1589 show_replays_play_button_clicked(GtkWidget *widget, gpointer data)
1591 GtkTreeView *view=GTK_TREE_VIEW(data);
1592 GtkTreeSelection *selection=gtk_tree_view_get_selection(view);
1593 GtkTreeModel *model;
1594 gboolean got_selected;
1595 GtkTreeIter iter;
1596 GdReplay *replay;
1597 GdCave *cave;
1599 got_selected=gtk_tree_selection_get_selected(selection, &model, &iter);
1600 if (!got_selected) /* if nothing selected, return */
1601 return;
1603 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1604 if (cave!=NULL && replay!=NULL)
1605 main_new_game_replay(cave, replay);
1608 /* enables or disables play button on selection change */
1609 static void
1610 show_replays_tree_view_selection_changed(GtkTreeSelection *selection, gpointer data)
1612 GtkWidget *button=GTK_WIDGET(data);
1613 GtkTreeModel *model;
1614 gboolean enable;
1615 GtkTreeIter iter;
1617 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1618 GdReplay *replay;
1620 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1621 enable=replay!=NULL;
1622 } else
1623 enable=FALSE;
1625 gtk_widget_set_sensitive(button, enable);
1630 static void
1631 show_replays_cb(GtkWidget *widget, gpointer data)
1633 static GtkWidget *dialog=NULL;
1634 GtkWidget *scroll, *view, *button;
1635 GList *iter;
1636 GtkTreeStore *model;
1637 GtkCellRenderer *renderer;
1639 /* if window already open, just show it and return */
1640 if (dialog) {
1641 gtk_window_present(GTK_WINDOW(dialog));
1642 return;
1645 dialog=gtk_dialog_new_with_buttons(_("Replays"), GTK_WINDOW(main_window.window), 0, NULL);
1646 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(gtk_widget_destroyed), &dialog);
1647 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(show_replays_dialog_response), NULL);
1648 gtk_window_set_default_size(GTK_WINDOW(dialog), 480, 360);
1650 gd_dialog_add_hint(GTK_DIALOG(dialog), _("Hint: When watching a replay, you can use the usual movement keys (left, right...) to "
1651 "stop the replay and immediately continue the playing of the cave yourself."));
1653 /* scrolled window to show replays tree view */
1654 scroll=gtk_scrolled_window_new(NULL, NULL);
1655 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_ETCHED_IN);
1656 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scroll, TRUE, TRUE, 6);
1658 /* create store containing replays */
1659 model=gtk_tree_store_new(COL_REPLAY_MAX, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_BOOLEAN);
1660 for (iter=gd_caveset; iter!=NULL; iter=iter->next) {
1661 GList *replayiter;
1662 GdCave *cave=(GdCave *)iter->data;
1664 /* if the cave has replays */
1665 if (cave->replays) {
1666 GtkTreeIter caveiter;
1668 gtk_tree_store_append(model, &caveiter, NULL);
1669 gtk_tree_store_set(model, &caveiter, COL_REPLAY_NAME, cave->name, -1);
1671 /* add each replay */
1672 for (replayiter=cave->replays; replayiter!=NULL; replayiter=replayiter->next) {
1673 GtkTreeIter riter;
1674 GdReplay *replay=(GdReplay *)replayiter->data;
1675 char score[20];
1676 const char *imagestock;
1678 /* we have to store the score as string, as for the cave lines the unset score field would also show zero */
1679 g_snprintf(score, sizeof(score), "%d", replay->score);
1680 gtk_tree_store_append(model, &riter, &caveiter);
1681 if (replay->wrong_checksum)
1682 imagestock=GTK_STOCK_DIALOG_ERROR;
1683 else
1684 imagestock=replay->success?GTK_STOCK_YES:GTK_STOCK_NO;
1685 gtk_tree_store_set(model, &riter, COL_REPLAY_CAVE_POINTER, cave, COL_REPLAY_REPLAY_POINTER, replay, COL_REPLAY_NAME, replay->player_name,
1686 COL_REPLAY_LEVEL, replay->level+1,
1687 COL_REPLAY_DATE, replay->date, COL_REPLAY_SCORE, score, COL_REPLAY_SUCCESS, imagestock,
1688 COL_REPLAY_COMMENT, replay->comment, COL_REPLAY_SAVED, replay->saved, COL_REPLAY_VISIBLE, TRUE, -1);
1693 view=gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); /* create tree view which will show data */
1694 gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
1695 gtk_container_add(GTK_CONTAINER(scroll), view);
1697 renderer=gtk_cell_renderer_text_new();
1698 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 0, _("Name"), renderer, "text", COL_REPLAY_NAME, NULL); /* 0 = column number */
1699 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0), TRUE); /* name column expands */
1701 renderer=gtk_cell_renderer_text_new();
1702 /* TRANSLATORS: "Lvl" here stands for Level. Some shorthand should be used.*/
1703 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 1, _("Lvl"), renderer, "text", COL_REPLAY_LEVEL, "visible", COL_REPLAY_VISIBLE, NULL); /* 0 = column number */
1704 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0), TRUE); /* name column expands */
1706 renderer=gtk_cell_renderer_text_new();
1707 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 2, _("Date"), renderer, "text", COL_REPLAY_DATE, NULL); /* 1 = column number */
1709 renderer=gtk_cell_renderer_pixbuf_new();
1710 g_object_set(G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, NULL);
1711 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 3, NULL, renderer, "stock-id", COL_REPLAY_SUCCESS, NULL);
1713 renderer=gtk_cell_renderer_text_new();
1714 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 4, _("Score"), renderer, "text", COL_REPLAY_SCORE, NULL);
1716 renderer=gtk_cell_renderer_text_new();
1717 g_object_set(G_OBJECT(renderer), "editable", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1718 g_signal_connect(renderer, "edited", G_CALLBACK(show_replays_comment_edited), model);
1719 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 5, _("Comment"), renderer, "text", COL_REPLAY_COMMENT, "visible", COL_REPLAY_VISIBLE, NULL);
1720 /* doubleclick will play the replay */
1721 g_signal_connect(G_OBJECT(view), "row-activated", G_CALLBACK(show_replays_tree_view_row_activated_cb), NULL);
1722 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 4), TRUE); /* name column expands */
1724 renderer=gtk_cell_renderer_toggle_new();
1725 g_signal_connect(renderer, "toggled", G_CALLBACK(show_replays_saved_toggled), model);
1726 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 6, _("Saved"), renderer, "active", COL_REPLAY_SAVED, "visible", COL_REPLAY_VISIBLE, NULL);
1728 /* play button */
1729 button=gtk_button_new_from_stock(GTK_STOCK_MEDIA_PLAY);
1730 gtk_widget_set_sensitive(button, FALSE); /* not sensitive by default. when the user selects a line, it will be enabled */
1731 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(show_replays_play_button_clicked), view);
1732 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(view))), "changed", G_CALLBACK(show_replays_tree_view_selection_changed), button);
1733 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->action_area), button);
1735 /* this must be added after the play button, so it is the rightmost one */
1736 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1737 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1739 gtk_widget_show_all(dialog);
1747 /* a cave name is selected, update the text boxt with current cave's data */
1748 static void
1749 cave_info_combo_changed(GtkComboBox *widget, gpointer data)
1751 GtkTextBuffer *buffer=GTK_TEXT_BUFFER(data);
1752 GtkTextIter iter;
1753 int i;
1755 /* clear text buffer */
1756 gtk_text_buffer_set_text(buffer, "", -1);
1757 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
1759 i=gtk_combo_box_get_active(widget);
1760 if (i==0) {
1761 /* cave set data */
1762 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, gd_caveset_data->name, -1, "heading", NULL);
1763 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1765 /* show properties with title only if they are not empty string */
1766 if (!g_str_equal(gd_caveset_data->description, "")) {
1767 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1768 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->description, -1);
1769 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1771 if (!g_str_equal(gd_caveset_data->author, "")) {
1772 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1773 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->author, -1);
1774 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1776 if (!g_str_equal(gd_caveset_data->date, "")) {
1777 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1778 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->date, -1);
1779 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1781 if (!g_str_equal(gd_caveset_data->difficulty, "")) {
1782 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Diffuculty: "), -1, "name", NULL);
1783 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->difficulty, -1);
1784 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1786 if (!g_str_equal(gd_caveset_data->story->str, "")) {
1787 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Story:\n"), -1, "name", NULL);
1788 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->story->str, -1);
1789 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1791 if (!g_str_equal(gd_caveset_data->remark->str, "")) {
1792 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark:\n"), -1, "name", NULL);
1793 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->remark->str, -1);
1794 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1797 else {
1798 /* cave data */
1799 GdCave *cave=gd_return_nth_cave(i-1);
1801 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, cave->name, -1, "heading", NULL);
1802 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1804 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Type: "), -1, "name", NULL);
1805 gtk_text_buffer_insert(buffer, &iter, cave->intermission?_("Intermission"):_("Normal cave"), -1);
1806 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1808 /* show properties with title only if they are not empty string */
1809 if (!g_str_equal(cave->description, "")) {
1810 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1811 gtk_text_buffer_insert(buffer, &iter, cave->description, -1);
1812 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1814 if (!g_str_equal(cave->author, "")) {
1815 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1816 gtk_text_buffer_insert(buffer, &iter, cave->author, -1);
1817 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1819 if (!g_str_equal(cave->date, "")) {
1820 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1821 gtk_text_buffer_insert(buffer, &iter, cave->date, -1);
1822 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1824 if (!g_str_equal(cave->difficulty, "")) {
1825 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Difficulty: "), -1, "name", NULL);
1826 gtk_text_buffer_insert(buffer, &iter, cave->difficulty, -1);
1827 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1830 if (!g_str_equal(cave->story->str, "")) {
1831 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Story:\n"), -1, "name", NULL);
1832 gtk_text_buffer_insert(buffer, &iter, cave->story->str, -1);
1833 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1835 if (!g_str_equal(cave->remark->str, "")) {
1836 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark:\n"), -1, "name", NULL);
1837 gtk_text_buffer_insert(buffer, &iter, cave->remark->str, -1);
1838 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1844 /* show info about cave or caveset */
1845 static void
1846 cave_info_cb(GtkWidget *widget, gpointer data)
1848 GtkWidget *dialog, *view, *sw, *combo;
1849 char *text;
1850 GtkTextIter iter;
1851 GtkTextBuffer *buffer;
1852 GList *citer;
1853 gboolean paused_save;
1855 /* dialog window */
1856 dialog=gtk_dialog_new_with_buttons(_("Caveset information"), GTK_WINDOW(main_window.window),
1857 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
1858 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1859 NULL);
1860 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1861 gtk_window_set_default_size(GTK_WINDOW(dialog), 360, 480);
1863 /* create a combo box. 0=caveset, 1, 2, 3... = caves */
1864 combo=gtk_combo_box_new_text();
1865 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), combo, FALSE, FALSE, 6);
1866 text=g_strdup_printf("[%s]", gd_caveset_data->name); /* caveset name = line 0 */
1867 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
1868 g_free(text);
1869 for (citer=gd_caveset; citer!=NULL; citer=citer->next) {
1870 GdCave *c=citer->data;
1872 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), c->name);
1875 /* create text buffer */
1876 buffer=gtk_text_buffer_new(NULL);
1877 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
1878 gtk_text_buffer_create_tag (buffer, "heading", "weight", PANGO_WEIGHT_BOLD, "scale", PANGO_SCALE_X_LARGE, NULL);
1879 gtk_text_buffer_create_tag (buffer, "name", "weight", PANGO_WEIGHT_BOLD, NULL);
1880 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, "GDash " PACKAGE_VERSION "\n\n", -1, "heading", NULL);
1882 sw=gtk_scrolled_window_new(NULL, NULL);
1883 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1884 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1885 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
1886 view=gtk_text_view_new_with_buffer(buffer);
1887 gtk_container_add(GTK_CONTAINER (sw), view);
1888 g_object_unref(buffer);
1889 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
1890 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
1891 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD);
1892 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view), 3);
1893 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 6);
1894 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 6);
1896 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(cave_info_combo_changed), buffer);
1897 if (main_window.game && main_window.game->original_cave)
1898 /* currently playing a cave - show info for that */
1899 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), g_list_index(gd_caveset, main_window.game->original_cave)+1);
1900 else
1901 /* show info for caveset */
1902 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1905 gtk_widget_show_all(dialog);
1906 paused_save=paused;
1907 paused=TRUE; /* set paused game, so it stops while the users sees the message box */
1908 gtk_dialog_run(GTK_DIALOG(dialog));
1909 gtk_widget_destroy(dialog);
1910 paused=paused_save;
1916 * Creates main window
1920 static void
1921 create_main_window()
1923 /* Menu UI */
1924 static GtkActionEntry action_entries_normal[]={
1925 {"PlayMenu", NULL, N_("_Play")},
1926 {"FileMenu", NULL, N_("_File")},
1927 {"SettingsMenu", NULL, N_("_Settings")},
1928 {"HelpMenu", NULL, N_("_Help")},
1929 {"Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK(quit_cb)},
1930 {"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_cb)},
1931 {"Errors", GTK_STOCK_DIALOG_ERROR, N_("_Error console"), NULL, NULL, G_CALLBACK(show_errors_cb)},
1932 {"Help", GTK_STOCK_HELP, NULL, NULL, NULL, G_CALLBACK(help_cb)},
1933 {"CaveInfo", GTK_STOCK_DIALOG_INFO, N_("Caveset _information"), NULL, N_("Show information about the game and its caves"), G_CALLBACK(cave_info_cb)},
1936 static GtkActionEntry action_entries_title[]={
1937 {"GamePreferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, G_CALLBACK(preferences_cb)},
1938 {"GameControlSettings", GD_ICON_KEYBOARD, N_("_Control keys"), NULL, NULL, G_CALLBACK(control_settings_cb)},
1939 {"NewGame", GTK_STOCK_MEDIA_PLAY, N_("_New game"), "<control>N", N_("Start new game"), G_CALLBACK(new_game_cb)},
1940 {"CaveEditor", GD_ICON_CAVE_EDITOR, N_("Cave _editor"), NULL, NULL, G_CALLBACK(cave_editor_cb)},
1941 {"OpenFile", GTK_STOCK_OPEN, NULL, NULL, NULL, G_CALLBACK(open_caveset_cb)},
1942 {"LoadInternal", GTK_STOCK_INDEX, N_("Load _internal game")},
1943 {"LoadRecent", GTK_STOCK_DIALOG_INFO, N_("Open _recent")},
1944 {"OpenCavesDir", GTK_STOCK_CDROM, N_("O_pen shipped"), NULL, NULL, G_CALLBACK(open_caveset_dir_cb)},
1945 {"SaveFile", GTK_STOCK_SAVE, NULL, NULL, NULL, G_CALLBACK(save_caveset_cb)},
1946 {"SaveAsFile", GTK_STOCK_SAVE_AS, NULL, NULL, NULL, G_CALLBACK(save_caveset_as_cb)},
1947 {"HighScore", GD_ICON_AWARD, N_("Hi_ghscores"), NULL, NULL, G_CALLBACK(highscore_cb)},
1950 static GtkActionEntry action_entries_title_replay[]={
1951 {"ShowReplays", GD_ICON_REPLAY, N_("Show _replays"), NULL, N_("List replays which are recorded for caves in this caveset"), G_CALLBACK(show_replays_cb)},
1954 static GtkActionEntry action_entries_game[]={
1955 {"TakeSnapshot", GD_ICON_SNAPSHOT, N_("_Take snapshot"), "F5", NULL, G_CALLBACK(save_snapshot_cb)},
1956 {"Restart", GD_ICON_RESTART_LEVEL, N_("Re_start level"), "Escape", N_("Restart current level"), G_CALLBACK(restart_level_cb)},
1957 {"EndGame", GTK_STOCK_STOP, N_("_End game"), "F1", N_("End current game"), G_CALLBACK(stop_game_cb)},
1960 static GtkActionEntry action_entries_snapshot[]={
1961 {"RevertToSnapshot", GTK_STOCK_UNDO, N_("_Revert to snapshot"), "F6", NULL, G_CALLBACK(load_snapshot_cb)},
1964 static GtkToggleActionEntry action_entries_toggle[]={
1965 {"PauseGame", GTK_STOCK_MEDIA_PAUSE, NULL, "space", N_("Pause game"), G_CALLBACK(toggle_pause_cb), FALSE},
1966 {"FullScreen", GTK_STOCK_FULLSCREEN, NULL, "F11", N_("Fullscreen mode during play"), G_CALLBACK(toggle_fullscreen_cb), FALSE},
1967 {"FastForward", GTK_STOCK_MEDIA_FORWARD, N_("Fast for_ward"), "<control>F", N_("Fast forward"), G_CALLBACK(toggle_fast_cb), FALSE},
1970 static const char *ui_info =
1971 "<ui>"
1972 "<menubar name='MenuBar'>"
1973 "<menu action='FileMenu'>"
1974 "<separator/>"
1975 "<menuitem action='OpenFile'/>"
1976 "<menuitem action='LoadRecent'/>"
1977 "<menuitem action='OpenCavesDir'/>"
1978 "<menuitem action='LoadInternal'/>"
1979 "<separator/>"
1980 "<menuitem action='CaveEditor'/>"
1981 "<separator/>"
1982 "<menuitem action='SaveFile'/>"
1983 "<menuitem action='SaveAsFile'/>"
1984 "<separator/>"
1985 "<menuitem action='Quit'/>"
1986 "</menu>"
1987 "<menu action='PlayMenu'>"
1988 "<menuitem action='NewGame'/>"
1989 "<menuitem action='PauseGame'/>"
1990 "<menuitem action='FastForward'/>"
1991 "<menuitem action='TakeSnapshot'/>"
1992 "<menuitem action='RevertToSnapshot'/>"
1993 "<menuitem action='Restart'/>"
1994 "<menuitem action='EndGame'/>"
1995 "<separator/>"
1996 "<menuitem action='CaveInfo'/>"
1997 "<menuitem action='HighScore'/>"
1998 "<menuitem action='ShowReplays'/>"
1999 "<separator/>"
2000 "<menuitem action='FullScreen'/>"
2001 "<menuitem action='GameControlSettings'/>"
2002 "<menuitem action='GamePreferences'/>"
2003 "</menu>"
2004 "<menu action='HelpMenu'>"
2005 "<menuitem action='Help'/>"
2006 "<separator/>"
2007 "<menuitem action='Errors'/>"
2008 "<menuitem action='About'/>"
2009 "</menu>"
2010 "</menubar>"
2012 "<toolbar name='ToolBar'>"
2013 "<toolitem action='NewGame'/>"
2014 "<toolitem action='EndGame'/>"
2015 "<toolitem action='FullScreen'/>"
2016 "<separator/>"
2017 "<toolitem action='PauseGame'/>"
2018 "<toolitem action='FastForward'/>"
2019 "<toolitem action='Restart'/>"
2020 "<separator/>"
2021 "<toolitem action='CaveInfo'/>"
2022 "<toolitem action='ShowReplays'/>"
2023 "</toolbar>"
2024 "</ui>";
2026 GtkWidget *vbox, *hbox, *recent_chooser;
2027 GtkRecentFilter *recent_filter;
2028 GdkPixbuf *logo;
2029 GtkUIManager *ui;
2030 int i;
2031 const gchar **names;
2032 GtkWidget *menu;
2034 logo=gd_icon();
2035 gtk_window_set_default_icon (logo);
2036 g_object_unref(logo);
2038 main_window.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
2039 gtk_window_set_default_size(GTK_WINDOW(main_window.window), 360, 300);
2040 g_signal_connect(G_OBJECT(main_window.window), "focus_out_event", G_CALLBACK(main_window_focus_out_event), NULL);
2041 g_signal_connect(G_OBJECT(main_window.window), "delete_event", G_CALLBACK(main_window_delete_event), NULL);
2042 g_signal_connect(G_OBJECT(main_window.window), "key_press_event", G_CALLBACK(main_window_keypress_event), NULL);
2043 g_signal_connect(G_OBJECT(main_window.window), "key_release_event", G_CALLBACK(main_window_keypress_event), NULL);
2045 /* vertical box */
2046 vbox=gtk_vbox_new(FALSE, 0);
2047 gtk_container_add (GTK_CONTAINER (main_window.window), vbox);
2049 /* menu */
2050 main_window.actions_normal=gtk_action_group_new("main_window.actions_normal");
2051 gtk_action_group_set_translation_domain(main_window.actions_normal, PACKAGE);
2052 gtk_action_group_add_actions(main_window.actions_normal, action_entries_normal, G_N_ELEMENTS(action_entries_normal), &main_window);
2053 gtk_action_group_add_toggle_actions(main_window.actions_normal, action_entries_toggle, G_N_ELEMENTS(action_entries_toggle), NULL);
2054 main_window.actions_title=gtk_action_group_new("main_window.actions_title");
2055 gtk_action_group_set_translation_domain(main_window.actions_title, PACKAGE);
2056 gtk_action_group_add_actions(main_window.actions_title, action_entries_title, G_N_ELEMENTS(action_entries_title), &main_window);
2057 main_window.actions_title_replay=gtk_action_group_new("main_window.actions_title_replay");
2058 gtk_action_group_set_translation_domain(main_window.actions_title_replay, PACKAGE);
2059 gtk_action_group_add_actions(main_window.actions_title_replay, action_entries_title_replay, G_N_ELEMENTS(action_entries_title_replay), &main_window);
2060 /* make this toolbar button always have a title */
2061 g_object_set (gtk_action_group_get_action (main_window.actions_title, "NewGame"), "is_important", TRUE, NULL);
2062 main_window.actions_game=gtk_action_group_new("main_window.actions_game");
2063 gtk_action_group_set_translation_domain(main_window.actions_game, PACKAGE);
2064 gtk_action_group_add_actions(main_window.actions_game, action_entries_game, G_N_ELEMENTS(action_entries_game), &main_window);
2065 main_window.actions_snapshot=gtk_action_group_new("main_window.actions_snapshot");
2066 gtk_action_group_set_translation_domain(main_window.actions_snapshot, PACKAGE);
2067 gtk_action_group_add_actions(main_window.actions_snapshot, action_entries_snapshot, G_N_ELEMENTS(action_entries_snapshot), &main_window);
2069 /* build the ui */
2070 ui=gtk_ui_manager_new();
2071 gtk_ui_manager_insert_action_group (ui, main_window.actions_normal, 0);
2072 gtk_ui_manager_insert_action_group (ui, main_window.actions_title, 0);
2073 gtk_ui_manager_insert_action_group (ui, main_window.actions_title_replay, 0);
2074 gtk_ui_manager_insert_action_group (ui, main_window.actions_game, 0);
2075 gtk_ui_manager_insert_action_group (ui, main_window.actions_snapshot, 0);
2076 gtk_window_add_accel_group (GTK_WINDOW(main_window.window), gtk_ui_manager_get_accel_group (ui));
2077 gtk_ui_manager_add_ui_from_string (ui, ui_info, -1, NULL);
2078 main_window.menubar=gtk_ui_manager_get_widget (ui, "/MenuBar");
2079 gtk_box_pack_start(GTK_BOX (vbox), main_window.menubar, FALSE, FALSE, 0);
2080 main_window.toolbar=gtk_ui_manager_get_widget (ui, "/ToolBar");
2081 gtk_toolbar_set_style(GTK_TOOLBAR(main_window.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
2082 gtk_box_pack_start(GTK_BOX (vbox), main_window.toolbar, FALSE, FALSE, 0);
2083 gtk_toolbar_set_tooltips (GTK_TOOLBAR (main_window.toolbar), TRUE);
2085 /* make a submenu, which contains the games compiled in. */
2086 i=0;
2087 menu=gtk_menu_new();
2088 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadInternal")), menu);
2089 names=gd_caveset_get_internal_game_names ();
2090 while (names[i]) {
2091 GtkWidget *menuitem=gtk_menu_item_new_with_label(names[i]);
2093 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
2094 gtk_widget_show(menuitem);
2095 g_signal_connect (G_OBJECT(menuitem), "activate", G_CALLBACK(load_internal_cb), GINT_TO_POINTER (i));
2096 i++;
2099 /* recent file chooser */
2100 recent_chooser=gtk_recent_chooser_menu_new();
2101 recent_filter=gtk_recent_filter_new();
2102 /* gdash file extensions */
2103 for (i=0; gd_caveset_extensions[i]!=NULL; i++)
2104 gtk_recent_filter_add_pattern(recent_filter, gd_caveset_extensions[i]);
2105 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_chooser), recent_filter);
2106 gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(recent_chooser), TRUE);
2107 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_chooser), 10);
2108 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_chooser), GTK_RECENT_SORT_MRU);
2109 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadRecent")), recent_chooser);
2110 g_signal_connect(G_OBJECT(recent_chooser), "item-activated", G_CALLBACK(recent_chooser_activated_cb), NULL);
2112 g_object_unref(ui);
2114 /* this hbox will contain the replay logo and the labels */
2115 hbox=gtk_hbox_new(FALSE, 6);
2116 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2118 /* replay logo... */
2119 main_window.replay_image_align=gtk_alignment_new(0.5, 0.5, 0, 0);
2120 gtk_box_pack_start(GTK_BOX(hbox), main_window.replay_image_align, FALSE, FALSE, 0);
2121 gtk_container_add(GTK_CONTAINER(main_window.replay_image_align), gtk_image_new_from_stock(GD_ICON_REPLAY, GTK_ICON_SIZE_LARGE_TOOLBAR));
2123 /* ... labels. */
2124 main_window.labels=gtk_vbox_new(FALSE, 0);
2125 gtk_box_pack_start_defaults(GTK_BOX(hbox), main_window.labels);
2127 /* first hbox for labels ABOVE drawing */
2128 hbox=gtk_hbox_new(FALSE, 12);
2129 gtk_box_pack_start(GTK_BOX (main_window.labels), hbox, FALSE, FALSE, 0);
2130 main_window.label_topleft=gtk_label_new(NULL); /* NAME label */
2131 gtk_label_set_ellipsize(GTK_LABEL(main_window.label_topleft), PANGO_ELLIPSIZE_END); /* enable ellipsize, as the cave name might be a long string */
2132 gtk_misc_set_alignment(GTK_MISC(main_window.label_topleft), 0, 0.5); /* as it will be expanded, we must set left justify (0) */
2133 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_topleft, TRUE, TRUE, 0); /* should expand, as it is ellipsized!! */
2135 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_topright=gtk_label_new(NULL), FALSE, FALSE, 0);
2137 /* second row of labels */
2138 hbox=gtk_hbox_new(FALSE, 12);
2139 gtk_box_pack_start(GTK_BOX(main_window.labels), hbox, FALSE, FALSE, 0);
2140 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_bottomleft=gtk_label_new(NULL), FALSE, FALSE, 0); /* diamonds label */
2141 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_bottomright=gtk_label_new(NULL), FALSE, FALSE, 0); /* time, score label */
2144 /* scroll window which contains the cave or the title image, so this is the main content of the window */
2145 main_window.scroll_window=gtk_scrolled_window_new(NULL, NULL);
2146 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(main_window.scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2147 gtk_box_pack_start_defaults(GTK_BOX (vbox), main_window.scroll_window);
2149 main_window.label_variables=gtk_label_new(NULL);
2150 gtk_box_pack_start(GTK_BOX (vbox), main_window.label_variables, FALSE, FALSE, 0);
2152 hbox=gtk_hbox_new(FALSE, 6);
2153 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2154 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU), FALSE, FALSE, 0);
2155 gtk_box_pack_start(GTK_BOX(hbox), main_window.error_label=gtk_label_new(NULL), FALSE, FALSE, 0); /* error label */
2156 main_window.error_hbox=hbox;
2158 gtk_widget_show_all(main_window.window);
2159 gtk_window_present(GTK_WINDOW(main_window.window));
2163 main()
2164 function
2168 main(int argc, char *argv[])
2170 int quit=0;
2171 gboolean editor=FALSE;
2172 char *gallery_filename=NULL;
2173 char *png_filename=NULL, *png_size=NULL;
2174 char *save_cave_name=NULL;
2175 gboolean force_quit_no_gtk;
2177 GError *error=NULL;
2178 GOptionContext *context;
2179 GOptionEntry entries[]={
2180 {"editor", 'e', 0, G_OPTION_ARG_NONE, &editor, N_("Start editor")},
2181 {"gallery", 'g', 0, G_OPTION_ARG_FILENAME, &gallery_filename, N_("Save caveset in a HTML gallery")},
2182 {"stylesheet", 's', 0, G_OPTION_ARG_STRING /* not filename! */, &gd_html_stylesheet_filename, N_("Link stylesheet from file to a HTML gallery, eg. \"../style.css\"")},
2183 {"favicon", 's', 0, G_OPTION_ARG_STRING /* not filename! */, &gd_html_favicon_filename, N_("Link shortcut icon to a HTML gallery, eg. \"../favicon.ico\"")},
2184 {"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")},
2185 {"png_size", 'P', 0, G_OPTION_ARG_STRING, &png_size, N_("Set PNG image size. Default is 128x96, set to 0x0 for unscaled")},
2186 {"save", 'S', 0, G_OPTION_ARG_FILENAME, &save_cave_name, N_("Save caveset in a BDCFF file")},
2187 {"quit", 'q', 0, G_OPTION_ARG_NONE, &quit, N_("Batch mode: quit after specified tasks")},
2188 {NULL}
2191 context=gd_option_context_new();
2192 g_option_context_add_main_entries (context, entries, PACKAGE); /* gdash (gtk version) parameters */
2193 g_option_context_add_group (context, gtk_get_option_group(FALSE)); /* add gtk parameters */
2194 g_option_context_parse (context, &argc, &argv, &error);
2195 g_option_context_free (context);
2196 if (error) {
2197 g_warning("%s", error->message);
2198 g_error_free(error);
2201 /* show license? */
2202 if (gd_param_license) {
2203 char *wrapped=gd_wrap_text(gd_about_license, 72);
2205 g_print("%s", wrapped);
2206 g_free(wrapped);
2207 return 0;
2210 gd_install_log_handler();
2212 gd_settings_init_dirs();
2214 gd_load_settings();
2216 gtk_set_locale();
2217 gd_settings_set_locale();
2219 force_quit_no_gtk=FALSE;
2220 if (!gtk_init_check(&argc, &argv)) {
2221 force_quit_no_gtk=TRUE;
2224 gd_settings_init_translation();
2226 gd_cave_init();
2227 gd_cave_db_init();
2228 gd_cave_sound_db_init();
2229 gd_c64_import_init_tables();
2231 gd_caveset_clear(); /* this also creates the default cave */
2233 gd_clear_error_flag();
2235 gd_wait_before_game_over=FALSE;
2237 /* LOAD A CAVESET FROM A FILE, OR AN INTERNAL ONE */
2238 /* if remaining arguments, they are filenames */
2239 if (gd_param_cavenames && gd_param_cavenames[0]) {
2240 /* load caveset, "ignore" errors. */
2241 if (!gd_open_caveset_in_ui(gd_param_cavenames[0], gd_use_bdcff_highscore))
2242 g_critical (_("Errors during loading caveset from file '%s'"), gd_param_cavenames[0]);
2244 else if (gd_param_internal) {
2245 /* if specified an internal caveset; if error, halt now */
2246 if (!gd_caveset_load_from_internal (gd_param_internal-1, gd_user_config_dir))
2247 g_critical (_("%d: no such internal caveset"), gd_param_internal);
2249 else
2250 /* if nothing requested, load default */
2251 gd_caveset_load_from_internal(0, gd_user_config_dir);
2253 /* always load c64 graphics, as it is the builtin, and we need an icon for the theme selector. */
2254 gd_loadcells_default();
2255 gd_create_pixbuf_for_builtin_theme();
2257 /* load other theme, if specified in config. */
2258 if (gd_theme!=NULL && !g_str_equal(gd_theme, "")) {
2259 if (!gd_loadcells_file(gd_theme)) {
2260 /* forget the theme if we are unable to load */
2261 g_warning("Cannot load theme %s, switching to built-in theme", gd_theme);
2262 g_free(gd_theme);
2263 gd_theme=NULL;
2264 gd_loadcells_default(); /* load default gfx */
2268 /* after loading cells... see if generating a gallery. */
2269 /* but only if there are any caves at all. */
2270 if (gd_caveset && gallery_filename)
2271 gd_save_html (gallery_filename, NULL);
2273 /* if cave or level values given, check range */
2274 if (gd_param_cave)
2275 if (gd_param_cave<1 || gd_param_cave>gd_caveset_count() || gd_param_level<1 || gd_param_level>5)
2276 g_critical (_("Invalid cave or level number!"));
2278 /* save cave png */
2279 if (png_filename) {
2280 GError *error=NULL;
2281 GdkPixbuf *pixbuf;
2282 GdCave *renderedcave;
2283 unsigned int size_x=128, size_y=96; /* default size */
2285 if (gd_param_cave == 0)
2286 gd_param_cave=g_random_int_range(0, gd_caveset_count ())+1;
2288 if (png_size && (sscanf (png_size, "%ux%u", &size_x, &size_y) != 2))
2289 g_critical (_("Invalid image size: %s"), png_size);
2290 if (size_x<1 || size_y<1) {
2291 size_x=0;
2292 size_y=0;
2295 /* rendering cave for png: seed=0 */
2296 renderedcave=gd_cave_new_from_caveset (gd_param_cave-1, gd_param_level-1, 0);
2297 pixbuf=gd_drawcave_to_pixbuf(renderedcave, size_x, size_y, TRUE, FALSE);
2298 if (!gdk_pixbuf_save (pixbuf, png_filename, "png", &error, "compression", "9", NULL))
2299 g_critical ("Error saving PNG image %s: %s", png_filename, error->message);
2300 g_object_unref(pixbuf);
2301 gd_cave_free (renderedcave);
2303 /* avoid starting game */
2304 gd_param_cave=0;
2307 if (save_cave_name)
2308 gd_caveset_save(save_cave_name);
2310 /* if batch mode, quit now */
2311 if (quit)
2312 return 0;
2313 if (force_quit_no_gtk) {
2314 g_critical("Cannot initialize GUI");
2315 return 1;
2318 /* create window */
2319 gd_register_stock_icons();
2320 create_main_window();
2321 gd_main_window_set_title();
2323 gd_sound_init(0);
2325 gd_sound_set_music_volume(gd_sound_music_volume_percent);
2326 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
2328 main_window_init_title();
2330 #ifdef G_THREADS_ENABLED
2331 if (!g_thread_supported())
2332 g_thread_init(NULL);
2333 #endif
2335 if (gd_param_cave) {
2336 /* if cave number given, start game */
2337 main_new_game(g_get_real_name(), gd_param_cave-1, gd_param_level-1);
2339 else if (editor)
2340 cave_editor_cb(NULL, &main_window);
2342 gtk_main();
2344 gd_save_highscore(gd_user_config_dir);
2346 gd_save_settings();
2348 return 0;