2009050
[gdash.git] / src / gtkui.c
blob094a40950426495417b9fda5dc1826fd62385efb
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 <glib/gi18n.h>
18 #include <glib/gstdio.h>
19 #include <gdk-pixbuf/gdk-pixbuf.h>
20 #include <string.h>
21 #include <gdk/gdkkeysyms.h>
22 #include "cavedb.h"
23 #include "gtkgfx.h"
24 #include "caveset.h"
25 #include "settings.h"
26 #include "util.h"
27 #include "gtkui.h"
28 #include "config.h"
29 #include "gtkmain.h"
30 #include "gfxutil.h"
31 #include "sound.h"
33 /* pixbufs of icons and the like */
34 #include "icons.h"
35 /* title image and icon */
36 #include "title.h"
38 static char *caveset_filename=NULL;
39 static char *last_folder=NULL;
43 void
44 gd_register_stock_icons()
46 static struct {
47 const guint8 *data;
48 char *stock_id;
49 } icons[]={
50 { cave_editor, GD_ICON_CAVE_EDITOR},
51 { move, GD_ICON_EDITOR_MOVE},
52 { add_join, GD_ICON_EDITOR_JOIN},
53 { add_freehand, GD_ICON_EDITOR_FREEHAND},
54 { add_point, GD_ICON_EDITOR_POINT},
55 { add_line, GD_ICON_EDITOR_LINE},
56 { add_rectangle, GD_ICON_EDITOR_RECTANGLE},
57 { add_filled_rectangle, GD_ICON_EDITOR_FILLRECT},
58 { add_raster, GD_ICON_EDITOR_RASTER},
59 { add_fill_border, GD_ICON_EDITOR_FILL_BORDER},
60 { add_fill_replace, GD_ICON_EDITOR_FILL_REPLACE},
61 { add_maze, GD_ICON_EDITOR_MAZE},
62 { add_maze_uni, GD_ICON_EDITOR_MAZE_UNI},
63 { add_maze_braid, GD_ICON_EDITOR_MAZE_BRAID},
64 { snapshot, GD_ICON_SNAPSHOT},
65 { restart_level, GD_ICON_RESTART_LEVEL},
66 { random_fill, GD_ICON_RANDOM_FILL},
67 { award, GD_ICON_AWARD},
68 { to_top, GD_ICON_TO_TOP},
69 { to_bottom, GD_ICON_TO_BOTTOM},
70 { object_on_all, GD_ICON_OBJECT_ON_ALL},
71 { object_not_on_all, GD_ICON_OBJECT_NOT_ON_ALL},
72 { object_not_on_current, GD_ICON_OBJECT_NOT_ON_CURRENT},
73 { replay, GD_ICON_REPLAY},
74 { keyboard, GD_ICON_KEYBOARD},
75 { image, GD_ICON_IMAGE },
78 GtkIconFactory *factory;
79 int i;
81 factory=gtk_icon_factory_new();
82 for (i=0; i < G_N_ELEMENTS (icons); i++) {
83 GtkIconSet *iconset;
84 GdkPixbuf *pixbuf;
86 pixbuf=gdk_pixbuf_new_from_inline (-1, icons[i].data, FALSE, NULL);
87 iconset=gtk_icon_set_new_from_pixbuf(pixbuf);
88 g_object_unref (pixbuf);
89 gtk_icon_factory_add (factory, icons[i].stock_id, iconset);
91 gtk_icon_factory_add_default (factory);
92 g_object_unref (factory);
96 GdkPixbuf *
97 gd_icon()
99 return gd_pixbuf_load_from_data(gdash, sizeof(gdash));
102 /* create and return an array of pixmaps, which contain the title animation.
103 the array is one pointer larger than all frames; the last pointer is a null.
104 up to the caller to free.
106 GdkPixmap **
107 gd_create_title_animation()
109 GdkPixbuf *screen;
110 GdkPixbuf *tile, *tile_black;
111 GdkPixbuf *frame;
112 GdkPixbuf *bigone;
113 GdkPixmap **pixmaps;
114 int i;
115 int x, y;
116 int w, h, tw, th;
118 screen=NULL;
119 tile=NULL;
120 if (gd_caveset_data->title_screen->len!=0) {
121 /* user defined title screen */
122 screen=gd_pixbuf_load_from_base64(gd_caveset_data->title_screen->str);
123 if (!screen) {
124 g_warning("Caveset is storing an invalid title screen image.");
125 g_string_assign(gd_caveset_data->title_screen, "");
126 } else {
127 /* if we loaded the screen, now try to load the tile. */
128 /* only if the screen has an alpha channel. otherwise it would not make any sense */
129 if (gdk_pixbuf_get_has_alpha(screen) && gd_caveset_data->title_screen_scroll->len!=0) {
130 tile=gd_pixbuf_load_from_base64(gd_caveset_data->title_screen_scroll->str);
132 if (!tile) {
133 g_warning("Caveset is storing an invalid title screen background image.");
134 g_string_assign(gd_caveset_data->title_screen_scroll, "");
141 /* if no special title image or unable to load that one, load the built-in */
142 if (!screen) {
143 /* the screen */
144 screen=gd_pixbuf_load_from_data(gdash_screen, sizeof(gdash_screen));
145 g_assert(screen!=NULL);
146 /* the tile to be put under the screen */
147 tile=gd_pixbuf_load_from_data(gdash_tile, sizeof(gdash_tile));
148 g_assert(tile!=NULL);
151 /* get sizes */
152 w=gdk_pixbuf_get_width(screen);
153 h=gdk_pixbuf_get_height(screen);
155 /* if no tile, let it be black. */
156 /* the sdl version does the same. */
157 if (!tile) {
158 /* one-row pixbuf, so no animation. */
159 tile=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, 1);
160 gdk_pixbuf_fill(tile, 0x000000FFU); /* opaque black */
163 /* now we must have a tile pixbuf, so get that size too */
164 tw=gdk_pixbuf_get_width(tile);
165 th=gdk_pixbuf_get_height(tile);
167 /* do not allow more than 40 frames of animation */
168 if (th>GD_TITLE_SCROLL_MAX_HEIGHT) {
169 g_warning("Caveset is storing an oversized title screen background image.");
170 g_string_assign(gd_caveset_data->title_screen_scroll, "");
173 /* either because the tile has no alpha channel, or because it cannot be transparent anymore... */
174 /* also needed because the "bigone" pixbuf will have an alpha channel, and pixbuf_copy would not work otherwise. */
175 tile_black=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, tw, th);
176 gdk_pixbuf_fill(tile_black, 0x000000FFU); /* fill with opaque black, as even the tile may have an alpha channel */
177 gdk_pixbuf_composite(tile, tile_black, 0, 0, tw, th, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
178 g_object_unref(tile);
179 tile=tile_black;
181 /* create a big image, which is one tile larger than the title image size */
182 bigone=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h+th);
183 /* and fill it with the tile. */
184 for (y=0; y<h+th; y+=th)
185 for (x=0; x<w; x+=tw) {
186 int cw, ch; /* copied width and height */
188 /* check if out of bounds, as gdk does not clip rather sends errors */
189 if (x+tw>w)
190 cw=w-x;
191 else
192 cw=tw;
193 if (y+th>h+th)
194 ch=h+th-y;
195 else
196 ch=th;
197 gdk_pixbuf_copy_area(tile, 0, 0, cw, ch, bigone, x, y);
199 g_object_unref(tile);
201 pixmaps=g_new0(GdkPixmap *, th+1); /* 'th' number of images, and a NULL to the end */
202 frame=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
203 for (i=0; i<th; i++) {
204 GdkPixbuf *scaled;
205 /* copy part of the big tiled image */
206 gdk_pixbuf_copy_area(bigone, 0, i, w, h, frame, 0, 0);
207 /* and composite it with the title image */
208 gdk_pixbuf_composite(screen, frame, 0, 0, w, h, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
210 /* scale the title screen the same way as we scale the game */
211 scaled=gd_pixbuf_scale(frame, gd_cell_scale_game);
212 if (gd_pal_emulation_game)
213 gd_pal_emulate_pixbuf(scaled);
214 pixmaps[i]=gdk_pixmap_new(gdk_get_default_root_window(), gdk_pixbuf_get_width(scaled), gdk_pixbuf_get_height(scaled), -1);
215 gdk_draw_pixbuf(pixmaps[i], NULL, scaled, 0, 0, 0, 0, gdk_pixbuf_get_width(scaled), gdk_pixbuf_get_height(scaled), GDK_RGB_DITHER_MAX, 0, 0);
216 g_object_unref(scaled);
218 g_object_unref(bigone);
219 g_object_unref(frame);
221 g_object_unref(screen);
223 return pixmaps;
230 /************
232 * functions for the settings window.
233 * these implement the list view, which shows available graphics.
236 enum {
237 THEME_COL_FILENAME,
238 THEME_COL_NAME,
239 THEME_COL_PIXBUF,
240 NUM_THEME_COLS
243 static gboolean
244 find_image_in_store(GtkTreeModel *store, const gchar *find_name, GtkTreeIter *set_iter)
246 GtkTreeIter iter;
248 if (gtk_tree_model_get_iter_first(store, &iter)) {
249 do {
250 char *stored_name;
252 /* get filename from store */
253 gtk_tree_model_get(store, &iter, THEME_COL_FILENAME, &stored_name, -1);
254 /* if find_name is null and stored_name is null: that is the builtin theme, so they are equal! */
255 /* otherwise, compare the strings. */
256 if ((!find_name && !stored_name) || (find_name && stored_name && g_str_equal(find_name, stored_name))) {
257 if (set_iter)
258 *set_iter=iter;
259 return TRUE;
261 } while (gtk_tree_model_iter_next(store, &iter));
264 /* not found: return false */
265 return FALSE;
268 static GtkTreeIter *
269 add_image_to_store (GtkListStore *store, const char *filename)
271 static GtkTreeIter iter;
272 GdkPixbuf *pixbuf, *cell_pixbuf;
273 char *seen_name;
274 int cell_size;
275 int cell_num=gd_elements[O_PLAYER].image_game; /* load the image of the player from every pixbuf */
276 GError *error=NULL;
278 pixbuf=gdk_pixbuf_new_from_file(filename, &error);
279 if (error) {
280 /* unable to load image - silently ignore. */
281 /* if we were called from add_dir_to_store, it is ok to ignore the error, as the file will not
282 be visible in the prefs window. */
283 /* if we are called from the add_theme button, the file is already checked. */
284 g_error_free(error);
285 return NULL;
287 /* check if pixbuf is ok */
288 if (gd_is_pixbuf_ok_for_theme(pixbuf)!=NULL) {
289 g_object_unref(pixbuf);
290 return NULL;
293 cell_size=gdk_pixbuf_get_width (pixbuf) / NUM_OF_CELLS_X;
294 cell_pixbuf=gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, cell_size, cell_size);
295 gdk_pixbuf_copy_area(pixbuf, (cell_num % NUM_OF_CELLS_X) * cell_size, (cell_num / NUM_OF_CELLS_X) * cell_size, cell_size, cell_size, cell_pixbuf, 0, 0);
296 g_object_unref(pixbuf);
298 /* check list store to find if this file is already added. may happen if a theme is overwritten by the add theme button */
299 if (find_image_in_store(GTK_TREE_MODEL(store), filename, &iter))
300 gtk_list_store_remove(store, &iter);
302 /* add to list */
303 seen_name=g_filename_display_basename(filename);
304 if (strrchr(seen_name, '.')) /* remove extension */
305 *strrchr(seen_name, '.')='\0';
306 gtk_list_store_append(store, &iter);
307 gtk_list_store_set(store, &iter, THEME_COL_FILENAME, filename, THEME_COL_NAME, seen_name, THEME_COL_PIXBUF, cell_pixbuf, -1);
308 g_free(seen_name);
309 g_object_unref(cell_pixbuf); /* the store has its own ref */
311 return &iter;
314 static void
315 add_dir_to_store(GtkListStore *store, const char *dirname)
317 GDir *dir;
318 GError *error=NULL;
319 /* search directory */
320 dir=g_dir_open(dirname, 0, &error);
321 if (!error) {
322 const char *name;
324 while ((name=g_dir_read_name(dir))) {
325 char *filename=g_build_filename(dirname, name, NULL);
327 /* if image file can be loaded and proper size for theme */
328 if (g_file_test(filename, G_FILE_TEST_IS_REGULAR) && gd_is_image_ok_for_theme(filename)==NULL) {
329 add_image_to_store(store, filename);
331 g_free(filename);
333 g_dir_close(dir);
334 } else {
335 /* unable to open directory */
336 g_warning("%s", error->message);
337 g_error_free(error);
341 GtkListStore *
342 gd_create_themes_list()
344 GtkListStore *store;
345 GtkTreeIter iter;
347 store=gtk_list_store_new(NUM_THEME_COLS, G_TYPE_STRING, G_TYPE_STRING, GDK_TYPE_PIXBUF);
349 /* default builtin theme */
350 gtk_list_store_append(store, &iter);
351 gtk_list_store_set(store, &iter, THEME_COL_FILENAME, NULL, THEME_COL_NAME, _("Default"), THEME_COL_PIXBUF, gd_pixbuf_for_builtin_theme, -1);
353 /* add themes found in config directories */
354 add_dir_to_store(store, gd_system_data_dir);
355 add_dir_to_store(store, gd_user_config_dir);
357 /* if the current theme cannot be found in the store for some reason, add it now. */
358 if (!find_image_in_store(GTK_TREE_MODEL(store), gd_theme, NULL))
359 add_image_to_store(store, gd_theme);
361 return store;
366 /****************************
368 * settings window
371 typedef struct pref_info {
372 GtkWidget *dialog;
373 GtkWidget *treeview, *sizecombo_game, *sizecombo_editor;
374 GtkWidget *image_game, *image_editor;
375 } pref_info;
377 static void
378 settings_window_update(pref_info* info)
380 GdkPixbuf *orig_pb;
381 GdkPixbuf *pb;
382 GtkTreeIter iter;
383 GtkTreeModel *model;
385 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(info->treeview)), &model, &iter))
386 return;
387 gtk_tree_model_get(model, &iter, THEME_COL_PIXBUF, &orig_pb, -1);
389 /* sample for game */
390 pb=gd_pixbuf_scale(orig_pb, gtk_combo_box_get_active(GTK_COMBO_BOX(info->sizecombo_game)));
391 if (gd_pal_emulation_game)
392 gd_pal_emulate_pixbuf(pb);
393 gtk_image_set_from_pixbuf(GTK_IMAGE(info->image_game), pb);
394 g_object_unref(pb);
396 /* sample for editor */
397 pb=gd_pixbuf_scale(orig_pb, gtk_combo_box_get_active(GTK_COMBO_BOX(info->sizecombo_editor)));
398 if (gd_pal_emulation_editor)
399 gd_pal_emulate_pixbuf(pb);
400 gtk_image_set_from_pixbuf(GTK_IMAGE(info->image_editor), pb);
401 g_object_unref(pb);
404 static void
405 settings_window_changed(gpointer object, gpointer data)
407 settings_window_update((pref_info *)data);
410 /* callback for a toggle button. data is a pointer to a gboolean. */
411 static void
412 settings_window_toggle(GtkWidget *widget, gpointer data)
414 gboolean *bl=(gboolean *)data;
416 *bl=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
419 /* callback for a spin button. data is a pointer to a gboolean. */
420 static void
421 settings_window_value_change(GtkWidget *widget, gpointer data)
423 int *value=(int *) data;
425 *value=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
428 /* return a list of image gtk_image_filter's. */
429 /* they have floating reference. */
430 /* the list is to be freed by the caller. */
431 static GList *
432 image_load_filters()
434 GSList *formats=gdk_pixbuf_get_formats();
435 GSList *iter;
436 GtkFileFilter *all_filter;
437 GList *filters=NULL; /* new list of filters */
439 all_filter=gtk_file_filter_new();
440 gtk_file_filter_set_name(all_filter, _("All image files"));
442 /* iterate the list of formats given by gdk. create file filters for each. */
443 for (iter=formats; iter!=NULL; iter=iter->next) {
444 GdkPixbufFormat *frm=iter->data;
446 if (!gdk_pixbuf_format_is_disabled(frm)) {
447 GtkFileFilter *filter;
448 char **extensions;
449 int i;
451 filter=gtk_file_filter_new();
452 gtk_file_filter_set_name(filter, gdk_pixbuf_format_get_description(frm));
453 extensions=gdk_pixbuf_format_get_extensions(frm);
454 for (i=0; extensions[i]!=NULL; i++) {
455 char *pattern;
457 pattern=g_strdup_printf("*.%s", extensions[i]);
458 gtk_file_filter_add_pattern(filter, pattern);
459 gtk_file_filter_add_pattern(all_filter, pattern);
460 g_free(pattern);
462 g_strfreev(extensions);
464 filters=g_list_append(filters, filter);
467 g_slist_free(formats);
469 /* add "all image files" filter */
470 filters=g_list_prepend(filters, all_filter);
472 return filters;
475 /* file open dialog, with filters for image types gdk-pixbuf recognizes. */
476 char *
477 gd_select_image_file(const char *title, GtkWidget *parent)
479 GtkWidget *dialog;
480 GList *filters;
481 GList *iter;
482 int result;
483 char *filename;
485 dialog=gtk_file_chooser_dialog_new (title, GTK_WINDOW(parent), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
486 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
488 /* obtain list of image filters, and add all to the window */
489 filters=image_load_filters();
490 for (iter=filters; iter!=NULL; iter=iter->next)
491 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), GTK_FILE_FILTER(iter->data));
492 g_list_free(filters);
494 result=gtk_dialog_run(GTK_DIALOG(dialog));
495 if (result==GTK_RESPONSE_ACCEPT)
496 filename=gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
497 else
498 filename=NULL;
499 gtk_widget_destroy (dialog);
501 return filename;
504 static void
505 add_theme_cb(GtkWidget *widget, gpointer data)
507 pref_info *info=(pref_info *)data;
508 char *filename;
510 filename=gd_select_image_file(_("Add Theme from Image File"), info->dialog);
512 if (filename) {
513 const char *error_msg;
515 error_msg=gd_is_image_ok_for_theme(filename);
516 if (error_msg==NULL) {
517 /* make up new filename */
518 char *basename, *new_filename;
519 GError *error=NULL;
520 gchar *contents;
521 gsize length;
523 basename=g_path_get_basename(filename);
524 new_filename=g_build_path(G_DIR_SEPARATOR_S, gd_user_config_dir, basename, NULL);
525 g_free(basename);
527 /* if file not exists, or exists BUT overwrite allowed */
528 if (!g_file_test(new_filename, G_FILE_TEST_EXISTS) || gd_ask_overwrite(new_filename)) {
529 /* copy theme to user config directory */
530 if (g_file_get_contents(filename, &contents, &length, &error) && g_file_set_contents(new_filename, contents, length, &error)) {
531 GtkTreeIter *iter;
533 iter=add_image_to_store(GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(info->treeview))), new_filename);
534 if (iter) /* select newly added. if there was an error, the old selection was not cleared */
535 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(info->treeview)), iter);
537 else {
538 gd_errormessage(error->message, NULL);
539 g_error_free(error);
542 g_free(new_filename);
544 else
545 gd_errormessage(_("The selected image cannot be used as a GDash theme."), error_msg);
546 g_free(filename);
550 static void
551 remove_theme_cb(GtkWidget *widget, gpointer data)
553 pref_info *info=(pref_info *)data;
554 GtkTreeIter iter;
555 GtkTreeSelection *selection;
556 GtkTreeModel *model;
557 char *filename, *seen_name;
558 GtkWidget *dialog;
559 int result;
561 selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(info->treeview));
562 /* if no row selected for some reason, don't care. return now. */
563 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
564 return;
566 gtk_tree_model_get(model, &iter, THEME_COL_FILENAME, &filename, THEME_COL_NAME, &seen_name, -1);
567 /* do not thelete the built-in theme */
568 if (!filename)
569 return;
570 dialog=gtk_message_dialog_new(GTK_WINDOW(info->dialog), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Do you really want to remove theme '%s'?"), seen_name);
571 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (dialog), _("The image file of the theme is '%s'."), filename);
573 result=gtk_dialog_run (GTK_DIALOG (dialog));
574 gtk_widget_destroy(dialog);
575 if (result==GTK_RESPONSE_YES) {
576 /* remove */
577 if (g_unlink(filename)==0) {
578 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
579 /* current file removed - select first iter found in list */
580 if (gtk_tree_model_get_iter_first(model, &iter))
581 gtk_tree_selection_select_iter(selection, &iter);
582 } else
583 gd_errormessage(_("Cannot delete the image file."), filename);
587 GtkWidget *
588 gd_combo_box_new_from_stringv(const char **str)
590 GtkWidget *combo;
591 int i;
593 combo=gtk_combo_box_new_text();
594 for (i=0; str[i]!=NULL; i++)
595 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _(str[i])); /* also translate */
597 return combo;
600 /* when a combo box created from a stringv gets updated. data should point to the integer to be changed. */
601 static void
602 combo_box_stringv_changed(GtkWidget *widget, gpointer data)
604 int *ptr=(int *) data;
606 *ptr=gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
607 /* if nothing selected (for some reason), set to zero. */
608 if (*ptr==-1)
609 *ptr=0;
613 void
614 gd_preferences (GtkWidget *parent)
616 typedef enum _settingtype {
617 TypeLabel,
618 TypeBoolean,
619 TypePercent,
620 TypeStringv,
621 TypeNewColumn,
622 } SettingType;
623 struct {
624 SettingType type;
625 const char *name;
626 const char *description;
627 gpointer value;
628 gboolean update; /* if sample pixbuf should be updated when changing this option */
629 const char **stringv;
630 } options[]={
631 {TypeLabel, N_("<b>Language</b> (requires restart)"), NULL, NULL},
632 {TypeStringv, NULL, N_("The language of the application. Requires restart!"), &gd_language, FALSE, gd_languages_names},
633 {TypeLabel, N_("<b>Cave options</b>"), NULL, NULL},
634 {TypeBoolean, N_("Mouse play (experimental!)"), N_("Use the mouse to play. The player will follow the cursor!"), &gd_mouse_play, FALSE},
635 {TypeBoolean, N_("All caves selectable"), N_("All caves and intermissions can be selected at game start."), &gd_all_caves_selectable, FALSE},
636 {TypeBoolean, N_("Import as all caves selectable"), N_("Original, C64 games are imported not with A, E, I, M caves selectable, but all caves (ABCD, EFGH... excluding intermissions). This does not affect BDCFF caves."), &gd_import_as_all_caves_selectable, FALSE},
637 {TypeBoolean, N_("Use BDCFF highscore"), N_("Use BDCFF highscores. GDash saves highscores in its own configuration directory and also in the *.bd files. However, it prefers loading them from the configuration directory; as the *.bd files might be read-only. You can enable this setting to let GDash load them from the *.bd files. This can be selected for a specific file in the file open dialog, too."), &gd_use_bdcff_highscore, FALSE},
638 {TypeBoolean, N_("Show story"), N_("If the cave has a story, it will be shown when the cave is first started."), &gd_show_story, FALSE },
639 #ifdef GD_SOUND
640 {TypeBoolean, N_("Time as min:sec"), N_("Show times in minutes and seconds, instead of seconds only."), &gd_time_min_sec, FALSE},
641 {TypeBoolean, N_("No invisible outbox"), N_("Show invisible outboxes as visible (blinking) ones."), &gd_no_invisible_outbox, FALSE},
642 {TypeLabel, N_("<b>Sound options</b> (require restart)"), NULL, NULL},
643 {TypeBoolean, N_("Sound"), N_("Play sounds. Enabling this setting requires a restart!"), &gd_sdl_sound, FALSE},
644 {TypePercent, N_("Music volume"), N_("Volume of title screen music."), &gd_sound_music_volume_percent, FALSE},
645 {TypePercent, N_("Cave volume"), N_("Volume of sounds played in a cave."), &gd_sound_chunks_volume_percent, FALSE},
646 {TypeBoolean, N_("Classic sounds only"), N_("Play only classic sounds taken from the original game."), &gd_classic_sound, FALSE},
647 {TypeBoolean, N_("16-bit mixing"), N_("Use 16-bit mixing of sounds. Try changing this setting if sound is clicky. Changing this setting requires a restart!"), &gd_sdl_16bit_mixing, FALSE},
648 {TypeBoolean, N_("44kHz mixing"), N_("Use 44kHz mixing of sounds. Try changing this setting if sound is clicky. Changing this setting requires a restart!"), &gd_sdl_44khz_mixing, FALSE},
649 #endif
651 {TypeNewColumn, },
652 {TypeLabel, N_("<b>Display options</b>"), NULL, NULL},
653 {TypeBoolean, N_("Random colors"), N_("Use randomly selected colors for caves."), &gd_random_colors, FALSE},
654 {TypeBoolean, N_("PAL emulation for game"), N_("Use PAL emulated graphics, ie. lines are striped."), &gd_pal_emulation_game, TRUE},
655 {TypeBoolean, N_("PAL emulation for editor"), N_("Use PAL emulated graphics, ie. lines are striped."), &gd_pal_emulation_editor, TRUE},
656 // {TypeBoolean, N_("Even lines vertical scroll"), N_("Even lines vertical scroll. Scrolls to every second scanline vertically. If you use PAL emulation and PAL scanline shade, scrolling might look better with this turned on."), &gd_even_line_pal_emu_vertical_scroll, FALSE},
657 {TypeBoolean, N_("Fine scroll"), N_("Fine scroll - 50 frames per second."), &gd_fine_scroll, FALSE},
658 {TypePercent, N_("PAL scanline shade (%%)"), N_("Darker rows for PAL emulation."), &gd_pal_emu_scanline_shade, TRUE},
659 {TypeLabel, N_("<b>C64 palette</b>"), NULL, NULL},
660 {TypeStringv, NULL, N_("The color palette for games imported from C64 files."), &gd_c64_palette, FALSE, gd_color_get_c64_palette_names()},
661 {TypeLabel, N_("<b>C64 DTV palette</b>"), NULL, NULL},
662 {TypeStringv, NULL, N_("The color palette for imported C64 DTV games."), &gd_c64dtv_palette, FALSE, gd_color_get_c64dtv_palette_names()},
663 {TypeLabel, N_("<b>Atari palette</b>"), NULL, NULL},
664 {TypeStringv, NULL, N_("The color palette for imported Atari games."), &gd_atari_palette, FALSE, gd_color_get_atari_palette_names()},
665 {TypeLabel, N_("<b>Preferred palette</b>"), NULL, NULL},
666 {TypeStringv, NULL, N_("New caves and random colored caves use this palette."), &gd_preferred_palette, FALSE, gd_color_get_palette_types_names()},
669 GtkWidget *dialog, *table, *label, *button;
670 pref_info info;
671 int i, row, col;
672 GtkWidget *sw, *align, *hbox;
673 GtkListStore *store;
674 GtkTreeSelection *selection;
675 GtkTreeIter iter;
676 char *filename;
678 dialog=gtk_dialog_new_with_buttons (_("GDash Preferences"), (GtkWindow *) parent, GTK_DIALOG_DESTROY_WITH_PARENT, NULL);
679 info.dialog=dialog;
680 gtk_window_set_resizable (GTK_WINDOW(dialog), FALSE);
682 /* remove theme button */
683 button=gtk_button_new_with_mnemonic(_("_Remove theme"));
684 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0);
685 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remove_theme_cb), &info);
687 /* add theme button */
688 button=gtk_button_new_with_mnemonic(_("_Add theme"));
689 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0);
690 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(add_theme_cb), &info);
692 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT);
693 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
695 hbox=gtk_hbox_new(FALSE, 6);
696 gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
697 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox);
698 table=gtk_table_new (1, 1, FALSE);
699 gtk_box_pack_start(GTK_BOX(hbox), table, 0, 0, FALSE);
700 gtk_container_set_border_width (GTK_CONTAINER (table), 6);
701 gtk_table_set_row_spacings (GTK_TABLE (table), 3);
702 gtk_table_set_col_spacings (GTK_TABLE (table), 6);
704 /* game booleans */
705 row=0;
706 col=0;
707 for (i=0; i<G_N_ELEMENTS(options); i++) {
708 GtkWidget *widget, *hbox;
710 switch (options[i].type) {
711 case TypeLabel:
712 label=gtk_label_new(NULL);
713 gtk_label_set_markup(GTK_LABEL(label), _(options[i].name));
714 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
715 gtk_table_attach_defaults (GTK_TABLE (table), label, col, col+2, row, row+1);
716 break;
718 case TypeBoolean:
719 widget=gtk_check_button_new_with_label(_(options[i].name));
720 gtk_widget_set_tooltip_text(widget, _(options[i].description));
721 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), *(gboolean *)options[i].value);
722 gtk_table_attach_defaults (GTK_TABLE (table), widget, col+1, col+2, row, row+1);
723 g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(settings_window_toggle), options[i].value);
724 /* if sample pixbuf should be updated */
725 if (options[i].update)
726 g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(settings_window_changed), &info);
727 break;
729 case TypePercent:
730 hbox=gtk_hbox_new(FALSE, 6);
731 widget=gtk_spin_button_new_with_range(0, 100, 5);
732 gtk_widget_set_tooltip_text(widget, _(options[i].description));
733 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *(int *)options[i].value);
734 g_signal_connect(G_OBJECT(widget), "value-changed", G_CALLBACK(settings_window_value_change), options[i].value);
735 if (options[i].update)
736 g_signal_connect(G_OBJECT(widget), "value-changed", G_CALLBACK(settings_window_changed), &info);
737 gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
738 gtk_box_pack_start_defaults(GTK_BOX(hbox), gd_label_new_printf(_(options[i].name)));
740 gtk_table_attach_defaults (GTK_TABLE (table), hbox, col+1, col+2, row, row+1);
741 break;
743 case TypeStringv:
744 g_assert(!options[i].update); /* must be false */
745 widget=gd_combo_box_new_from_stringv(options[i].stringv);
746 gtk_widget_set_tooltip_text(widget, _(options[i].description));
747 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *(int *)options[i].value);
748 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(combo_box_stringv_changed), options[i].value);
749 gtk_table_attach_defaults(GTK_TABLE(table), widget, col+1, col+2, row, row+1);
750 break;
752 case TypeNewColumn:
753 col+=2;
754 row=-1; /* so row++ under will result in zero */
755 break;
757 row++;
760 /* gfx */
761 store=gd_create_themes_list();
762 info.treeview=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
763 g_object_unref(store);
765 gtk_widget_set_tooltip_text(info.treeview, _("This is the list of available themes. Use the Add Theme button to install a new one."));
766 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(info.treeview), FALSE); /* don't need headers as everything is self-explaining */
767 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(info.treeview), -1, "", gtk_cell_renderer_pixbuf_new(), "pixbuf", THEME_COL_PIXBUF, NULL);
768 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(info.treeview), -1, "", gtk_cell_renderer_text_new(), "text", THEME_COL_NAME, NULL);
769 selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(info.treeview));
770 gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
771 if (find_image_in_store(GTK_TREE_MODEL(store), gd_theme, &iter))
772 gtk_tree_selection_select_iter(selection, &iter);
774 sw=gtk_scrolled_window_new(NULL, NULL);
775 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
776 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
777 gtk_container_add(GTK_CONTAINER(sw), info.treeview);
779 table=gtk_table_new (1, 1, FALSE);
780 gtk_box_pack_start(GTK_BOX(hbox), table, 0, 0, FALSE);
781 gtk_container_set_border_width (GTK_CONTAINER (table), 6);
782 gtk_table_set_row_spacings (GTK_TABLE (table), 6);
783 gtk_table_set_col_spacings (GTK_TABLE (table), 12);
785 label=gtk_label_new(NULL);
786 gtk_label_set_markup(GTK_LABEL(label), _("<b>Theme</b>"));
787 gtk_misc_set_alignment(GTK_MISC (label), 0, 0.5);
788 gtk_table_attach(GTK_TABLE (table), label, 0, 3, 0, 1, GTK_EXPAND|GTK_FILL, 0, 0, 0);
790 gtk_table_attach_defaults(GTK_TABLE(table), sw, 1, 3, 1, 2);
792 /* cells size combo for game */
793 gtk_table_attach(GTK_TABLE(table), gd_label_new_printf("<b>Game</b>"), 1, 2, 2, 3, GTK_EXPAND|GTK_FILL, 0, 0, 0);
794 info.sizecombo_game=gd_combo_box_new_from_stringv(gd_scaling_name);
795 gtk_combo_box_set_active(GTK_COMBO_BOX(info.sizecombo_game), gd_cell_scale_game);
797 gtk_table_attach(GTK_TABLE(table), info.sizecombo_game, 1, 2, 3, 4, GTK_EXPAND|GTK_FILL, 0, 0, 0);
798 /* image for game */
799 align=gtk_alignment_new(0.5, 0.5, 0, 0);
800 gtk_widget_set_size_request(align, 64, 64);
801 info.image_game=gtk_image_new();
802 gtk_container_add(GTK_CONTAINER(align), info.image_game);
803 gtk_table_attach(GTK_TABLE(table), align, 1, 2, 4, 5, GTK_EXPAND|GTK_FILL, 0, 0, 0);
805 /* cells size combo for editor */
806 gtk_table_attach(GTK_TABLE(table), gd_label_new_printf("<b>Editor</b>"), 2, 3, 2, 3, GTK_EXPAND|GTK_FILL, 0, 0, 0);
807 info.sizecombo_editor=gd_combo_box_new_from_stringv(gd_scaling_name);
808 gtk_combo_box_set_active(GTK_COMBO_BOX(info.sizecombo_editor), gd_cell_scale_editor);
809 gtk_table_attach(GTK_TABLE(table), info.sizecombo_editor, 2, 3, 3, 4, GTK_EXPAND|GTK_FILL, 0, 0, 0);
810 /* image for editor */
811 align=gtk_alignment_new(0.5, 0.5, 0, 0);
812 gtk_widget_set_size_request(align, 64, 64);
813 info.image_editor=gtk_image_new();
814 gtk_container_add(GTK_CONTAINER(align), info.image_editor);
815 gtk_table_attach(GTK_TABLE(table), align, 2, 3, 4, 5, GTK_EXPAND|GTK_FILL, 0, 0, 0);
817 /* add the "changed" signal handlers only here, as by appending the texts, the changes signal whould have been called */
818 g_signal_connect(G_OBJECT(info.sizecombo_game), "changed", G_CALLBACK(settings_window_changed), &info);
819 g_signal_connect(G_OBJECT(info.sizecombo_editor), "changed", G_CALLBACK(settings_window_changed), &info);
820 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(settings_window_changed), &info);
822 /* update images (for the first time) before showing the window */
823 settings_window_update (&info);
825 /* run dialog */
826 gtk_widget_show_all(dialog);
827 gtk_dialog_run(GTK_DIALOG(dialog));
829 /* and now process results. ***************/
831 /* get cell sizes */
832 gd_cell_scale_game=gtk_combo_box_get_active(GTK_COMBO_BOX(info.sizecombo_game));
833 gd_cell_scale_editor=gtk_combo_box_get_active(GTK_COMBO_BOX(info.sizecombo_editor));
834 /* get theme file name from table */
835 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
836 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, THEME_COL_FILENAME, &filename, -1);
837 else
838 filename=NULL;
839 gtk_widget_destroy (dialog);
841 /* load new theme */
842 if (filename) {
843 if (gd_loadcells_file(filename)) {
844 /* if successful, remember theme setting */
845 g_free(gd_theme);
846 gd_theme=g_strdup(filename);
847 } else {
848 /* unable to load; switch to default theme */
849 g_warning("%s: unable to load theme, switching to built-in", filename);
850 g_free(gd_theme);
851 gd_theme=NULL;
854 else {
855 /* no filename means the builtin */
856 g_free(gd_theme);
857 gd_theme=NULL;
858 gd_loadcells_default();
861 /* graphics settings might have changed (ie. pal emu or zoom), so recreate main winow. */
862 gd_main_window_set_title_animation();
864 #ifdef GD_SOUND
865 gd_sound_set_music_volume(gd_sound_music_volume_percent);
866 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
867 #endif
878 void
879 gd_control_settings(GtkWidget *parent)
881 GtkWidget *dialog, *table;
883 dialog=gtk_dialog_new_with_buttons(_("GDash Control Keys"), (GtkWindow *) parent, 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
884 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
885 gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
887 table=gtk_table_new (1, 1, TRUE); /* homogenous! */
888 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), table);
889 gtk_container_set_border_width (GTK_CONTAINER (table), 6);
890 gtk_table_set_row_spacings (GTK_TABLE (table), 6);
891 gtk_table_set_col_spacings (GTK_TABLE (table), 6);
892 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Movements</b>")), 0, 1, 0, 1);
893 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Up"), &gd_gtk_key_up), 2, 3, 0, 1);
894 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Right"), &gd_gtk_key_right), 3, 4, 1, 2);
895 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Down"), &gd_gtk_key_down), 2, 3, 2, 3);
896 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Left"), &gd_gtk_key_left), 1, 2, 1, 2);
897 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Fire</b>")), 0, 1, 3, 4);
898 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Fire"), &gd_gtk_key_fire_1), 1, 2, 3, 4);
899 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Fire (alternative)"), &gd_gtk_key_fire_2), 2, 3, 3, 4);
900 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Suicide</b>")), 0, 1, 4, 5);
901 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Suicide"), &gd_gtk_key_suicide), 1, 2, 4, 5);
903 gd_dialog_add_hint(GTK_DIALOG(dialog), _("Click on a button to change a key. You can set two keys for fire (snapping) for convenience. "
904 "Those behave exactly the same way in the game."));
906 /* run dialog */
907 gtk_widget_show_all(dialog);
908 gtk_dialog_run(GTK_DIALOG(dialog));
910 gtk_widget_destroy(dialog);
918 enum {
919 HS_COLUMN_RANK,
920 HS_COLUMN_NAME,
921 HS_COLUMN_SCORE,
922 HS_COLUMN_BOLD,
923 NUM_COLUMNS,
926 /* a cave name is selected, update the list store with highscore data */
927 #define GD_LISTSTORE "gd-liststore-for-combo"
928 #define GD_HIGHLIGHT_CAVE "gd-highlight-cave"
929 #define GD_HIGHLIGHT_RANK "gd-highlight-rank"
930 static void
931 hs_cave_combo_changed(GtkComboBox *widget, gpointer data)
933 GtkListStore *store=GTK_LIST_STORE(g_object_get_data(G_OBJECT(widget), GD_LISTSTORE));
934 int highlight_cave=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), GD_HIGHLIGHT_CAVE));
935 int highlight_rank=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), GD_HIGHLIGHT_RANK));
936 int i;
937 GdHighScore *scores;
939 gtk_list_store_clear(store);
940 i=gtk_combo_box_get_active(widget);
941 if (i==0)
942 scores=gd_caveset_data->highscore;
943 else
944 scores=gd_return_nth_cave(i-1)->highscore;
946 for (i=0; i<GD_HIGHSCORE_NUM; i++)
947 if (scores[i].score>0) {
948 GtkTreeIter iter;
950 gtk_list_store_append(store, &iter);
951 gtk_list_store_set(store, &iter, HS_COLUMN_RANK, i+1, HS_COLUMN_NAME, scores[i].name, HS_COLUMN_SCORE, scores[i].score,
952 HS_COLUMN_BOLD, (i==highlight_cave && i==highlight_rank)?PANGO_WEIGHT_BOLD:PANGO_WEIGHT_NORMAL, -1);
956 static void
957 hs_clear_highscore(GtkWidget *widget, gpointer data)
959 GtkComboBox *combo=GTK_COMBO_BOX(data);
960 GtkListStore *store=GTK_LIST_STORE(g_object_get_data(G_OBJECT(combo), GD_LISTSTORE));
961 int i;
962 GdHighScore *scores;
964 i=gtk_combo_box_get_active(combo);
965 if (i==0)
966 scores=gd_caveset_data->highscore;
967 else
968 scores=gd_return_nth_cave(i-1)->highscore;
970 /* if there is any entry, delete */
971 gd_clear_highscore(scores);
972 gtk_list_store_clear(store);
975 void
976 gd_show_highscore(GtkWidget *parent, GdCave *cave, gboolean show_clear_button, GdCave *highlight_cave, int highlight_rank)
978 GtkWidget *dialog;
979 int i;
980 char *text;
981 GtkListStore *store;
982 GtkWidget *treeview, *sw;
983 GtkCellRenderer *renderer;
984 GtkTreeViewColumn *column;
985 GtkWidget *combo;
986 GList *iter;
987 int hl_cave;
989 /* dialog window */
990 dialog=gtk_dialog_new_with_buttons(_("Highscores"), (GtkWindow *) parent, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, NULL);
992 store=gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
993 treeview=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
995 renderer=gtk_cell_renderer_text_new();
996 column=gtk_tree_view_column_new_with_attributes(_("Rank"), renderer, "text", HS_COLUMN_RANK, "weight", HS_COLUMN_BOLD, NULL);
997 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
999 renderer=gtk_cell_renderer_text_new();
1000 column=gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", HS_COLUMN_NAME, "weight", HS_COLUMN_BOLD, NULL);
1001 gtk_tree_view_column_set_expand(column, TRUE);
1002 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
1004 renderer=gtk_cell_renderer_text_new();
1005 column=gtk_tree_view_column_new_with_attributes(_("Score"), renderer, "text", HS_COLUMN_SCORE, "weight", HS_COLUMN_BOLD, NULL);
1006 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
1008 combo=gtk_combo_box_new_text();
1009 g_object_set_data(G_OBJECT(combo), GD_LISTSTORE, store);
1010 hl_cave=g_list_index(gd_caveset, highlight_cave);
1011 if (hl_cave==-1)
1012 hl_cave=0;
1013 g_object_set_data(G_OBJECT(combo), GD_HIGHLIGHT_CAVE, GINT_TO_POINTER(hl_cave));
1014 g_object_set_data(G_OBJECT(combo), GD_HIGHLIGHT_RANK, GINT_TO_POINTER(highlight_rank));
1015 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(hs_cave_combo_changed), NULL);
1016 text=g_strdup_printf("[%s]", gd_caveset_data->name);
1017 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
1018 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1019 g_free(text);
1021 for (iter=gd_caveset, i=1; iter!=NULL; iter=iter->next, i++) {
1022 GdCave *c=iter->data;
1024 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), c->name);
1026 /* if this one is the active, select it */
1027 if (c==cave)
1028 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
1031 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), combo, FALSE, FALSE, 6);
1032 sw=gtk_scrolled_window_new(NULL, NULL);
1033 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1034 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1035 gtk_container_add(GTK_CONTAINER(sw), treeview);
1036 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
1038 /* clear button */
1039 if (show_clear_button) {
1040 GtkWidget *button;
1042 button=gtk_button_new_from_stock(GTK_STOCK_CLEAR);
1043 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0);
1044 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(hs_clear_highscore), combo);
1047 /* close button */
1048 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1049 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1051 gtk_window_set_default_size(GTK_WINDOW(dialog), 240, 320);
1052 gtk_widget_show_all(dialog);
1053 gtk_dialog_run(GTK_DIALOG(dialog));
1054 gtk_widget_destroy(dialog);
1056 #undef GD_LISTSTORE
1057 #undef GD_HIGHLIGHT_CAVE
1058 #undef GD_HIGHLIGHT_RANK
1060 /* try to guess which window is active */
1061 static GtkWidget *
1062 guess_active_toplevel()
1064 GtkWidget *parent=NULL;
1065 GList *toplevels, *iter;
1067 /* before doing anything, process updates, as windows may have been opened or closed right at the previous moment */
1068 gdk_window_process_all_updates();
1070 /* if we find a modal window, it is active. */
1071 toplevels=gtk_window_list_toplevels();
1072 for (iter=toplevels; iter!=NULL; iter=iter->next)
1073 if (gtk_window_get_modal(GTK_WINDOW(iter->data)))
1074 parent=iter->data;
1076 /* if no modal window found, search for a focused toplevel */
1077 if (!parent)
1078 for (iter=toplevels; iter!=NULL; iter=iter->next)
1079 if (gtk_window_has_toplevel_focus(GTK_WINDOW(iter->data)))
1080 parent=iter->data;
1082 /* if any of them is focused, just choose the last from the list as a fallback. */
1083 if (!parent && toplevels)
1084 parent=g_list_last(toplevels)->data;
1085 g_list_free(toplevels);
1087 return parent;
1091 * show a warning window
1093 static void
1094 show_message(GtkMessageType type, const char *primary, const char *secondary)
1096 GtkWidget *dialog;
1098 dialog=gtk_message_dialog_new((GtkWindow *) guess_active_toplevel(),
1099 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1100 type, GTK_BUTTONS_OK,
1101 "%s", primary);
1102 gtk_window_set_title(GTK_WINDOW(dialog), "GDash");
1103 /* secondary message exists an is not empty string: */
1104 if (secondary && secondary[0]!=0)
1105 gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG (dialog), "%s", secondary);
1106 gtk_dialog_run (GTK_DIALOG (dialog));
1107 gtk_widget_destroy (dialog);
1110 void
1111 gd_warningmessage(const char *primary, const char *secondary)
1113 show_message(GTK_MESSAGE_WARNING, primary, secondary);
1116 void
1117 gd_errormessage(const char *primary, const char *secondary)
1119 show_message(GTK_MESSAGE_ERROR, primary, secondary);
1122 void
1123 gd_infomessage(const char *primary, const char *secondary)
1125 show_message(GTK_MESSAGE_INFO, primary, secondary);
1128 /* if necessary, ask the user if he doesn't want to save changes to cave */
1129 gboolean
1130 gd_discard_changes (GtkWidget *parent)
1132 GtkWidget *dialog, *button;
1133 gboolean discard;
1135 /* save highscore on every ocassion when the caveset is to be removed from memory */
1136 gd_save_highscore(gd_user_config_dir);
1138 /* caveset is not edited, so pretend user confirmed */
1139 if (!gd_caveset_edited)
1140 return TRUE;
1142 dialog=gtk_message_dialog_new((GtkWindow *) parent, 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Caveset \"%s\" is edited or new replays are added. Discard changes?"), gd_caveset_data->name);
1143 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (dialog), _("If you discard the caveset, all changes and new replays will be lost."));
1144 gtk_dialog_add_button(GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1145 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
1146 /* create a discard button with a trash icon and Discard text */
1147 button=gtk_button_new_with_label(_("_Discard"));
1148 gtk_button_set_image(GTK_BUTTON (button), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
1149 gtk_widget_show (button);
1150 gtk_dialog_add_action_widget(GTK_DIALOG (dialog), button, GTK_RESPONSE_YES);
1152 discard=gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_YES;
1153 gtk_widget_destroy (dialog);
1155 /* return button pressed */
1156 return discard;
1159 gboolean
1160 gd_ask_overwrite(const char *filename)
1162 gboolean result;
1163 char *sec;
1165 /* ask if overwrite file */
1166 sec=g_strdup_printf(_("The file (%s) already exists, and will be overwritten."), filename);
1167 result=gd_question_yesno(_("The file already exists. Do you want to overwrite it?"), sec);
1168 g_free(sec);
1170 return result;
1175 static void
1176 caveset_file_operation_successful(const char *filename)
1178 /* save successful, so remember filename */
1179 /* first we make a copy, as it is possible that filename==caveset_filename (the pointers!) */
1180 char *uri;
1182 /* add to recent chooser */
1183 if (g_path_is_absolute(filename))
1184 uri=g_filename_to_uri(filename, NULL, NULL);
1185 else {
1186 /* make an absolute filename if needed */
1187 char *absolute;
1188 char *currentdir;
1190 currentdir=g_get_current_dir();
1191 absolute=g_build_path(G_DIR_SEPARATOR_S, currentdir, filename, NULL);
1192 g_free(currentdir);
1193 uri=g_filename_to_uri(absolute, NULL, NULL);
1194 g_free(absolute);
1196 gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri);
1197 g_free(uri);
1199 /* if it is a bd file, remember new filename */
1200 if (g_str_has_suffix(filename, ".bd")) {
1201 char *stored;
1203 /* first make copy, then free and set pointer. we might be called with filename=caveset_filename */
1204 stored=g_strdup(filename);
1205 g_free(caveset_filename);
1206 caveset_filename=stored;
1207 } else {
1208 g_free(caveset_filename);
1209 caveset_filename=NULL;
1214 /* save caveset to specified directory, and pop up error message if failed */
1215 static void
1216 caveset_save(const gchar *filename)
1218 gboolean saved;
1220 saved=gd_caveset_save(filename);
1221 if (!saved)
1222 gd_show_last_error(guess_active_toplevel());
1223 else
1224 caveset_file_operation_successful(filename);
1228 void
1229 gd_save_caveset_as(GtkWidget *parent)
1231 GtkWidget *dialog;
1232 GtkFileFilter *filter;
1233 char *filename=NULL, *suggested_name;
1235 dialog=gtk_file_chooser_dialog_new (_("Save File As"), GTK_WINDOW(parent), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
1236 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1238 filter=gtk_file_filter_new();
1239 gtk_file_filter_set_name(filter, _("BDCFF cave sets (*.bd)"));
1240 gtk_file_filter_add_pattern(filter, "*.bd");
1241 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1243 filter=gtk_file_filter_new();
1244 gtk_file_filter_set_name(filter, _("All files (*)"));
1245 gtk_file_filter_add_pattern(filter, "*");
1246 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1248 suggested_name=g_strdup_printf("%s.bd", gd_caveset_data->name);
1249 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), suggested_name);
1250 g_free(suggested_name);
1252 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
1253 filename=gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
1255 /* check if .bd extension should be added */
1256 if (filename) {
1257 char *suffixed;
1259 /* if it has no .bd extension, add one */
1260 if (!g_str_has_suffix(filename, ".bd")) {
1261 suffixed=g_strdup_printf("%s.bd", filename);
1263 g_free(filename);
1264 filename=suffixed;
1268 /* if we have a filename, do the save */
1269 if (filename) {
1270 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1271 /* if exists, ask if overwrite */
1272 if (gd_ask_overwrite(filename))
1273 caveset_save(filename);
1274 } else
1275 /* if did not exist, simply save */
1276 caveset_save(filename);
1278 g_free(filename);
1279 gtk_widget_destroy (dialog);
1283 void
1284 gd_save_caveset(GtkWidget *parent)
1286 if (!caveset_filename)
1287 /* if no filename remembered, rather start the save_as function, which asks for one. */
1288 gd_save_caveset_as(parent);
1289 else
1290 /* if given, save. */
1291 caveset_save(caveset_filename);
1297 /* load a caveset, and remember its filename, it is a bdcff file. */
1298 /* called after "open file" dialogs, and also from the main() of the gtk version */
1299 gboolean
1300 gd_open_caveset_in_ui(const char *filename, gboolean highscore_load_from_bdcff)
1302 gboolean loaded;
1304 gd_clear_error_flag();
1306 loaded=gd_caveset_load_from_file(filename, gd_user_config_dir);
1307 gd_main_window_set_title_animation();
1308 if (loaded)
1309 caveset_file_operation_successful(filename);
1311 /* if successful loading and this is a bd file, and we load highscores from our own config dir */
1312 if (!gd_has_new_error() && g_str_has_suffix(filename, ".bd") && !highscore_load_from_bdcff)
1313 gd_load_highscore(gd_user_config_dir);
1315 /* return true if successful, false if any error */
1316 return !gd_has_new_error();
1322 void
1323 gd_open_caveset(GtkWidget *parent, const char *directory)
1325 GtkWidget *dialog, *check;
1326 GtkFileFilter *filter;
1327 int result;
1328 char *filename=NULL;
1329 gboolean highscore_load_from_bdcff;
1330 int i;
1332 /* if caveset is edited, and user does not want to discard changes */
1333 if (!gd_discard_changes(parent))
1334 return;
1336 dialog=gtk_file_chooser_dialog_new (_("Open File"), (GtkWindow *) parent, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
1337 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1338 check=gtk_check_button_new_with_mnemonic(_("Load _highscores from BDCFF file"));
1340 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), check, FALSE, FALSE, 6);
1341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), gd_use_bdcff_highscore);
1342 gtk_widget_show(check);
1344 filter=gtk_file_filter_new();
1345 gtk_file_filter_set_name(filter, _("GDash cave sets"));
1346 for (i=0; gd_caveset_extensions[i]!=NULL; i++)
1347 gtk_file_filter_add_pattern(filter, gd_caveset_extensions[i]);
1348 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1350 /* if callback shipped with a directory name, show that directory by default */
1351 if (directory)
1352 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), directory);
1353 else
1354 if (last_folder)
1355 /* if we previously had an open command, the directory was remembered */
1356 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), last_folder);
1357 else
1358 /* otherwise user home */
1359 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), g_get_home_dir());
1361 result=gtk_dialog_run(GTK_DIALOG (dialog));
1362 if (result==GTK_RESPONSE_ACCEPT) {
1363 filename=gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
1364 g_free (last_folder);
1365 last_folder=gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER(dialog));
1367 /* read the state of the check button */
1368 highscore_load_from_bdcff=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
1369 gtk_widget_destroy (dialog);
1371 /* WINDOWS GTK+ 20080926 HACK XXX XXX XXX XXX */
1372 /* gtk bug - sometimes the above widget destroy creates an error message. */
1373 /* so we delete the error flag here. */
1374 #ifdef G_OS_WIN32
1375 gd_clear_error_flag();
1376 #endif
1378 if (filename)
1379 gd_open_caveset_in_ui(filename, highscore_load_from_bdcff);
1380 g_free (filename);
1382 if (gd_has_new_error())
1383 gd_show_last_error(parent);
1386 /* load an internal caveset, after checking that the current one is to be saved or not */
1387 void
1388 gd_load_internal(GtkWidget *parent, int i)
1390 if (gd_discard_changes(parent)) {
1391 g_free(caveset_filename);
1392 caveset_filename=NULL; /* forget cave filename, as this one is not loaded from a file... */
1394 gd_caveset_load_from_internal (i, gd_user_config_dir);
1395 gd_infomessage(_("Loaded game:"), gd_caveset_data->name);
1401 /* set a label's markup */
1402 static void
1403 label_set_markup_vprintf(GtkLabel *label, const char *format, va_list args)
1405 char *text;
1407 text=g_strdup_vprintf(format, args);
1408 /* only set if not the same as the old one. saves a lot of cpu time! */
1409 if (!g_str_equal(gtk_label_get_label(label), text))
1410 gtk_label_set_markup(label, text);
1411 g_free(text);
1414 /* create a label, and set its text by a printf; the text will be centered */
1415 GtkWidget *
1416 gd_label_new_printf_centered(const char *format, ...)
1418 va_list args;
1419 GtkWidget *label;
1421 label=gtk_label_new(NULL);
1423 va_start(args, format);
1424 label_set_markup_vprintf(GTK_LABEL(label), format, args);
1425 va_end(args);
1427 return label;
1430 /* create a label, and set its text by a printf */
1431 GtkWidget *
1432 gd_label_new_printf(const char *format, ...)
1434 va_list args;
1435 GtkWidget *label;
1437 label=gtk_label_new(NULL);
1438 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1440 va_start(args, format);
1441 label_set_markup_vprintf(GTK_LABEL(label), format, args);
1442 va_end(args);
1444 return label;
1447 /* set a label's markup with a printf format string */
1448 void
1449 gd_label_set_markup_printf(GtkLabel *label, const char *format, ...)
1451 va_list args;
1453 va_start(args, format);
1454 label_set_markup_vprintf(label, format, args);
1455 va_end(args);
1460 void
1461 gd_show_errors (GtkWidget *parent)
1463 /* create text buffer */
1464 GtkTextIter iter;
1465 GtkTextBuffer *buffer=gtk_text_buffer_new(NULL);
1466 GtkWidget *dialog, *sw, *view;
1467 GList *liter;
1468 int result;
1469 GdkPixbuf *pixbuf_error, *pixbuf_warning, *pixbuf_info;
1471 dialog=gtk_dialog_new_with_buttons (_("GDash Errors"), (GtkWindow *) parent, GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CLEAR, 1, GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
1472 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
1473 gtk_window_set_default_size (GTK_WINDOW (dialog), 512, 384);
1474 sw = gtk_scrolled_window_new (NULL, NULL);
1475 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), sw);
1476 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
1477 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1479 /* get text and show it */
1480 view=gtk_text_view_new_with_buffer (buffer);
1481 gtk_container_add (GTK_CONTAINER (sw), view);
1482 g_object_unref (buffer);
1484 pixbuf_error=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU, NULL);
1485 pixbuf_warning=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU, NULL);
1486 pixbuf_info=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_MENU, NULL);
1487 for (liter=gd_errors; liter!=NULL; liter=liter->next) {
1488 GdErrorMessage *error=liter->data;
1490 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
1491 if (error->flags>=G_LOG_LEVEL_MESSAGE)
1492 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_info);
1493 else
1494 if (error->flags<G_LOG_LEVEL_WARNING)
1495 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_error);
1496 else
1497 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_warning);
1498 gtk_text_buffer_insert(buffer, &iter, error->message, -1);
1499 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1501 g_object_unref(pixbuf_error);
1502 g_object_unref(pixbuf_warning);
1503 g_object_unref(pixbuf_info);
1505 /* set some tags */
1506 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
1507 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
1508 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view), 3);
1509 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 6);
1510 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 6);
1511 gtk_widget_show_all (dialog);
1512 result=gtk_dialog_run (GTK_DIALOG (dialog));
1513 gtk_widget_destroy (dialog);
1515 /* the user has seen the errors, clear the "has new error" flag */
1516 gd_clear_error_flag();
1517 /* maybe clearing the whole error list requested? */
1518 if (result==1)
1519 gd_clear_errors();
1522 void
1523 gd_show_last_error(GtkWidget *parent)
1525 GtkWidget *dialog;
1526 int result;
1527 GdErrorMessage *m;
1529 if (!gd_errors)
1530 return;
1532 /* set new error flag to false, as the user now knows that some error has happened */
1533 gd_clear_error_flag();
1535 m=g_list_last(gd_errors)->data;
1537 dialog=gtk_message_dialog_new ((GtkWindow *) parent,
1538 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1539 GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE,
1540 "%s", m->message);
1541 gtk_dialog_add_buttons(GTK_DIALOG(dialog), _("_Show all"), 1, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1542 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
1543 gtk_window_set_title(GTK_WINDOW(dialog), "GDash");
1544 result=gtk_dialog_run (GTK_DIALOG (dialog));
1545 gtk_widget_destroy (dialog);
1546 if (result==1)
1547 /* user requested to show all errors */
1548 gd_show_errors(parent);
1552 gboolean
1553 gd_question_yesno(const char *primary, const char *secondary)
1555 GtkWidget *dialog;
1556 int response;
1558 dialog=gtk_message_dialog_new ((GtkWindow *) guess_active_toplevel(), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", primary);
1559 if (secondary && !g_str_equal(secondary, ""))
1560 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
1561 response=gtk_dialog_run (GTK_DIALOG (dialog));
1562 gtk_widget_destroy (dialog);
1564 return response==GTK_RESPONSE_YES;
1572 static gboolean
1573 keysim_button_keypress_event(GtkWidget* widget, GdkEventKey* event, gpointer data)
1575 g_assert(event->type==GDK_KEY_PRESS); /* must be true. */
1577 gtk_dialog_response(GTK_DIALOG(widget), event->keyval);
1578 return TRUE; /* and say that we processed the key. */
1581 #define GDASH_KEYSIM_WHAT_FOR "gdash-keysim-what-for"
1582 static void
1583 keysim_button_clicked_cb(GtkWidget *button, gpointer data)
1585 const char *what_for=g_object_get_data(G_OBJECT(button), GDASH_KEYSIM_WHAT_FOR);
1586 guint *keyval=(guint *)data;
1587 GtkWidget *dialog, *table;
1588 int result;
1590 /* dialog which has its keypress event connected to the handler above */
1591 dialog=gtk_dialog_new_with_buttons(_("Select Key"), GTK_WINDOW(gtk_widget_get_toplevel(button)),
1592 GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
1593 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
1594 table=gtk_table_new(1,1, FALSE);
1595 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1596 gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1597 gtk_container_set_border_width(GTK_CONTAINER(table), 6);
1598 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
1599 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Press key for action:")), 0, 1, 0, 1);
1600 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf("<b>%s</b>", what_for), 0, 1, 1, 2);
1601 g_signal_connect(G_OBJECT(dialog), "key_press_event", G_CALLBACK(keysim_button_keypress_event), dialog);
1603 gtk_widget_show_all(dialog);
1604 result=gtk_dialog_run(GTK_DIALOG(dialog));
1605 if (result>=0) {
1606 /* if positive, it must be a keyval. gtk_response_cancel and gtk_response delete is negative. */
1607 *keyval=result;
1608 gtk_button_set_label(GTK_BUTTON(button), gdk_keyval_name(*keyval));
1610 gtk_widget_destroy(dialog);
1615 GtkWidget *
1616 gd_keysim_button(const char *what_for, guint *keyval)
1618 GtkWidget *button;
1619 char *tooltip;
1621 g_assert(keyval!=NULL);
1623 /* the button shows the current value in its name */
1624 button=gtk_button_new_with_label(gdk_keyval_name(*keyval));
1625 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(keysim_button_clicked_cb), keyval);
1626 g_object_set_data(G_OBJECT(button), GDASH_KEYSIM_WHAT_FOR, (gpointer) what_for);
1627 tooltip=g_strdup_printf(_("Click here to set the key for action: %s"), what_for);
1628 gtk_widget_set_tooltip_text(button, tooltip);
1629 g_free(tooltip);
1631 return button;
1634 #undef GDASH_KEYSIM_WHAT_FOR
1639 void
1640 gd_dialog_add_hint(GtkDialog *dialog, const char *hint)
1642 GtkWidget *hbox, *label;
1643 /* turn off separator, as it does not look nice with the hint */
1644 gtk_dialog_set_has_separator(dialog, FALSE);
1646 hbox=gtk_hbox_new(FALSE, 6);
1647 label=gd_label_new_printf_centered(hint);
1648 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1650 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, TRUE, 0);
1651 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG), FALSE, FALSE, 0);
1652 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);