20100212
[gdash.git] / src / gtkmain.c
blob097c5e586c46db0544a38edd8bd95cc3a0219b2a
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 *info_bar, *info_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)
214 return;
215 if (!main_window.drawing_area->window)
216 return;
217 if (!main_window.game)
218 return;
219 if (!main_window.game->cave) /* if already no cave, just return */
220 return;
221 if (!main_window.game->gfx_buffer) /* if already no cave, just return */
222 return;
224 /* 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. */
225 /* on the other hand, xd and yd are screen coordinates (of course, multiply by cell size) */
226 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
227 /* top left part of cave:
228 * x=0 xd=0
229 * |
230 *y=0 wwww|wwwwwwwwwwwwww
231 *yd=0 ----+--------------
232 * w...|o p........ visible part
233 * w...|..d...........
235 /* before drawing, process all pending events for the drawing area, so no confusion occurs. */
236 for (y=main_window.game->cave->y1, yd=0; y<=main_window.game->cave->y2; y++, yd++) {
237 for (x=main_window.game->cave->x1, xd=0; x<=main_window.game->cave->x2; x++, xd++) {
238 if (main_window.game->gfx_buffer[y][x] & GD_REDRAW) {
239 main_window.game->gfx_buffer[y][x] &= ~GD_REDRAW;
240 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]),
241 0, 0, xd*gd_cell_size_game, yd*gd_cell_size_game, gd_cell_size_game, gd_cell_size_game);
249 /* CAVE DRAWING is done in an idle func. it requires much time, especially on windows. */
250 static gboolean draw_idle_func_installed=FALSE;
252 static gboolean draw_idle_func(gpointer data)
254 drawing_area_draw_cave();
256 draw_idle_func_installed=FALSE;
257 return FALSE;
260 static void schedule_draw()
262 /* update in an idle func, so we do not slow down the application, when there is no time to draw. */
263 /* the priority must be very low, as gtk also does its things in idle funcs, ie. window resizing and */
264 /* expose events. otherwise we would mess up the scrolling */
265 if (!draw_idle_func_installed) {
266 draw_idle_func_installed=TRUE;
267 g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc) draw_idle_func, NULL, NULL);
274 static gboolean
275 drawing_area_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
277 int x1, y1, x2, y2, xd, yd;
279 if (!widget->window)
280 return FALSE;
281 if (!main_window.game)
282 return FALSE;
283 if (!main_window.game->cave) /* if already no cave, just return */
284 return FALSE;
285 if (!main_window.game->gfx_buffer) /* if already no cave, just return */
286 return FALSE;
288 /* redraw entire area, not specific rectangles.
289 * this function gets called only when the main window gets exposed
290 * by the user removing another window. */
291 /* these are screen coordinates. */
292 x1=event->area.x/gd_cell_size_game;
293 y1=event->area.y/gd_cell_size_game;
294 x2=(event->area.x+event->area.width-1)/gd_cell_size_game;
295 y2=(event->area.y+event->area.height-1)/gd_cell_size_game;
296 /* when resizing the cave, we may get events which store the old size, if the drawing area is not yet resized. */
297 if (x1<0) x1=0;
298 if (y1<0) x1=0;
299 if (x2>=main_window.game->cave->w) x2=main_window.game->cave->w-1;
300 if (y2>=main_window.game->cave->h) y2=main_window.game->cave->h-1;
302 /* run through screen coordinates to refresh. */
303 /* inside the loop, calculate cave coordinates, which might not be equal to screen coordinates. */
304 /* top left part of cave:
305 * x=0 xd=0
306 * |
307 *y=0 wwww|wwwwwwwwwwwwww
308 *yd=0 ----+--------------
309 * w...|o p........ visible part
310 * w...|..d...........
312 /* mark all cells to be redrawn with GD_REDRAW, and then call the normal drawcave routine. */
313 for (yd=y1; yd<=y2; yd++) {
314 int y=yd+main_window.game->cave->y1;
315 for (xd=x1; xd<=x2; xd++) {
316 int x=xd+main_window.game->cave->x1;
317 if (main_window.game->gfx_buffer[y][x]!=-1)
318 main_window.game->gfx_buffer[y][x] |= GD_REDRAW;
321 /* schedule drawing as an idle func. */
322 schedule_draw();
324 return TRUE;
333 * functions for the main window
334 * - fullscreen
335 * - game
336 * - title animation
340 static gboolean
341 main_window_set_fullscreen_idle_func(gpointer data)
343 gtk_window_fullscreen(GTK_WINDOW(data));
344 return FALSE;
347 /* set or unset fullscreen if necessary */
348 /* hack: gtk-win32 does not correctly handle fullscreen & removing widgets.
349 so we put fullscreening call into a low priority idle function, which will be called
350 after all window resizing & the like did take place. */
351 static void
352 main_window_set_fullscreen(gboolean ingame)
354 if (ingame && fullscreen) {
355 gtk_widget_hide(main_window.menubar);
356 gtk_widget_hide(main_window.toolbar);
357 g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc) main_window_set_fullscreen_idle_func, main_window.window, NULL);
359 else {
360 g_idle_remove_by_data(main_window.window);
361 gtk_window_unfullscreen(GTK_WINDOW(main_window.window));
362 gtk_widget_show(main_window.menubar);
363 gtk_widget_show(main_window.toolbar);
369 * init mainwindow
370 * creates title screen or drawing area
374 static gboolean main_window_title_animation_idle_func_installed=FALSE;
376 static gboolean
377 main_window_title_animation_idle_func(gpointer data)
379 gtk_image_set_from_pixmap(GTK_IMAGE(main_window.title_image), (GdkPixmap *) data, NULL);
381 main_window_title_animation_idle_func_installed=FALSE;
382 return FALSE;
385 static void
386 main_window_title_animation_install_idle(GdkPixmap *pixbuf)
388 if (!main_window_title_animation_idle_func_installed) {
389 g_idle_add_full(G_PRIORITY_LOW, main_window_title_animation_idle_func, pixbuf, NULL);
390 main_window_title_animation_idle_func_installed=TRUE;
394 static gboolean
395 main_window_title_animation_func(gpointer data)
397 static int animcycle=0;
398 int count;
400 /* if title image widget does not exist for some reason, we quit now */
401 if (main_window.title_image==NULL)
402 return FALSE;
404 /* count the number of frames. */
405 count=0;
406 while (main_window.title_pixmaps[count]!=NULL)
407 count++;
409 if (gtk_window_has_toplevel_focus(GTK_WINDOW(main_window.window))) {
410 animcycle=(animcycle+1)%count;
411 /* do the drawing when we have time. */
412 main_window_title_animation_install_idle(main_window.title_pixmaps[animcycle]);
414 return TRUE;
417 static void
418 main_window_title_animation_remove()
420 int i;
422 g_source_remove_by_user_data(main_window_title_animation_func);
423 i=0;
424 /* free animation frames, if any */
425 if (main_window.title_pixmaps!=NULL) {
426 while (main_window.title_pixmaps[i]!=NULL) {
427 g_object_unref(main_window.title_pixmaps[i]);
428 i++;
430 g_free(main_window.title_pixmaps);
431 main_window.title_pixmaps=NULL;
435 void
436 gd_main_window_set_title_animation()
438 int w, h;
440 /* if main window does not exist, ignore. */
441 if (main_window.window==NULL)
442 return;
443 /* if exists but not showing a title image, that is an error. */
444 g_return_if_fail(main_window.title_image!=NULL);
446 main_window_title_animation_remove();
447 main_window.title_pixmaps=gd_create_title_animation();
448 /* set first frame immediately */
449 gtk_image_set_from_pixmap(GTK_IMAGE(main_window.title_image), main_window.title_pixmaps[0], NULL);
450 /* and if more frames, add animation timeout */
451 if (main_window.title_pixmaps[1]!=NULL)
452 g_timeout_add(40, main_window_title_animation_func, main_window_title_animation_func); /* 25fps animation */
454 /* resize the scrolling window so the image fits - a bit larger than the image so scroll bars do not appear*/
455 gdk_drawable_get_size(GDK_DRAWABLE(main_window.title_pixmaps[0]), &w, &h);
456 /* also some minimum size... 320x200 is some adhoc value only. */
457 gtk_widget_set_size_request(main_window.scroll_window, MAX(w, 320)+24, MAX(h, 200)+24);
459 /* with the title screen, it is usually smaller than in the game. shrink it. */
460 gtk_window_resize(GTK_WINDOW(main_window.window), 1, 1);
463 static void
464 main_window_init_title()
466 if (!main_window.title_image) {
467 /* destroy current widget in main window */
468 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
469 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
471 /* title screen */
472 main_window.title_image=gtk_image_new();
473 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.title_image);
474 g_signal_connect (G_OBJECT(main_window.title_image), "destroy", G_CALLBACK(main_window_title_animation_remove), NULL);
475 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), main_window.title_image);
477 gd_main_window_set_title_animation();
479 /* show newly created widgets */
480 gtk_widget_show_all(main_window.scroll_window);
483 /* hide cave data */
484 gtk_widget_hide(main_window.labels);
485 gtk_widget_hide(main_window.label_variables);
486 if (gd_has_new_error()) {
487 gtk_label_set(GTK_LABEL(main_window.info_label), ((GdErrorMessage *)(g_list_last(gd_errors)->data))->message);
488 gtk_widget_show(main_window.info_bar);
489 } else {
490 gtk_widget_hide(main_window.info_bar);
493 /* enable menus and buttons of game */
494 gtk_action_group_set_sensitive(main_window.actions_title, !gd_editor_window);
495 gtk_action_group_set_sensitive(main_window.actions_title_replay, !gd_editor_window && gd_caveset_has_replays());
496 gtk_action_group_set_sensitive(main_window.actions_game, FALSE);
497 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
498 /* if editor window exists, no music. */
499 if (gd_editor_window)
500 gd_music_stop();
501 else
502 gd_music_play_random();
503 if (gd_editor_window)
504 gtk_widget_set_sensitive(gd_editor_window, TRUE);
505 gtk_widget_hide(main_window.replay_image_align);
507 /* set or unset fullscreen if necessary */
508 main_window_set_fullscreen(FALSE);
513 static void
514 main_window_init_cave(GdCave *cave)
516 char *name_escaped;
518 g_assert(cave!=NULL);
520 /* cave drawing */
521 if (!main_window.drawing_area) {
522 GtkWidget *align;
524 /* destroy current widget in main window */
525 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
526 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
528 /* put drawing area in an alignment, so window can be any large w/o problems */
529 align=gtk_alignment_new(0.5, 0.5, 0, 0);
530 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), align);
532 main_window.drawing_area=gtk_drawing_area_new();
533 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);
534 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_press_event", G_CALLBACK(drawing_area_button_event), NULL);
535 g_signal_connect (G_OBJECT(main_window.drawing_area), "button_release_event", G_CALLBACK(drawing_area_button_event), NULL);
536 g_signal_connect (G_OBJECT(main_window.drawing_area), "motion_notify_event", G_CALLBACK(drawing_area_motion_event), NULL);
537 g_signal_connect (G_OBJECT(main_window.drawing_area), "leave_notify_event", G_CALLBACK(drawing_area_leave_event), NULL);
538 g_signal_connect (G_OBJECT(main_window.drawing_area), "expose_event", G_CALLBACK(drawing_area_expose_event), NULL);
539 g_signal_connect (G_OBJECT(main_window.drawing_area), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.drawing_area);
540 gtk_container_add (GTK_CONTAINER (align), main_window.drawing_area);
541 if (gd_mouse_play)
542 gdk_window_set_cursor (main_window.drawing_area->window, gdk_cursor_new(GDK_CROSSHAIR));
544 /* show newly created widgets */
545 gtk_widget_show_all(main_window.scroll_window);
547 /* set the minimum size of the scroll window: 20*12 cells */
548 /* XXX adding some pixels for the scrollbars-here we add 24 */
549 gtk_widget_set_size_request(main_window.scroll_window, 20*gd_cell_size_game+24, 12*gd_cell_size_game+24);
550 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);
552 /* show cave data */
553 gtk_widget_show(main_window.labels);
554 if (main_window.game->type==GD_GAMETYPE_TEST && gd_show_test_label)
555 gtk_widget_show(main_window.label_variables);
556 else
557 gtk_widget_hide(main_window.label_variables);
558 gtk_widget_hide(main_window.info_bar);
560 name_escaped=g_markup_escape_text(cave->name, -1);
561 /* TRANSLATORS: cave name, level x */
562 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topleft), _("<b>%s</b>, level %d"), name_escaped, cave->rendered);
563 g_free(name_escaped);
565 /* enable menus and buttons of game */
566 gtk_action_group_set_sensitive(main_window.actions_title, FALSE);
567 gtk_action_group_set_sensitive(main_window.actions_title_replay, FALSE);
568 gtk_action_group_set_sensitive(main_window.actions_game, TRUE);
569 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
571 gd_music_stop();
572 if (gd_editor_window)
573 gtk_widget_set_sensitive(gd_editor_window, FALSE);
574 gtk_widget_hide(main_window.replay_image_align); /* it will be shown if needed. */
576 /* set or unset fullscreen if necessary */
577 main_window_set_fullscreen(TRUE);
580 static void
581 main_window_init_story(GdCave *cave)
583 char *escaped_name, *escaped_story;
585 if (!main_window.story_label) {
586 /* destroy current widget in main window */
587 if (gtk_bin_get_child(GTK_BIN(main_window.scroll_window)))
588 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(main_window.scroll_window)));
590 /* title screen */
591 main_window.story_label=gtk_label_new(NULL);
592 gtk_label_set_line_wrap(GTK_LABEL(main_window.story_label), TRUE);
593 g_signal_connect(G_OBJECT(main_window.story_label), "destroy", G_CALLBACK(gtk_widget_destroyed), &main_window.story_label);
594 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(main_window.scroll_window), main_window.story_label);
596 /* show newly created widgets */
597 gtk_widget_show_all(main_window.scroll_window);
599 escaped_name=g_markup_escape_text(cave->name, -1);
600 escaped_story=g_markup_escape_text(cave->story->str, -1);
601 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);
602 g_free(escaped_name);
603 g_free(escaped_story);
604 /* the width of the label is 320, as the windows is usually not smaller than 320x200 */
605 gtk_widget_set_size_request(main_window.story_label, 320, -1);
607 /* hide cave data */
608 gtk_widget_hide(main_window.labels);
609 gtk_widget_hide(main_window.label_variables);
610 if (gd_has_new_error()) {
611 gtk_widget_show(main_window.info_bar);
612 gtk_label_set(GTK_LABEL(main_window.info_label), ((GdErrorMessage *)(g_list_last(gd_errors)->data))->message);
613 } else {
614 gtk_widget_hide(main_window.info_bar);
617 /* enable menus and buttons of game */
618 gtk_action_group_set_sensitive(main_window.actions_title, FALSE);
619 gtk_action_group_set_sensitive(main_window.actions_title_replay, FALSE);
620 gtk_action_group_set_sensitive(main_window.actions_game, TRUE);
621 gtk_action_group_set_sensitive(main_window.actions_snapshot, snapshot!=NULL);
622 /* if editor window exists, no music. */
623 gd_music_stop();
624 if (gd_editor_window)
625 gtk_widget_set_sensitive(gd_editor_window, FALSE);
626 gtk_widget_hide(main_window.replay_image_align);
628 /* set or unset fullscreen if necessary */
629 main_window_set_fullscreen(TRUE);
634 /* game over, and highscore is achieved.
635 * ask for name, and if given, record it! */
636 static void
637 game_over_highscore()
639 char *text;
640 int rank;
642 text=g_strdup_printf(_("You have %d points, and achieved a highscore."), main_window.game->player_score);
643 gd_infomessage(_("Game over!"), text);
644 g_free(text);
646 /* enter to highscore table */
647 rank=gd_add_highscore(gd_caveset_data->highscore, main_window.game->player_name, main_window.game->player_score);
648 gd_show_highscore(main_window.window, NULL, FALSE, NULL, rank);
651 /* game over, but no highscore.
652 * only pops up a simple dialog box. */
653 static void
654 game_over_without_highscore()
656 gchar *text;
658 text=g_strdup_printf(_("You have %d points."), main_window.game->player_score);
659 gd_infomessage(_("Game over!"), text);
660 g_free(text);
665 /* amoeba state to string */
666 static const char *
667 amoeba_state_string(GdAmoebaState a)
669 switch(a) {
670 case GD_AM_SLEEPING: return _("sleeping"); /* sleeping - not yet let out. */
671 case GD_AM_AWAKE: return _("awake"); /* living, growing */
672 case GD_AM_TOO_BIG: return _("too big"); /* grown too big, will convert to stones */
673 case GD_AM_ENCLOSED: return _("enclosed"); /* enclosed, will convert to diamonds */
675 return _("unknown");
678 /* amoeba state to string */
679 static const char *
680 magic_wall_state_string(GdMagicWallState m)
682 switch(m) {
683 case GD_MW_DORMANT: return _("dormant");
684 case GD_MW_ACTIVE: return _("active");
685 case GD_MW_EXPIRED: return _("expired");
687 return _("unknown");
692 static void
693 main_int_set_labels()
695 const GdCave *cave=main_window.game->cave;
696 int time;
698 /* lives reamining in game. */
699 /* not trivial, but this sometimes DOES change during the running of a cave. */
700 /* as when playing a replay, it might change from playing replay to continuing replay. */
701 switch (main_window.game->type) {
702 /* for a normal cave, show number of lives or "bonus life" if it is an intermission */
703 case GD_GAMETYPE_NORMAL:
704 if (!cave->intermission)
705 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Lives: <b>%d</b>"), main_window.game->player_lives);
706 else
707 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("<b>Bonus life</b>"));
708 break;
710 /* for other game types, the number of lives is zero. so show the game type */
711 case GD_GAMETYPE_SNAPSHOT:
712 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Continuing from <b>snapshot</b>"));
713 break;
714 case GD_GAMETYPE_TEST:
715 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("<b>Testing</b> cave"));
716 break;
717 case GD_GAMETYPE_REPLAY:
718 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Playing <b>replay</b>"));
719 break;
720 case GD_GAMETYPE_CONTINUE_REPLAY:
721 gd_label_set_markup_printf(GTK_LABEL(main_window.label_topright), _("Continuing <b>replay</b>"));
722 break;
726 /* SECOND ROW OF STATUS BAR. */
727 /* if the user is not pressing the left shift, show the normal status bar. otherwise, show the alternative. */
728 if (!key_alt_status_bar) {
729 /* NORMAL STATUS BAR */
730 /* diamond value */
731 if (cave->diamonds_needed>0)
732 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);
733 else
734 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 ??< */
736 /* show time & score */
737 time=gd_cave_time_show(cave, cave->time);
738 if (gd_time_min_sec)
739 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);
740 else
741 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);
742 } else {
743 /* ALTERNATIVE STATUS BAR */
744 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);
745 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));
748 if (gd_editor_window && gd_show_test_label) {
749 gd_label_set_markup_printf(GTK_LABEL(main_window.label_variables),
750 _("Speed: %dms, Amoeba 1: %ds %s, 2: %ds %s, Magic wall: %ds %s\n"
751 "Expanding wall: %s, Creatures: %ds, %s, Gravity: %s\n"
752 "Kill player: %s, Sweet eaten: %s, Diamond key: %s, Diamonds: %d"),
753 cave->speed,
754 gd_cave_time_show(cave, cave->amoeba_time),
755 amoeba_state_string(cave->amoeba_state),
756 gd_cave_time_show(cave, cave->amoeba_2_time),
757 amoeba_state_string(cave->amoeba_2_state),
758 gd_cave_time_show(cave, cave->magic_wall_time),
759 magic_wall_state_string(cave->magic_wall_state),
760 cave->expanding_wall_changed?_("vertical"):_("horizontal"),
761 gd_cave_time_show(cave, cave->creatures_direction_will_change),
762 cave->creatures_backwards?_("backwards"):_("forwards"),
763 gettext(gd_direction_get_visible_name(cave->gravity_disabled?MV_STILL:cave->gravity)),
764 cave->kill_player?_("yes"):_("no"),
765 cave->sweet_eaten?_("yes"):_("no"),
766 cave->diamond_key_collected?_("yes"):_("no"),
767 cave->diamonds_collected
773 /* THE MAIN GAME TIMER */
774 /* called at 50hz */
775 /* for the gtk version, it seems nicer if we first draw, then scroll. */
776 /* this is because there is an expose event; scrolling the "old" drawing would draw the old, and then the new. */
777 /* (this is not true for the sdl version) */
778 #if 0
779 static GTimer *timer=NULL;
780 static int called=0;
781 #endif
785 /* SCROLLING
787 * scrolls to the player during game play.
789 static void
790 main_int_scroll()
792 static int scroll_desired_x=0, scroll_desired_y=0;
793 GtkAdjustment *adjustment;
794 int scroll_center_x, scroll_center_y;
795 gboolean out_of_window=FALSE;
796 gboolean changed;
797 int i;
798 int player_x, player_y;
799 const GdCave *cave=main_window.game->cave;
800 gboolean exact_scroll=FALSE; /* to avoid compiler warning */
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/5; /* window usually smaller vertically, so let the region be also smaller than the horizontal one */
806 int scroll_to_y=main_window.scroll_window->allocation.height/10;
807 int scroll_speed;
809 /* if cave not yet rendered, return. (might be the case for 50hz scrolling */
810 if (main_window.game==NULL || main_window.game->cave==NULL)
811 return;
812 if (paused && main_window.game->cave->player_state!=GD_PL_NOT_YET)
813 return; /* no scrolling when pause button is pressed, BUT ALLOW SCROLLING when the player is not yet born */
815 /* max scrolling speed depends on the speed of the cave. */
816 /* game moves cell_size_game* 1s/cave time pixels in a second. */
817 /* scrolling moves scroll speed * 1s/scroll_time in a second. */
818 /* these should be almost equal; scrolling speed a little slower. */
819 /* that way, the player might reach the border with a small probability, */
820 /* but the scrolling will not "oscillate", ie. turn on for little intervals as it has */
821 /* caught up with the desired position. smaller is better. */
822 scroll_speed=gd_cell_size_game*(gd_fine_scroll?20:40)/cave->speed;
824 /* check player state. */
825 switch (main_window.game->cave->player_state) {
826 case GD_PL_NOT_YET:
827 exact_scroll=TRUE;
828 break;
830 case GD_PL_LIVING:
831 case GD_PL_EXITED:
832 exact_scroll=FALSE;
833 break;
835 case GD_PL_TIMEOUT:
836 case GD_PL_DIED:
837 /* do not scroll when the player is dead or cave time is over. */
838 return; /* return from function */
841 player_x=cave->player_x-cave->x1;
842 player_y=cave->player_y-cave->y1;
844 /* get the size of the window so we know where to place player.
845 * first guess is the middle of the screen.
846 * main_window.drawing_area->parent->parent is the viewport.
847 * +cellsize/2 gets the stomach of player :) so the very center */
848 scroll_center_x=player_x*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.width/2;
849 scroll_center_y=player_y*gd_cell_size_game + gd_cell_size_game/2-main_window.drawing_area->parent->parent->allocation.height/2;
851 /* HORIZONTAL */
852 /* hystheresis function.
853 * when scrolling left, always go a bit less left than player being at the middle.
854 * when scrolling right, always go a bit less to the right. */
855 adjustment=gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
856 if (exact_scroll)
857 scroll_desired_x=scroll_center_x;
858 else {
859 if (adjustment->value+scroll_start_x<scroll_center_x)
860 scroll_desired_x=scroll_center_x-scroll_to_x;
861 if (adjustment->value-scroll_start_x>scroll_center_x)
862 scroll_desired_x=scroll_center_x+scroll_to_x;
864 scroll_desired_x=CLAMP(scroll_desired_x, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
866 changed=FALSE;
867 if (adjustment->value<scroll_desired_x) {
868 for (i=0; i<scroll_speed; i++)
869 if ((int)adjustment->value<scroll_desired_x)
870 adjustment->value++;
871 changed=TRUE;
873 if (adjustment->value>scroll_desired_x) {
874 for (i=0; i<scroll_speed; i++)
875 if ((int)adjustment->value>scroll_desired_x)
876 adjustment->value--;
877 changed=TRUE;
879 if (changed)
880 gtk_adjustment_value_changed (adjustment);
882 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
883 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)
884 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
885 if (cave->player_x>=cave->x1 && cave->player_x<=cave->x2)
886 out_of_window=TRUE;
891 /* VERTICAL */
892 adjustment=gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(main_window.scroll_window));
893 if (exact_scroll)
894 scroll_desired_y=scroll_center_y;
895 else {
896 if (adjustment->value+scroll_start_y<scroll_center_y)
897 scroll_desired_y=scroll_center_y-scroll_to_y;
898 if (adjustment->value-scroll_start_y>scroll_center_y)
899 scroll_desired_y=scroll_center_y+scroll_to_y;
901 scroll_desired_y=CLAMP(scroll_desired_y, 0, adjustment->upper-adjustment->step_increment-adjustment->page_increment);
903 changed=FALSE;
904 if (adjustment->value<scroll_desired_y) {
905 for (i=0; i<scroll_speed; i++)
906 if ((int)adjustment->value<scroll_desired_y)
907 adjustment->value++;
908 changed=TRUE;
910 if (adjustment->value > scroll_desired_y) {
911 for (i=0; i<scroll_speed; i++)
912 if ((int)adjustment->value>scroll_desired_y)
913 adjustment->value--;
914 changed=TRUE;
916 if (changed)
917 gtk_adjustment_value_changed(adjustment);
919 /* check if active player is outside drawing area. if yes, we should wait for scrolling */
920 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)
921 /* but only do the wait, if the player SHOULD BE visible, ie. he is inside the defined visible area of the cave */
922 if (cave->player_y>=cave->y1 && cave->player_y<=cave->y2)
923 out_of_window=TRUE;
925 /* remember if player is visible inside window */
926 main_window.game->out_of_window=out_of_window;
928 /* 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. */
929 if (main_window.game->cave->player_state==GD_PL_NOT_YET)
930 main_window.game->out_of_window=FALSE;
932 /* XXX gdk_window_process_updates(main_window.drawing_area->window, TRUE); */
935 /* the timing thread runs in a separate thread. this variable is set to true,
936 * then the function exits (and also the thread.) */
937 static gboolean main_int_quit_thread=FALSE;
939 static gboolean
940 main_int(gpointer data)
942 static gboolean toggle=FALSE; /* value irrelevant */
943 int up, down, left, right;
944 GdDirection player_move;
945 gboolean fire;
946 GdGameState state;
948 #if 0
949 called++;
950 if (called%100==0)
951 g_message("%8d. call, avg %gms", called, g_timer_elapsed(timer, NULL)*1000/called);
952 #endif
954 if (main_window.game->type==GD_GAMETYPE_REPLAY)
955 gtk_widget_show(main_window.replay_image_align);
956 else
957 gtk_widget_hide(main_window.replay_image_align);
959 up=key_up;
960 down=key_down;
961 left=key_left;
962 right=key_right;
963 fire=key_fire_1 || key_fire_2;
965 /* compare mouse coordinates to player coordinates, and make up movements */
966 if (gd_mouse_play && mouse_cell_x>=0) {
967 down=down || (main_window.game->cave->player_y<mouse_cell_y);
968 up=up || (main_window.game->cave->player_y>mouse_cell_y);
969 left=left || (main_window.game->cave->player_x>mouse_cell_x);
970 right=right || (main_window.game->cave->player_x<mouse_cell_x);
971 fire=fire || mouse_cell_click;
974 /* call the game "interrupt" to do all things. */
975 player_move=gd_direction_from_keypress(up, down, left, right);
976 /* tell the interrupt that 20ms has passed. */
977 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);
979 /* the game "interrupt" gives signals to us, which we act upon: update status bar, resize the drawing area... */
980 switch (state) {
981 case GD_GAME_INVALID_STATE:
982 g_assert_not_reached();
983 break;
985 case GD_GAME_SHOW_STORY:
986 main_window_init_story(main_window.game->cave);
987 main_int_set_labels();
988 break;
990 case GD_GAME_CAVE_LOADED:
991 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);
992 main_window_init_cave(main_window.game->cave);
993 main_int_set_labels();
994 restart=FALSE; /* so we do not remember the restart key from a previous cave run */
995 break;
997 case GD_GAME_NO_MORE_LIVES: /* <- only used by sdl version */
998 case GD_GAME_SHOW_STORY_WAIT: /* <- only used by sdl version */
999 case GD_GAME_NOTHING:
1000 /* normally continue. */
1001 break;
1003 case GD_GAME_LABELS_CHANGED:
1004 case GD_GAME_TIMEOUT_NOW: /* <- maybe we should do something else for this */
1005 /* normal, but we are told that the labels (score, ...) might have changed. */
1006 main_int_set_labels();
1007 break;
1009 case GD_GAME_STOP:
1010 gd_main_stop_game();
1011 return FALSE; /* do not call again - it will be created later */
1013 case GD_GAME_GAME_OVER:
1014 main_stop_game_but_maybe_highscore();
1015 if (gd_is_highscore(gd_caveset_data->highscore, main_window.game->player_score))
1016 game_over_highscore(); /* achieved a high score! */
1017 else
1018 game_over_without_highscore(); /* no high score */
1019 main_free_game();
1020 return FALSE; /* do not call again - it will be created later */
1023 /* if drawing area already exists, draw cave. */
1024 /* remember that the drawings are cached, so if we did no change, this will barely do anything - so will not slow down. */
1025 if (main_window.drawing_area)
1026 schedule_draw();
1027 /* do the scrolling at the given interval. */
1028 /* but only if the drawing area already exists. */
1029 /* if fine scrolling, drawing is called at a 50hz rate. */
1030 /* if not, only at a 25hz rate */
1031 toggle=!toggle;
1032 if (main_window.drawing_area && (gd_fine_scroll || toggle))
1033 main_int_scroll();
1035 return TRUE; /* call again */
1038 /* this is a simple wrapper which makes the main_int to be callable as an idle func. */
1039 /* when used as an idle func by the thread routine, it should run only once for every g_idle_add */
1040 static gboolean
1041 main_int_return_false_wrapper(gpointer data)
1043 main_int(data);
1044 return FALSE;
1047 /* this function will run in its own thread, and add the main int as an idle func in every 20ms */
1048 static gpointer
1049 main_int_timer_thread(gpointer data)
1051 int interval_msec=20;
1053 /* wait before we first call it */
1054 g_usleep(interval_msec*1000);
1055 while (!main_int_quit_thread) {
1056 /* add processing as an idle func */
1057 /* no need to lock the main loop context, as glib does that automatically */
1058 g_idle_add(main_int_return_false_wrapper, data);
1060 g_usleep(interval_msec*1000);
1062 return NULL;
1066 uninstall game timers, if any installed.
1068 static void
1069 main_int_uninstall_timer()
1071 main_int_quit_thread=TRUE;
1072 /* remove timeout associated to game play */
1073 while (g_source_remove_by_user_data(main_window.window)) {
1074 /* nothing */
1079 static void
1080 main_int_install_timer()
1082 GThread *thread;
1083 GError *error=NULL;
1085 /* remove timer, if it is installed for some reason */
1086 main_int_uninstall_timer();
1088 if (!paused)
1089 gtk_window_present(GTK_WINDOW(main_window.window));
1091 #if 0
1092 if (!timer)
1093 timer=g_timer_new();
1094 #endif
1096 /* this makes the main int load the first cave, and then we do the drawing. */
1097 main_int(main_window.window);
1098 gdk_window_process_all_updates(); /* so resizes will be done (?) */
1099 /* after that, install timer. create a thread with higher priority than normal: */
1100 /* so its priority will be higher than the main thread, which does the drawing etc. */
1101 /* if the scheduling thread wants to do something, it gets processed first. this makes */
1102 /* the intervals more even. */
1103 main_int_quit_thread=FALSE;
1104 #ifdef G_THREADS_ENABLED
1105 thread=g_thread_create_full(main_int_timer_thread, main_window.window, 0, FALSE, FALSE, G_THREAD_PRIORITY_HIGH, &error);
1106 #else
1107 thread=NULL; /* if glib without thread support, we will use timeout source. */
1108 #endif
1109 if (!thread) {
1110 /* if unable to create thread */
1111 if (error) {
1112 g_critical("%s", error->message);
1113 g_error_free(error);
1115 /* use the main int as a timeout routine. */
1116 g_timeout_add(20, main_int, main_window.window);
1119 #if 0
1120 g_timer_start(timer);
1121 called=0;
1122 #endif
1125 static void
1126 main_stop_game_but_maybe_highscore()
1128 main_int_uninstall_timer();
1129 gd_sound_off(); /* hack for game over dialog */
1131 main_window_init_title();
1132 /* if editor is active, go back to its window. */
1133 if (gd_editor_window)
1134 gtk_window_present(GTK_WINDOW(gd_editor_window));
1137 static void
1138 main_free_game()
1140 if (main_window.game) {
1141 gd_game_free(main_window.game);
1142 main_window.game=NULL;
1146 void
1147 gd_main_stop_game()
1149 main_stop_game_but_maybe_highscore();
1150 main_free_game();
1155 /* this starts a new game */
1156 static void
1157 main_new_game(const char *player_name, const int cave, const int level)
1159 if (main_window.game)
1160 gd_main_stop_game();
1162 main_window.game=gd_game_new(player_name, cave, level);
1163 main_int_install_timer();
1167 static void
1168 main_new_game_snapshot(GdCave *snapshot)
1170 if (main_window.game)
1171 gd_main_stop_game();
1173 main_window.game=gd_game_new_snapshot(snapshot);
1174 main_int_install_timer();
1177 /* the new game for testing a level is global, not static.
1178 * it is used by the editor. */
1179 void
1180 gd_main_new_game_test(GdCave *test, int level)
1182 if (main_window.game)
1183 gd_main_stop_game();
1185 main_window.game=gd_game_new_test(test, level);
1186 main_int_install_timer();
1190 static void
1191 main_new_game_replay(GdCave *cave, GdReplay *replay)
1193 if (main_window.game)
1194 gd_main_stop_game();
1196 main_window.game=gd_game_new_replay(cave, replay);
1197 main_int_install_timer();
1208 * set the main window title from the caveset name.
1209 * made global, as the editor might ocassionally call it.
1212 void
1213 gd_main_window_set_title()
1215 if (!g_str_equal(gd_caveset_data->name, "")) {
1216 char *text;
1218 text=g_strdup_printf("GDash - %s", gd_caveset_data->name);
1219 gtk_window_set_title (GTK_WINDOW(main_window.window), text);
1220 g_free (text);
1222 else
1223 gtk_window_set_title (GTK_WINDOW(main_window.window), "GDash");
1230 /**********************************-
1232 * CALLBACKS
1236 static void
1237 help_cb(GtkWidget * widget, gpointer data)
1239 gd_show_game_help (((GdMainWindow *)data)->window);
1242 static void
1243 preferences_cb(GtkWidget *widget, gpointer data)
1245 gd_preferences(((GdMainWindow *)data)->window);
1248 static void
1249 control_settings_cb(GtkWidget *widget, gpointer data)
1251 gd_control_settings(((GdMainWindow *)data)->window);
1254 static void
1255 quit_cb(GtkWidget * widget, const gpointer data)
1257 if (gd_discard_changes(main_window.window))
1258 gtk_main_quit ();
1261 static void
1262 stop_game_cb(GtkWidget *widget, gpointer data)
1264 gd_main_stop_game();
1267 static void
1268 save_snapshot_cb(GtkWidget * widget, gpointer data)
1270 if (snapshot)
1271 gd_cave_free(snapshot);
1273 snapshot=gd_create_snapshot(main_window.game);
1274 gtk_action_group_set_sensitive (main_window.actions_snapshot, snapshot!=NULL);
1277 static void
1278 load_snapshot_cb(GtkWidget * widget, gpointer data)
1280 g_return_if_fail(snapshot!=NULL);
1281 main_new_game_snapshot(snapshot);
1284 /* restart level button clicked */
1285 static void
1286 restart_level_cb(GtkWidget * widget, gpointer data)
1288 g_return_if_fail(main_window.game!=NULL);
1289 g_return_if_fail(main_window.game->cave!=NULL);
1290 /* sets the restart variable, which will be interpreted by the iterate routine */
1291 restart=TRUE;
1294 static void
1295 about_cb(GtkWidget *widget, gpointer data)
1297 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);
1300 static void
1301 open_caveset_cb(GtkWidget * widget, gpointer data)
1303 gd_open_caveset(main_window.window, NULL);
1304 main_window_init_title();
1307 static void
1308 open_caveset_dir_cb(GtkWidget * widget, gpointer data)
1310 gd_open_caveset(main_window.window, gd_system_caves_dir);
1311 main_window_init_title();
1314 static void
1315 save_caveset_as_cb(GtkWidget * widget, gpointer data)
1317 gd_save_caveset_as(main_window.window);
1320 static void
1321 save_caveset_cb(GtkWidget * widget, gpointer data)
1323 gd_save_caveset(main_window.window);
1326 /* load internal game from the executable. those are inlined in caveset.c. */
1327 static void
1328 load_internal_cb(GtkWidget * widget, gpointer data)
1330 gd_load_internal(main_window.window, GPOINTER_TO_INT(data));
1331 main_window_init_title();
1334 static void
1335 highscore_cb(GtkWidget *widget, gpointer data)
1337 gd_show_highscore(main_window.window, NULL, FALSE, NULL, -1);
1342 static void
1343 show_errors_cb(GtkWidget *widget, gpointer data)
1345 gtk_widget_hide(main_window.info_bar); /* if the user is presented the error list, the label is to be hidden */
1346 gd_show_errors(main_window.window);
1349 static void
1350 cave_editor_cb()
1352 gd_open_cave_editor();
1353 /* to be sure no cave is playing. */
1354 /* this will also stop music. to be called after opening editor window, so the music stops. */
1355 main_window_init_title();
1358 /* called from the menu when a recent file is activated. */
1359 static void
1360 recent_chooser_activated_cb(GtkRecentChooser *chooser, gpointer data)
1362 GtkRecentInfo *current;
1363 char *filename_utf8, *filename;
1365 current=gtk_recent_chooser_get_current_item(chooser);
1366 /* we do not support non-local files */
1367 if (!gtk_recent_info_is_local(current)) {
1368 char *display_name;
1370 display_name=gtk_recent_info_get_uri_display(current);
1371 gd_errormessage(_("GDash cannot load file from a network link."), display_name);
1372 g_free(display_name);
1373 return;
1376 /* if the edited caveset is to be saved, but user cancels */
1377 if (!gd_discard_changes(main_window.window))
1378 return;
1380 filename_utf8=gtk_recent_info_get_uri_display(current);
1381 filename=g_filename_from_utf8(filename_utf8, -1, NULL, NULL, NULL);
1382 /* ask for save first? */
1383 gd_open_caveset_in_ui(filename, gd_use_bdcff_highscore);
1385 /* things to do after loading. */
1386 main_window_init_title();
1387 gd_infomessage(_("Loaded caveset from file:"), filename_utf8);
1389 g_free(filename);
1390 g_free(filename_utf8);
1395 static void
1396 toggle_pause_cb(GtkWidget * widget, gpointer data)
1398 paused=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
1400 if (paused)
1401 gd_sound_off();
1404 static void
1405 toggle_fullscreen_cb (GtkWidget * widget, gpointer data)
1407 fullscreen=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget));
1408 main_window_set_fullscreen(main_window.game!=NULL); /* we do not exactly know if in game, but try to guess */
1411 static void
1412 toggle_fast_cb (GtkWidget * widget, gpointer data)
1414 fast_forward=gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (widget));
1426 * START NEW GAME DIALOG
1428 * show a dialog to the user so he can select the cave to start game at.
1432 typedef struct _new_game_dialog {
1433 GtkWidget *dialog;
1434 GtkWidget *combo_cave;
1435 GtkWidget *spin_level;
1436 GtkWidget *entry_name;
1438 GtkWidget *image;
1439 } NewGameDialog;
1441 /* keypress. key_* can be event_type==gdk_key_press, as we
1442 connected this function to key press and key release.
1444 static gboolean
1445 new_game_keypress_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1447 NewGameDialog *jump_dialog=(NewGameDialog *)data;
1448 int level=gtk_range_get_value (GTK_RANGE(jump_dialog->spin_level));
1450 switch (event->keyval) {
1451 case GDK_Left:
1452 level--;
1453 if (level<1)
1454 level=1;
1455 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
1456 return TRUE;
1457 case GDK_Right:
1458 level++;
1459 if (level>5)
1460 level=5;
1461 gtk_range_set_value (GTK_RANGE(jump_dialog->spin_level), level);
1462 return TRUE;
1463 case GDK_Return:
1464 gtk_dialog_response(GTK_DIALOG(jump_dialog->dialog), GTK_RESPONSE_ACCEPT);
1465 return TRUE;
1467 return FALSE; /* if any other key, we did not process it. go on, let gtk handle it. */
1471 /* update pixbuf */
1472 static void
1473 new_game_update_preview(GtkWidget *widget, gpointer data)
1475 NewGameDialog *jump_dialog=(NewGameDialog *)data;
1476 GdkPixbuf *cave_image;
1477 GdCave *cave;
1479 /* loading cave, draw cave and scale to specified size. seed=0 */
1480 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);
1481 cave_image=gd_drawcave_to_pixbuf(cave, 320, 240, TRUE, TRUE);
1482 gtk_image_set_from_pixbuf(GTK_IMAGE (jump_dialog->image), cave_image);
1483 g_object_unref(cave_image);
1485 /* freeing temporary cave data */
1486 gd_cave_free(cave);
1489 static void
1490 new_game_cb (const GtkWidget * widget, const gpointer data)
1492 static GdString player_name="";
1493 GtkWidget *table, *expander, *eventbox;
1494 NewGameDialog jump_dialog;
1495 GtkCellRenderer *renderer;
1496 GtkListStore *store;
1497 GList *iter;
1499 /* check if caveset is empty! */
1500 if (gd_caveset_count()==0) {
1501 gd_warningmessage(_("There are no caves in this cave set!"), NULL);
1502 return;
1505 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);
1506 gtk_dialog_set_default_response (GTK_DIALOG(jump_dialog.dialog), GTK_RESPONSE_ACCEPT);
1507 gtk_window_set_resizable (GTK_WINDOW(jump_dialog.dialog), FALSE);
1509 table=gtk_table_new(0, 0, FALSE);
1510 gtk_box_pack_start(GTK_BOX (GTK_DIALOG(jump_dialog.dialog)->vbox), table, FALSE, FALSE, 0);
1511 gtk_container_set_border_width(GTK_CONTAINER (table), 6);
1512 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1513 gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1515 /* name, which will be used for highscore & the like */
1516 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Name:")), 0, 1, 0, 1);
1517 if (g_str_equal(player_name, ""))
1518 gd_strcpy(player_name, g_get_real_name());
1519 jump_dialog.entry_name=gtk_entry_new();
1520 /* little inconsistency below: max length has unicode characters, while gdstring will have utf-8.
1521 however this does not make too much difference */
1522 gtk_entry_set_max_length(GTK_ENTRY(jump_dialog.entry_name), sizeof(GdString));
1523 gtk_entry_set_activates_default(GTK_ENTRY(jump_dialog.entry_name), TRUE);
1524 gtk_entry_set_text(GTK_ENTRY(jump_dialog.entry_name), player_name);
1525 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.entry_name, 1, 2, 0, 1);
1527 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Cave:")), 0, 1, 1, 2);
1529 /* store of caves: cave pointer, cave name, selectable */
1530 store=gtk_list_store_new(3, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1531 for (iter=gd_caveset; iter; iter=g_list_next (iter)) {
1532 GdCave *cave=iter->data;
1533 GtkTreeIter treeiter;
1535 gtk_list_store_insert_with_values (store, &treeiter, -1, 0, iter->data, 1, cave->name, 2, cave->selectable || gd_all_caves_selectable, -1);
1537 jump_dialog.combo_cave=gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
1538 g_object_unref(store);
1540 renderer=gtk_cell_renderer_text_new();
1541 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, TRUE);
1542 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(jump_dialog.combo_cave), renderer, "text", 1, "sensitive", 2, NULL);
1543 /* we put the combo in an event box, so we can receive keypresses on our own */
1544 eventbox=gtk_event_box_new();
1545 gtk_container_add(GTK_CONTAINER(eventbox), jump_dialog.combo_cave);
1546 gtk_table_attach_defaults(GTK_TABLE(table), eventbox, 1, 2, 1, 2);
1548 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Level:")), 0, 1, 2, 3);
1549 jump_dialog.spin_level=gtk_hscale_new_with_range(1.0, 5.0, 1.0);
1550 gtk_range_set_increments(GTK_RANGE(jump_dialog.spin_level), 1.0, 1.0);
1551 gtk_scale_set_value_pos(GTK_SCALE(jump_dialog.spin_level), GTK_POS_LEFT);
1552 gtk_table_attach_defaults(GTK_TABLE(table), jump_dialog.spin_level, 1, 2, 2, 3);
1554 g_signal_connect(G_OBJECT(jump_dialog.combo_cave), "changed", G_CALLBACK(new_game_update_preview), &jump_dialog);
1555 gtk_widget_add_events(eventbox, GDK_KEY_PRESS_MASK);
1556 g_signal_connect(G_OBJECT(eventbox), "key_press_event", G_CALLBACK(new_game_keypress_event), &jump_dialog);
1557 g_signal_connect(G_OBJECT(jump_dialog.spin_level), "value-changed", G_CALLBACK(new_game_update_preview), &jump_dialog);
1559 /* this allows the user to select if he wants to see a preview of the cave */
1560 expander=gtk_expander_new(_("Preview"));
1561 gtk_expander_set_expanded(GTK_EXPANDER (expander), gd_show_preview);
1562 gtk_table_attach_defaults(GTK_TABLE(table), expander, 0, 2, 3, 4);
1563 jump_dialog.image=gtk_image_new();
1564 gtk_container_add(GTK_CONTAINER (expander), jump_dialog.image);
1566 gtk_widget_show_all(jump_dialog.dialog);
1567 gtk_widget_grab_focus(jump_dialog.combo_cave);
1568 gtk_editable_select_region(GTK_EDITABLE(jump_dialog.entry_name), 0, 0);
1569 /* set default and also trigger redrawing */
1570 gtk_combo_box_set_active(GTK_COMBO_BOX(jump_dialog.combo_cave), gd_caveset_last_selected);
1571 gtk_range_set_value(GTK_RANGE(jump_dialog.spin_level), 1);
1573 if (gtk_dialog_run (GTK_DIALOG(jump_dialog.dialog))==GTK_RESPONSE_ACCEPT) {
1574 gd_strcpy(player_name, gtk_entry_get_text(GTK_ENTRY(jump_dialog.entry_name)));
1575 gd_caveset_last_selected=gtk_combo_box_get_active(GTK_COMBO_BOX(jump_dialog.combo_cave));
1576 gd_caveset_last_selected_level=gtk_range_get_value(GTK_RANGE(jump_dialog.spin_level))-1;
1577 main_new_game (player_name, gd_caveset_last_selected, gd_caveset_last_selected_level);
1579 gd_show_preview=gtk_expander_get_expanded(GTK_EXPANDER(expander)); /* remember expander state-even if cancel pressed */
1580 gtk_widget_destroy(jump_dialog.dialog);
1594 enum _replay_fields {
1595 COL_REPLAY_CAVE_POINTER,
1596 COL_REPLAY_REPLAY_POINTER,
1597 COL_REPLAY_NAME, /* cave or player name */
1598 COL_REPLAY_LEVEL, /* level the replay is played on */
1599 COL_REPLAY_DATE,
1600 COL_REPLAY_SCORE,
1601 COL_REPLAY_SUCCESS,
1602 COL_REPLAY_SAVED,
1603 COL_REPLAY_COMMENT, /* set to a gtk stock icon if it has a comment */
1604 COL_REPLAY_VISIBLE, /* set to true for replay lines, false for cave lines. so "saved" toggle and comment are not visible. */
1605 COL_REPLAY_MAX,
1608 static void
1609 show_replays_tree_view_row_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1611 GtkTreeModel *model=gtk_tree_view_get_model(view);
1612 GtkTreeIter iter;
1613 GdCave *cave;
1614 GdReplay *replay;
1616 gtk_tree_model_get_iter(model, &iter, path);
1617 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1618 if (cave!=NULL && replay!=NULL)
1619 main_new_game_replay(cave, replay);
1622 static void
1623 show_replays_dialog_response(GtkDialog *dialog, int response_id, gpointer data)
1625 gtk_widget_destroy(GTK_WIDGET(dialog));
1628 static void
1629 show_replays_saved_toggled(GtkCellRendererText *cell, const gchar *path_string, gpointer data)
1631 GtkTreeModel *model=(GtkTreeModel *)data;
1632 GtkTreePath *path=gtk_tree_path_new_from_string(path_string);
1633 GtkTreeIter iter;
1634 GdReplay *replay;
1636 /* get replay. */
1637 gtk_tree_model_get_iter (model, &iter, path);
1638 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1639 /* if not available, maybe the user edited a cave line? do nothing. */
1640 if (!replay)
1641 return;
1643 replay->saved=!replay->saved;
1644 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_SAVED, replay->saved, -1);
1645 /* we selected or unselected a replay for saving - the caveset is now edited. */
1646 gd_caveset_edited=TRUE;
1649 static void
1650 show_replays_play_button_clicked(GtkWidget *widget, gpointer data)
1652 GtkTreeView *view=GTK_TREE_VIEW(data);
1653 GtkTreeSelection *selection=gtk_tree_view_get_selection(view);
1654 GtkTreeModel *model;
1655 gboolean got_selected;
1656 GtkTreeIter iter;
1657 GdReplay *replay;
1658 GdCave *cave;
1660 got_selected=gtk_tree_selection_get_selected(selection, &model, &iter);
1661 if (!got_selected) /* if nothing selected, return */
1662 return;
1664 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1665 if (cave!=NULL && replay!=NULL)
1666 main_new_game_replay(cave, replay);
1669 /* edit a replay comment for a given cave and a given replay.
1670 return true if comment is non empty.
1672 static gboolean
1673 show_replays_edit_comment(GtkWindow *parent, GdCave *cave, GdReplay *replay)
1675 GtkWidget *dialog;
1676 GtkTextBuffer *buffer;
1677 GtkWidget *text, *sw;
1678 int response;
1680 dialog=gtk_dialog_new_with_buttons(_("Edit Replay Comment"), parent,
1681 GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_NO_SEPARATOR,
1682 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1683 GTK_STOCK_OK, GTK_RESPONSE_OK,
1684 NULL);
1685 gtk_window_set_default_size(GTK_WINDOW(dialog), 480, 360);
1686 sw=gtk_scrolled_window_new(NULL, NULL);
1687 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1688 buffer=gtk_text_buffer_new(NULL);
1689 text=gtk_text_view_new_with_buffer(buffer);
1690 gtk_text_buffer_set_text(buffer, replay->comment->str, -1);
1691 gtk_container_add(GTK_CONTAINER(sw), text);
1692 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), sw);
1694 gtk_widget_show_all(dialog);
1695 response=gtk_dialog_run(GTK_DIALOG(dialog));
1696 if (response==GTK_RESPONSE_OK) {
1697 GtkTextIter iter_start, iter_end;
1698 char *text;
1700 gtk_text_buffer_get_iter_at_offset(buffer, &iter_start, 0);
1701 gtk_text_buffer_get_iter_at_offset(buffer, &iter_end, -1);
1702 text=gtk_text_buffer_get_text(buffer, &iter_start, &iter_end, TRUE);
1703 g_string_assign(replay->comment, text);
1704 gd_caveset_edited=TRUE;
1705 g_free(text);
1707 gtk_widget_destroy(dialog);
1708 return replay->comment->len!=0;
1711 static void
1712 show_replays_edit_button_clicked(GtkWidget *widget, gpointer data)
1714 GtkTreeView *view=GTK_TREE_VIEW(data);
1715 GtkTreeSelection *selection=gtk_tree_view_get_selection(view);
1716 GtkTreeModel *model;
1717 gboolean got_selected;
1718 GtkTreeIter iter;
1719 GdReplay *replay;
1720 GdCave *cave;
1722 got_selected=gtk_tree_selection_get_selected(selection, &model, &iter);
1723 if (!got_selected) /* if nothing selected, return */
1724 return;
1726 gtk_tree_model_get(model, &iter, COL_REPLAY_CAVE_POINTER, &cave, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1727 if (cave!=NULL && replay!=NULL) {
1728 gboolean has_comment;
1730 has_comment=show_replays_edit_comment(GTK_WINDOW(gtk_widget_get_toplevel(widget)), cave, replay);
1731 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_REPLAY_COMMENT, has_comment?GTK_STOCK_EDIT:"", -1);
1737 /* enables or disables play button on selection change */
1738 static void
1739 show_replays_tree_view_selection_changed(GtkTreeSelection *selection, gpointer data)
1741 GtkTreeModel *model;
1742 gboolean enable;
1743 GtkTreeIter iter;
1745 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1746 GdReplay *replay;
1748 gtk_tree_model_get(model, &iter, COL_REPLAY_REPLAY_POINTER, &replay, -1);
1749 enable=replay!=NULL;
1750 } else
1751 enable=FALSE;
1753 gtk_widget_set_sensitive(GTK_WIDGET(data), enable);
1756 static void
1757 show_replays_cb(GtkWidget *widget, gpointer data)
1759 static GtkWidget *dialog=NULL;
1760 GtkWidget *scroll, *view, *button;
1761 GList *iter;
1762 GtkTreeStore *model;
1763 GtkCellRenderer *renderer;
1765 /* if window already open, just show it and return */
1766 if (dialog) {
1767 gtk_window_present(GTK_WINDOW(dialog));
1768 return;
1771 dialog=gtk_dialog_new_with_buttons(_("Replays"), GTK_WINDOW(main_window.window), 0, NULL);
1772 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(gtk_widget_destroyed), &dialog);
1773 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(show_replays_dialog_response), NULL);
1774 gtk_window_set_default_size(GTK_WINDOW(dialog), 480, 360);
1776 gd_dialog_add_hint(GTK_DIALOG(dialog), _("Hint: When watching a replay, you can use the usual movement keys (left, right...) to "
1777 "stop the replay and immediately continue the playing of the cave yourself."));
1779 /* scrolled window to show replays tree view */
1780 scroll=gtk_scrolled_window_new(NULL, NULL);
1781 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_ETCHED_IN);
1782 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scroll, TRUE, TRUE, 6);
1784 /* create store containing replays */
1785 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);
1786 for (iter=gd_caveset; iter!=NULL; iter=iter->next) {
1787 GList *replayiter;
1788 GdCave *cave=(GdCave *)iter->data;
1790 /* if the cave has replays */
1791 if (cave->replays) {
1792 GtkTreeIter caveiter;
1794 gtk_tree_store_append(model, &caveiter, NULL);
1795 gtk_tree_store_set(model, &caveiter, COL_REPLAY_NAME, cave->name, -1);
1797 /* add each replay */
1798 for (replayiter=cave->replays; replayiter!=NULL; replayiter=replayiter->next) {
1799 GtkTreeIter riter;
1800 GdReplay *replay=(GdReplay *)replayiter->data;
1801 char score[20];
1802 const char *imagestock;
1804 /* we have to store the score as string, as for the cave lines the unset score field would also show zero */
1805 g_snprintf(score, sizeof(score), "%d", replay->score);
1806 gtk_tree_store_append(model, &riter, &caveiter);
1807 if (replay->wrong_checksum)
1808 imagestock=GTK_STOCK_DIALOG_ERROR;
1809 else
1810 imagestock=replay->success?GTK_STOCK_YES:GTK_STOCK_NO;
1811 gtk_tree_store_set(model, &riter, COL_REPLAY_CAVE_POINTER, cave, COL_REPLAY_REPLAY_POINTER, replay, COL_REPLAY_NAME, replay->player_name,
1812 COL_REPLAY_LEVEL, replay->level+1,
1813 COL_REPLAY_DATE, replay->date, COL_REPLAY_SCORE, score, COL_REPLAY_SUCCESS, imagestock,
1814 COL_REPLAY_COMMENT, replay->comment->len!=0?GTK_STOCK_EDIT:"", COL_REPLAY_SAVED, replay->saved, COL_REPLAY_VISIBLE, TRUE, -1);
1819 view=gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); /* create tree view which will show data */
1820 gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
1821 gtk_container_add(GTK_CONTAINER(scroll), view);
1823 renderer=gtk_cell_renderer_text_new();
1824 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 0, _("Name"), renderer, "text", COL_REPLAY_NAME, NULL); /* 0 = column number */
1825 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0), TRUE); /* name column expands */
1827 renderer=gtk_cell_renderer_text_new();
1828 /* TRANSLATORS: "Lvl" here stands for Level. Some shorthand should be used.*/
1829 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 */
1830 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0), TRUE); /* name column expands */
1832 renderer=gtk_cell_renderer_text_new();
1833 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 2, _("Date"), renderer, "text", COL_REPLAY_DATE, NULL); /* 1 = column number */
1835 renderer=gtk_cell_renderer_pixbuf_new();
1836 g_object_set(G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, NULL);
1837 /* TRANSLATORS: "S." here stands for Successful. A one-letter shorthand should be used.*/
1838 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 3, _("S."), renderer, "stock-id", COL_REPLAY_SUCCESS, NULL);
1840 renderer=gtk_cell_renderer_text_new();
1841 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 4, _("Score"), renderer, "text", COL_REPLAY_SCORE, NULL);
1843 renderer=gtk_cell_renderer_pixbuf_new();
1844 g_object_set(G_OBJECT(renderer), "stock-size", GTK_ICON_SIZE_MENU, NULL);
1845 /* TRANSLATORS: "C." here stands for Comment. A one-letter shorthand should be used.*/
1846 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 5, _("C."), renderer, "stock-id", COL_REPLAY_COMMENT, NULL);
1848 renderer=gtk_cell_renderer_toggle_new();
1849 g_signal_connect(renderer, "toggled", G_CALLBACK(show_replays_saved_toggled), model);
1850 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), 6, _("Saved"), renderer, "active", COL_REPLAY_SAVED, "visible", COL_REPLAY_VISIBLE, NULL);
1852 /* doubleclick will play the replay */
1853 g_signal_connect(G_OBJECT(view), "row-activated", G_CALLBACK(show_replays_tree_view_row_activated_cb), NULL);
1854 gtk_tree_view_column_set_expand(gtk_tree_view_get_column(GTK_TREE_VIEW(view), 4), TRUE); /* name column expands */
1856 /* play button */
1857 button=gtk_button_new_from_stock(GTK_STOCK_MEDIA_PLAY);
1858 gtk_widget_set_sensitive(button, FALSE); /* not sensitive by default. when the user selects a line, it will be enabled */
1859 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(show_replays_play_button_clicked), view);
1860 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(view))), "changed", G_CALLBACK(show_replays_tree_view_selection_changed), button);
1861 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->action_area), button);
1863 /* comment edit button */
1864 button=gtk_button_new_from_stock(GTK_STOCK_EDIT);
1865 gtk_widget_set_sensitive(button, FALSE); /* not sensitive by default. when the user selects a line, it will be enabled */
1866 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(show_replays_edit_button_clicked), view);
1867 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(view))), "changed", G_CALLBACK(show_replays_tree_view_selection_changed), button);
1868 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->action_area), button);
1871 /* this must be added after the play button, so it is the rightmost one */
1872 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1873 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1875 gtk_widget_show_all(dialog);
1889 /* a cave name is selected, update the text boxt with current cave's data */
1890 static void
1891 cave_info_combo_changed(GtkComboBox *widget, gpointer data)
1893 GtkTextBuffer *buffer=GTK_TEXT_BUFFER(data);
1894 GtkTextIter iter;
1895 int i;
1897 /* clear text buffer */
1898 gtk_text_buffer_set_text(buffer, "", -1);
1899 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
1901 i=gtk_combo_box_get_active(widget);
1902 if (i==0) {
1903 /* cave set data */
1904 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, gd_caveset_data->name, -1, "heading", NULL);
1905 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1907 /* show properties with title only if they are not empty string */
1908 if (!g_str_equal(gd_caveset_data->description, "")) {
1909 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1910 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->description, -1);
1911 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1913 if (!g_str_equal(gd_caveset_data->author, "")) {
1914 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1915 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->author, -1);
1916 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1918 if (!g_str_equal(gd_caveset_data->date, "")) {
1919 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1920 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->date, -1);
1921 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1923 if (!g_str_equal(gd_caveset_data->difficulty, "")) {
1924 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Diffuculty: "), -1, "name", NULL);
1925 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->difficulty, -1);
1926 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1928 if (!g_str_equal(gd_caveset_data->story->str, "")) {
1929 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Story:\n"), -1, "name", NULL);
1930 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->story->str, -1);
1931 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1933 if (!g_str_equal(gd_caveset_data->remark->str, "")) {
1934 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark:\n"), -1, "name", NULL);
1935 gtk_text_buffer_insert(buffer, &iter, gd_caveset_data->remark->str, -1);
1936 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1939 else {
1940 /* cave data */
1941 GdCave *cave=gd_return_nth_cave(i-1);
1943 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, cave->name, -1, "heading", NULL);
1944 gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
1946 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Type: "), -1, "name", NULL);
1947 gtk_text_buffer_insert(buffer, &iter, cave->intermission?_("Intermission"):_("Normal cave"), -1);
1948 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1950 /* show properties with title only if they are not empty string */
1951 if (!g_str_equal(cave->description, "")) {
1952 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Description: "), -1, "name", NULL);
1953 gtk_text_buffer_insert(buffer, &iter, cave->description, -1);
1954 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1956 if (!g_str_equal(cave->author, "")) {
1957 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Author: "), -1, "name", NULL);
1958 gtk_text_buffer_insert(buffer, &iter, cave->author, -1);
1959 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1961 if (!g_str_equal(cave->date, "")) {
1962 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Date: "), -1, "name", NULL);
1963 gtk_text_buffer_insert(buffer, &iter, cave->date, -1);
1964 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1966 if (!g_str_equal(cave->difficulty, "")) {
1967 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Difficulty: "), -1, "name", NULL);
1968 gtk_text_buffer_insert(buffer, &iter, cave->difficulty, -1);
1969 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1972 if (!g_str_equal(cave->story->str, "")) {
1973 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Story:\n"), -1, "name", NULL);
1974 gtk_text_buffer_insert(buffer, &iter, cave->story->str, -1);
1975 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1977 if (!g_str_equal(cave->remark->str, "")) {
1978 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Remark:\n"), -1, "name", NULL);
1979 gtk_text_buffer_insert(buffer, &iter, cave->remark->str, -1);
1980 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1986 /* show info about cave or caveset */
1987 static void
1988 cave_info_cb(GtkWidget *widget, gpointer data)
1990 GtkWidget *dialog, *view, *sw, *combo;
1991 char *text;
1992 GtkTextIter iter;
1993 GtkTextBuffer *buffer;
1994 GList *citer;
1995 gboolean paused_save;
1997 /* dialog window */
1998 dialog=gtk_dialog_new_with_buttons(_("Caveset information"), GTK_WINDOW(main_window.window),
1999 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
2000 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2001 NULL);
2002 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
2003 gtk_window_set_default_size(GTK_WINDOW(dialog), 360, 480);
2005 /* create a combo box. 0=caveset, 1, 2, 3... = caves */
2006 combo=gtk_combo_box_new_text();
2007 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), combo, FALSE, FALSE, 6);
2008 text=g_strdup_printf("[%s]", gd_caveset_data->name); /* caveset name = line 0 */
2009 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
2010 g_free(text);
2011 for (citer=gd_caveset; citer!=NULL; citer=citer->next) {
2012 GdCave *c=citer->data;
2014 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), c->name);
2017 /* create text buffer */
2018 buffer=gtk_text_buffer_new(NULL);
2019 gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
2020 gtk_text_buffer_create_tag (buffer, "heading", "weight", PANGO_WEIGHT_BOLD, "scale", PANGO_SCALE_X_LARGE, NULL);
2021 gtk_text_buffer_create_tag (buffer, "name", "weight", PANGO_WEIGHT_BOLD, NULL);
2022 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, "GDash " PACKAGE_VERSION "\n\n", -1, "heading", NULL);
2024 sw=gtk_scrolled_window_new(NULL, NULL);
2025 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
2026 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2027 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
2028 view=gtk_text_view_new_with_buffer(buffer);
2029 gtk_container_add(GTK_CONTAINER (sw), view);
2030 g_object_unref(buffer);
2031 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
2032 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
2033 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD);
2034 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view), 3);
2035 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 6);
2036 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 6);
2038 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(cave_info_combo_changed), buffer);
2039 if (main_window.game && main_window.game->original_cave)
2040 /* currently playing a cave - show info for that */
2041 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), g_list_index(gd_caveset, main_window.game->original_cave)+1);
2042 else
2043 /* show info for caveset */
2044 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
2047 gtk_widget_show_all(dialog);
2048 paused_save=paused;
2049 paused=TRUE; /* set paused game, so it stops while the users sees the message box */
2050 gtk_dialog_run(GTK_DIALOG(dialog));
2051 gtk_widget_destroy(dialog);
2052 paused=paused_save;
2058 * Creates main window
2062 static void
2063 create_main_window()
2065 /* Menu UI */
2066 static GtkActionEntry action_entries_normal[]={
2067 {"PlayMenu", NULL, N_("_Play")},
2068 {"FileMenu", NULL, N_("_File")},
2069 {"SettingsMenu", NULL, N_("_Settings")},
2070 {"HelpMenu", NULL, N_("_Help")},
2071 {"Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK(quit_cb)},
2072 {"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_cb)},
2073 {"Errors", GTK_STOCK_DIALOG_ERROR, N_("_Error console"), NULL, NULL, G_CALLBACK(show_errors_cb)},
2074 {"Help", GTK_STOCK_HELP, NULL, NULL, NULL, G_CALLBACK(help_cb)},
2075 {"CaveInfo", GTK_STOCK_DIALOG_INFO, N_("Caveset _information"), NULL, N_("Show information about the game and its caves"), G_CALLBACK(cave_info_cb)},
2078 static GtkActionEntry action_entries_title[]={
2079 {"GamePreferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, G_CALLBACK(preferences_cb)},
2080 {"GameControlSettings", GD_ICON_KEYBOARD, N_("_Control keys"), NULL, NULL, G_CALLBACK(control_settings_cb)},
2081 {"NewGame", GTK_STOCK_MEDIA_PLAY, N_("_New game"), "<control>N", N_("Start new game"), G_CALLBACK(new_game_cb)},
2082 {"CaveEditor", GD_ICON_CAVE_EDITOR, N_("Cave _editor"), NULL, NULL, G_CALLBACK(cave_editor_cb)},
2083 {"OpenFile", GTK_STOCK_OPEN, NULL, NULL, NULL, G_CALLBACK(open_caveset_cb)},
2084 {"LoadInternal", GTK_STOCK_INDEX, N_("Load _internal game")},
2085 {"LoadRecent", GTK_STOCK_DIALOG_INFO, N_("Open _recent")},
2086 {"OpenCavesDir", GTK_STOCK_CDROM, N_("O_pen shipped"), NULL, NULL, G_CALLBACK(open_caveset_dir_cb)},
2087 {"SaveFile", GTK_STOCK_SAVE, NULL, NULL, NULL, G_CALLBACK(save_caveset_cb)},
2088 {"SaveAsFile", GTK_STOCK_SAVE_AS, NULL, NULL, NULL, G_CALLBACK(save_caveset_as_cb)},
2089 {"HighScore", GD_ICON_AWARD, N_("Hi_ghscores"), NULL, NULL, G_CALLBACK(highscore_cb)},
2092 static GtkActionEntry action_entries_title_replay[]={
2093 {"ShowReplays", GD_ICON_REPLAY, N_("Show _replays"), NULL, N_("List replays which are recorded for caves in this caveset"), G_CALLBACK(show_replays_cb)},
2096 static GtkActionEntry action_entries_game[]={
2097 {"TakeSnapshot", GD_ICON_SNAPSHOT, N_("_Take snapshot"), "F5", NULL, G_CALLBACK(save_snapshot_cb)},
2098 {"Restart", GD_ICON_RESTART_LEVEL, N_("Re_start level"), "Escape", N_("Restart current level"), G_CALLBACK(restart_level_cb)},
2099 {"EndGame", GTK_STOCK_STOP, N_("_End game"), "F1", N_("End current game"), G_CALLBACK(stop_game_cb)},
2102 static GtkActionEntry action_entries_snapshot[]={
2103 {"RevertToSnapshot", GTK_STOCK_UNDO, N_("_Revert to snapshot"), "F6", NULL, G_CALLBACK(load_snapshot_cb)},
2106 static GtkToggleActionEntry action_entries_toggle[]={
2107 {"PauseGame", GTK_STOCK_MEDIA_PAUSE, NULL, "space", N_("Pause game"), G_CALLBACK(toggle_pause_cb), FALSE},
2108 {"FullScreen", GTK_STOCK_FULLSCREEN, NULL, "F11", N_("Fullscreen mode during play"), G_CALLBACK(toggle_fullscreen_cb), FALSE},
2109 {"FastForward", GTK_STOCK_MEDIA_FORWARD, N_("Fast for_ward"), "<control>F", N_("Fast forward"), G_CALLBACK(toggle_fast_cb), FALSE},
2112 static const char *ui_info =
2113 "<ui>"
2114 "<menubar name='MenuBar'>"
2115 "<menu action='FileMenu'>"
2116 "<separator/>"
2117 "<menuitem action='OpenFile'/>"
2118 "<menuitem action='LoadRecent'/>"
2119 "<menuitem action='OpenCavesDir'/>"
2120 "<menuitem action='LoadInternal'/>"
2121 "<separator/>"
2122 "<menuitem action='CaveEditor'/>"
2123 "<separator/>"
2124 "<menuitem action='SaveFile'/>"
2125 "<menuitem action='SaveAsFile'/>"
2126 "<separator/>"
2127 "<menuitem action='Quit'/>"
2128 "</menu>"
2129 "<menu action='PlayMenu'>"
2130 "<menuitem action='NewGame'/>"
2131 "<menuitem action='PauseGame'/>"
2132 "<menuitem action='FastForward'/>"
2133 "<menuitem action='TakeSnapshot'/>"
2134 "<menuitem action='RevertToSnapshot'/>"
2135 "<menuitem action='Restart'/>"
2136 "<menuitem action='EndGame'/>"
2137 "<separator/>"
2138 "<menuitem action='CaveInfo'/>"
2139 "<menuitem action='HighScore'/>"
2140 "<menuitem action='ShowReplays'/>"
2141 "<separator/>"
2142 "<menuitem action='FullScreen'/>"
2143 "<menuitem action='GameControlSettings'/>"
2144 "<menuitem action='GamePreferences'/>"
2145 "</menu>"
2146 "<menu action='HelpMenu'>"
2147 "<menuitem action='Help'/>"
2148 "<separator/>"
2149 "<menuitem action='Errors'/>"
2150 "<menuitem action='About'/>"
2151 "</menu>"
2152 "</menubar>"
2154 "<toolbar name='ToolBar'>"
2155 "<toolitem action='NewGame'/>"
2156 "<toolitem action='EndGame'/>"
2157 "<toolitem action='FullScreen'/>"
2158 "<separator/>"
2159 "<toolitem action='PauseGame'/>"
2160 "<toolitem action='FastForward'/>"
2161 "<toolitem action='Restart'/>"
2162 "<separator/>"
2163 "<toolitem action='CaveInfo'/>"
2164 "<toolitem action='ShowReplays'/>"
2165 "</toolbar>"
2166 "</ui>";
2168 GtkWidget *vbox, *hbox, *recent_chooser, *bar, *button;
2169 GtkRecentFilter *recent_filter;
2170 GdkPixbuf *logo;
2171 GtkUIManager *ui;
2172 int i;
2173 const gchar **names;
2174 GtkWidget *menu;
2176 logo=gd_icon();
2177 gtk_window_set_default_icon (logo);
2178 g_object_unref(logo);
2180 main_window.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
2181 gtk_window_set_default_size(GTK_WINDOW(main_window.window), 360, 300);
2182 g_signal_connect(G_OBJECT(main_window.window), "focus_out_event", G_CALLBACK(main_window_focus_out_event), NULL);
2183 g_signal_connect(G_OBJECT(main_window.window), "delete_event", G_CALLBACK(main_window_delete_event), NULL);
2184 g_signal_connect(G_OBJECT(main_window.window), "key_press_event", G_CALLBACK(main_window_keypress_event), NULL);
2185 g_signal_connect(G_OBJECT(main_window.window), "key_release_event", G_CALLBACK(main_window_keypress_event), NULL);
2187 /* vertical box */
2188 vbox=gtk_vbox_new(FALSE, 0);
2189 gtk_container_add (GTK_CONTAINER (main_window.window), vbox);
2191 /* menu */
2192 main_window.actions_normal=gtk_action_group_new("main_window.actions_normal");
2193 gtk_action_group_set_translation_domain(main_window.actions_normal, PACKAGE);
2194 gtk_action_group_add_actions(main_window.actions_normal, action_entries_normal, G_N_ELEMENTS(action_entries_normal), &main_window);
2195 gtk_action_group_add_toggle_actions(main_window.actions_normal, action_entries_toggle, G_N_ELEMENTS(action_entries_toggle), NULL);
2196 main_window.actions_title=gtk_action_group_new("main_window.actions_title");
2197 gtk_action_group_set_translation_domain(main_window.actions_title, PACKAGE);
2198 gtk_action_group_add_actions(main_window.actions_title, action_entries_title, G_N_ELEMENTS(action_entries_title), &main_window);
2199 main_window.actions_title_replay=gtk_action_group_new("main_window.actions_title_replay");
2200 gtk_action_group_set_translation_domain(main_window.actions_title_replay, PACKAGE);
2201 gtk_action_group_add_actions(main_window.actions_title_replay, action_entries_title_replay, G_N_ELEMENTS(action_entries_title_replay), &main_window);
2202 /* make this toolbar button always have a title */
2203 g_object_set (gtk_action_group_get_action (main_window.actions_title, "NewGame"), "is_important", TRUE, NULL);
2204 main_window.actions_game=gtk_action_group_new("main_window.actions_game");
2205 gtk_action_group_set_translation_domain(main_window.actions_game, PACKAGE);
2206 gtk_action_group_add_actions(main_window.actions_game, action_entries_game, G_N_ELEMENTS(action_entries_game), &main_window);
2207 main_window.actions_snapshot=gtk_action_group_new("main_window.actions_snapshot");
2208 gtk_action_group_set_translation_domain(main_window.actions_snapshot, PACKAGE);
2209 gtk_action_group_add_actions(main_window.actions_snapshot, action_entries_snapshot, G_N_ELEMENTS(action_entries_snapshot), &main_window);
2211 /* build the ui */
2212 ui=gtk_ui_manager_new();
2213 gtk_ui_manager_insert_action_group (ui, main_window.actions_normal, 0);
2214 gtk_ui_manager_insert_action_group (ui, main_window.actions_title, 0);
2215 gtk_ui_manager_insert_action_group (ui, main_window.actions_title_replay, 0);
2216 gtk_ui_manager_insert_action_group (ui, main_window.actions_game, 0);
2217 gtk_ui_manager_insert_action_group (ui, main_window.actions_snapshot, 0);
2218 gtk_window_add_accel_group (GTK_WINDOW(main_window.window), gtk_ui_manager_get_accel_group (ui));
2219 gtk_ui_manager_add_ui_from_string (ui, ui_info, -1, NULL);
2220 main_window.menubar=gtk_ui_manager_get_widget (ui, "/MenuBar");
2221 gtk_box_pack_start(GTK_BOX (vbox), main_window.menubar, FALSE, FALSE, 0);
2222 main_window.toolbar=gtk_ui_manager_get_widget (ui, "/ToolBar");
2223 gtk_toolbar_set_style(GTK_TOOLBAR(main_window.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
2224 gtk_box_pack_start(GTK_BOX (vbox), main_window.toolbar, FALSE, FALSE, 0);
2225 gtk_toolbar_set_tooltips (GTK_TOOLBAR (main_window.toolbar), TRUE);
2227 /* make a submenu, which contains the games compiled in. */
2228 i=0;
2229 menu=gtk_menu_new();
2230 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadInternal")), menu);
2231 names=gd_caveset_get_internal_game_names ();
2232 while (names[i]) {
2233 GtkWidget *menuitem=gtk_menu_item_new_with_label(names[i]);
2235 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
2236 gtk_widget_show(menuitem);
2237 g_signal_connect (G_OBJECT(menuitem), "activate", G_CALLBACK(load_internal_cb), GINT_TO_POINTER (i));
2238 i++;
2241 /* recent file chooser */
2242 recent_chooser=gtk_recent_chooser_menu_new();
2243 recent_filter=gtk_recent_filter_new();
2244 /* gdash file extensions */
2245 for (i=0; gd_caveset_extensions[i]!=NULL; i++)
2246 gtk_recent_filter_add_pattern(recent_filter, gd_caveset_extensions[i]);
2247 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_chooser), recent_filter);
2248 gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(recent_chooser), TRUE);
2249 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_chooser), 10);
2250 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_chooser), GTK_RECENT_SORT_MRU);
2251 gtk_menu_item_set_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/LoadRecent")), recent_chooser);
2252 g_signal_connect(G_OBJECT(recent_chooser), "item-activated", G_CALLBACK(recent_chooser_activated_cb), NULL);
2254 g_object_unref(ui);
2256 /* this hbox will contain the replay logo and the labels */
2257 hbox=gtk_hbox_new(FALSE, 6);
2258 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2260 /* replay logo... */
2261 main_window.replay_image_align=gtk_alignment_new(0.5, 0.5, 0, 0);
2262 gtk_box_pack_start(GTK_BOX(hbox), main_window.replay_image_align, FALSE, FALSE, 0);
2263 gtk_container_add(GTK_CONTAINER(main_window.replay_image_align), gtk_image_new_from_stock(GD_ICON_REPLAY, GTK_ICON_SIZE_LARGE_TOOLBAR));
2265 /* ... labels. */
2266 main_window.labels=gtk_vbox_new(FALSE, 0);
2267 gtk_box_pack_start_defaults(GTK_BOX(hbox), main_window.labels);
2269 /* first hbox for labels ABOVE drawing */
2270 hbox=gtk_hbox_new(FALSE, 12);
2271 gtk_box_pack_start(GTK_BOX (main_window.labels), hbox, FALSE, FALSE, 0);
2272 main_window.label_topleft=gtk_label_new(NULL); /* NAME label */
2273 gtk_label_set_ellipsize(GTK_LABEL(main_window.label_topleft), PANGO_ELLIPSIZE_END); /* enable ellipsize, as the cave name might be a long string */
2274 gtk_misc_set_alignment(GTK_MISC(main_window.label_topleft), 0, 0.5); /* as it will be expanded, we must set left justify (0) */
2275 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_topleft, TRUE, TRUE, 0); /* should expand, as it is ellipsized!! */
2277 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_topright=gtk_label_new(NULL), FALSE, FALSE, 0);
2279 /* second row of labels */
2280 hbox=gtk_hbox_new(FALSE, 12);
2281 gtk_box_pack_start(GTK_BOX(main_window.labels), hbox, FALSE, FALSE, 0);
2282 gtk_box_pack_start(GTK_BOX(hbox), main_window.label_bottomleft=gtk_label_new(NULL), FALSE, FALSE, 0); /* diamonds label */
2283 gtk_box_pack_end(GTK_BOX(hbox), main_window.label_bottomright=gtk_label_new(NULL), FALSE, FALSE, 0); /* time, score label */
2286 /* scroll window which contains the cave or the title image, so this is the main content of the window */
2287 main_window.scroll_window=gtk_scrolled_window_new(NULL, NULL);
2288 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(main_window.scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2289 gtk_box_pack_start_defaults(GTK_BOX (vbox), main_window.scroll_window);
2291 main_window.label_variables=gtk_label_new(NULL);
2292 gtk_box_pack_start(GTK_BOX (vbox), main_window.label_variables, FALSE, FALSE, 0);
2294 /* info bar */
2295 bar=gtk_info_bar_new();
2296 gtk_box_pack_start(GTK_BOX(vbox), bar, FALSE, FALSE, 0);
2297 gtk_info_bar_set_message_type(GTK_INFO_BAR(bar), GTK_MESSAGE_ERROR);
2298 main_window.info_label=gtk_label_new(NULL);
2299 gtk_box_pack_start(GTK_BOX(gtk_info_bar_get_content_area(GTK_INFO_BAR(bar))), main_window.info_label, FALSE, FALSE, 0); /* error label */
2300 main_window.info_bar=bar;
2301 button=gtk_button_new_with_mnemonic(_("_Show"));
2302 gtk_box_pack_start(GTK_BOX(gtk_info_bar_get_action_area(GTK_INFO_BAR(bar))), button, FALSE, FALSE, 0); /* error label */
2303 g_signal_connect(button, "clicked", G_CALLBACK(show_errors_cb), NULL);
2305 gtk_widget_show_all(main_window.window);
2306 gtk_window_present(GTK_WINDOW(main_window.window));
2310 main()
2311 function
2315 main(int argc, char *argv[])
2317 int quit=0;
2318 gboolean editor=FALSE;
2319 char *gallery_filename=NULL;
2320 char *png_filename=NULL, *png_size=NULL;
2321 char *save_cave_name=NULL;
2322 gboolean force_quit_no_gtk;
2324 GError *error=NULL;
2325 GOptionContext *context;
2326 GOptionEntry entries[]={
2327 {"editor", 'e', 0, G_OPTION_ARG_NONE, &editor, N_("Start editor")},
2328 {"gallery", 'g', 0, G_OPTION_ARG_FILENAME, &gallery_filename, N_("Save caveset in a HTML gallery")},
2329 {"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\"")},
2330 {"favicon", 's', 0, G_OPTION_ARG_STRING /* not filename! */, &gd_html_favicon_filename, N_("Link shortcut icon to a HTML gallery, eg. \"../favicon.ico\"")},
2331 {"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")},
2332 {"png_size", 'P', 0, G_OPTION_ARG_STRING, &png_size, N_("Set PNG image size. Default is 128x96, set to 0x0 for unscaled")},
2333 {"save", 'S', 0, G_OPTION_ARG_FILENAME, &save_cave_name, N_("Save caveset in a BDCFF file")},
2334 {"quit", 'q', 0, G_OPTION_ARG_NONE, &quit, N_("Batch mode: quit after specified tasks")},
2335 {NULL}
2338 context=gd_option_context_new();
2339 g_option_context_add_main_entries (context, entries, PACKAGE); /* gdash (gtk version) parameters */
2340 g_option_context_add_group (context, gtk_get_option_group(FALSE)); /* add gtk parameters */
2341 g_option_context_parse (context, &argc, &argv, &error);
2342 g_option_context_free (context);
2343 if (error) {
2344 g_warning("%s", error->message);
2345 g_error_free(error);
2348 /* show license? */
2349 if (gd_param_license) {
2350 char *wrapped=gd_wrap_text(gd_about_license, 72);
2352 g_print("%s", wrapped);
2353 g_free(wrapped);
2354 return 0;
2357 gd_install_log_handler();
2359 gd_settings_init_dirs();
2361 gd_load_settings();
2363 gtk_set_locale();
2364 gd_settings_set_locale();
2366 force_quit_no_gtk=FALSE;
2367 if (!gtk_init_check(&argc, &argv)) {
2368 force_quit_no_gtk=TRUE;
2371 gd_settings_init_translation();
2373 gd_cave_init();
2374 gd_cave_db_init();
2375 gd_cave_sound_db_init();
2376 gd_c64_import_init_tables();
2378 gd_caveset_clear(); /* this also creates the default cave */
2380 gd_clear_error_flag();
2382 gd_wait_before_game_over=FALSE;
2384 /* LOAD A CAVESET FROM A FILE, OR AN INTERNAL ONE */
2385 /* if remaining arguments, they are filenames */
2386 if (gd_param_cavenames && gd_param_cavenames[0]) {
2387 /* load caveset, "ignore" errors. */
2388 if (!gd_open_caveset_in_ui(gd_param_cavenames[0], gd_use_bdcff_highscore))
2389 g_critical (_("Errors during loading caveset from file '%s'"), gd_param_cavenames[0]);
2391 else if (gd_param_internal) {
2392 /* if specified an internal caveset; if error, halt now */
2393 if (!gd_caveset_load_from_internal (gd_param_internal-1, gd_user_config_dir))
2394 g_critical (_("%d: no such internal caveset"), gd_param_internal);
2396 else
2397 /* if nothing requested, load default */
2398 gd_caveset_load_from_internal(0, gd_user_config_dir);
2400 /* always load c64 graphics, as it is the builtin, and we need an icon for the theme selector. */
2401 gd_loadcells_default();
2402 gd_create_pixbuf_for_builtin_theme();
2404 /* load other theme, if specified in config. */
2405 if (gd_theme!=NULL && !g_str_equal(gd_theme, "")) {
2406 if (!gd_loadcells_file(gd_theme)) {
2407 /* forget the theme if we are unable to load */
2408 g_warning("Cannot load theme %s, switching to built-in theme", gd_theme);
2409 g_free(gd_theme);
2410 gd_theme=NULL;
2411 gd_loadcells_default(); /* load default gfx */
2415 /* after loading cells... see if generating a gallery. */
2416 /* but only if there are any caves at all. */
2417 if (gd_caveset && gallery_filename)
2418 gd_save_html (gallery_filename, NULL);
2420 /* if cave or level values given, check range */
2421 if (gd_param_cave)
2422 if (gd_param_cave<1 || gd_param_cave>gd_caveset_count() || gd_param_level<1 || gd_param_level>5)
2423 g_critical (_("Invalid cave or level number!"));
2425 /* save cave png */
2426 if (png_filename) {
2427 GError *error=NULL;
2428 GdkPixbuf *pixbuf;
2429 GdCave *renderedcave;
2430 unsigned int size_x=128, size_y=96; /* default size */
2432 if (gd_param_cave == 0)
2433 gd_param_cave=g_random_int_range(0, gd_caveset_count ())+1;
2435 if (png_size && (sscanf (png_size, "%ux%u", &size_x, &size_y) != 2))
2436 g_critical (_("Invalid image size: %s"), png_size);
2437 if (size_x<1 || size_y<1) {
2438 size_x=0;
2439 size_y=0;
2442 /* rendering cave for png: seed=0 */
2443 renderedcave=gd_cave_new_from_caveset (gd_param_cave-1, gd_param_level-1, 0);
2444 pixbuf=gd_drawcave_to_pixbuf(renderedcave, size_x, size_y, TRUE, FALSE);
2445 if (!gdk_pixbuf_save (pixbuf, png_filename, "png", &error, "compression", "9", NULL))
2446 g_critical ("Error saving PNG image %s: %s", png_filename, error->message);
2447 g_object_unref(pixbuf);
2448 gd_cave_free (renderedcave);
2450 /* avoid starting game */
2451 gd_param_cave=0;
2454 if (save_cave_name)
2455 gd_caveset_save(save_cave_name);
2457 /* if batch mode, quit now */
2458 if (quit)
2459 return 0;
2460 if (force_quit_no_gtk) {
2461 g_critical("Cannot initialize GUI");
2462 return 1;
2465 /* create window */
2466 gd_register_stock_icons();
2468 create_main_window();
2469 gd_main_window_set_title();
2471 gd_sound_init(0);
2473 #ifdef GD_SOUND
2474 gd_sound_set_music_volume(gd_sound_music_volume_percent);
2475 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
2476 #endif
2478 main_window_init_title();
2480 #ifdef G_THREADS_ENABLED
2481 if (!g_thread_supported())
2482 g_thread_init(NULL);
2483 #endif
2485 if (gd_param_cave) {
2486 /* if cave number given, start game */
2487 main_new_game(g_get_real_name(), gd_param_cave-1, gd_param_level-1);
2489 else if (editor)
2490 cave_editor_cb(NULL, &main_window);
2492 gtk_main();
2494 gd_save_highscore(gd_user_config_dir);
2496 gd_save_settings();
2498 return 0;