20090122
[gdash.git] / src / gtkui.c
blob5f55d51cbc283b6d829bd451382ce4dbc373c4b3
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 gd_sound_set_music_volume(gd_sound_music_volume_percent);
865 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
876 void
877 gd_control_settings(GtkWidget *parent)
879 GtkWidget *dialog, *table;
881 dialog=gtk_dialog_new_with_buttons(_("GDash Control Keys"), (GtkWindow *) parent, 0, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
882 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
883 gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
885 table=gtk_table_new (1, 1, TRUE); /* homogenous! */
886 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), table);
887 gtk_container_set_border_width (GTK_CONTAINER (table), 6);
888 gtk_table_set_row_spacings (GTK_TABLE (table), 6);
889 gtk_table_set_col_spacings (GTK_TABLE (table), 6);
890 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Movements</b>")), 0, 1, 0, 1);
891 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Up"), &gd_gtk_key_up), 2, 3, 0, 1);
892 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Right"), &gd_gtk_key_right), 3, 4, 1, 2);
893 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Down"), &gd_gtk_key_down), 2, 3, 2, 3);
894 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Left"), &gd_gtk_key_left), 1, 2, 1, 2);
895 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Fire</b>")), 0, 1, 3, 4);
896 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Fire"), &gd_gtk_key_fire_1), 1, 2, 3, 4);
897 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Fire (alternative)"), &gd_gtk_key_fire_2), 2, 3, 3, 4);
898 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("<b>Suicide</b>")), 0, 1, 4, 5);
899 gtk_table_attach_defaults(GTK_TABLE(table), gd_keysim_button(_("Suicide"), &gd_gtk_key_suicide), 1, 2, 4, 5);
901 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. "
902 "Those behave exactly the same way in the game."));
904 /* run dialog */
905 gtk_widget_show_all(dialog);
906 gtk_dialog_run(GTK_DIALOG(dialog));
908 gtk_widget_destroy(dialog);
916 enum {
917 HS_COLUMN_RANK,
918 HS_COLUMN_NAME,
919 HS_COLUMN_SCORE,
920 HS_COLUMN_BOLD,
921 NUM_COLUMNS,
924 /* a cave name is selected, update the list store with highscore data */
925 #define GD_LISTSTORE "gd-liststore-for-combo"
926 #define GD_HIGHLIGHT_CAVE "gd-highlight-cave"
927 #define GD_HIGHLIGHT_RANK "gd-highlight-rank"
928 static void
929 hs_cave_combo_changed(GtkComboBox *widget, gpointer data)
931 GtkListStore *store=GTK_LIST_STORE(g_object_get_data(G_OBJECT(widget), GD_LISTSTORE));
932 int highlight_cave=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), GD_HIGHLIGHT_CAVE));
933 int highlight_rank=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), GD_HIGHLIGHT_RANK));
934 int i;
935 GdHighScore *scores;
937 gtk_list_store_clear(store);
938 i=gtk_combo_box_get_active(widget);
939 if (i==0)
940 scores=gd_caveset_data->highscore;
941 else
942 scores=gd_return_nth_cave(i-1)->highscore;
944 for (i=0; i<GD_HIGHSCORE_NUM; i++)
945 if (scores[i].score>0) {
946 GtkTreeIter iter;
948 gtk_list_store_append(store, &iter);
949 gtk_list_store_set(store, &iter, HS_COLUMN_RANK, i+1, HS_COLUMN_NAME, scores[i].name, HS_COLUMN_SCORE, scores[i].score,
950 HS_COLUMN_BOLD, (i==highlight_cave && i==highlight_rank)?PANGO_WEIGHT_BOLD:PANGO_WEIGHT_NORMAL, -1);
954 static void
955 hs_clear_highscore(GtkWidget *widget, gpointer data)
957 GtkComboBox *combo=GTK_COMBO_BOX(data);
958 GtkListStore *store=GTK_LIST_STORE(g_object_get_data(G_OBJECT(combo), GD_LISTSTORE));
959 int i;
960 GdHighScore *scores;
962 i=gtk_combo_box_get_active(combo);
963 if (i==0)
964 scores=gd_caveset_data->highscore;
965 else
966 scores=gd_return_nth_cave(i-1)->highscore;
968 /* if there is any entry, delete */
969 gd_clear_highscore(scores);
970 gtk_list_store_clear(store);
973 void
974 gd_show_highscore(GtkWidget *parent, GdCave *cave, gboolean show_clear_button, GdCave *highlight_cave, int highlight_rank)
976 GtkWidget *dialog;
977 int i;
978 char *text;
979 GtkListStore *store;
980 GtkWidget *treeview, *sw;
981 GtkCellRenderer *renderer;
982 GtkTreeViewColumn *column;
983 GtkWidget *combo;
984 GList *iter;
985 int hl_cave;
987 /* dialog window */
988 dialog=gtk_dialog_new_with_buttons(_("Highscores"), (GtkWindow *) parent, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, NULL);
990 store=gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
991 treeview=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
993 renderer=gtk_cell_renderer_text_new();
994 column=gtk_tree_view_column_new_with_attributes(_("Rank"), renderer, "text", HS_COLUMN_RANK, "weight", HS_COLUMN_BOLD, NULL);
995 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
997 renderer=gtk_cell_renderer_text_new();
998 column=gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", HS_COLUMN_NAME, "weight", HS_COLUMN_BOLD, NULL);
999 gtk_tree_view_column_set_expand(column, TRUE);
1000 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
1002 renderer=gtk_cell_renderer_text_new();
1003 column=gtk_tree_view_column_new_with_attributes(_("Score"), renderer, "text", HS_COLUMN_SCORE, "weight", HS_COLUMN_BOLD, NULL);
1004 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
1006 combo=gtk_combo_box_new_text();
1007 g_object_set_data(G_OBJECT(combo), GD_LISTSTORE, store);
1008 hl_cave=g_list_index(gd_caveset, highlight_cave);
1009 if (hl_cave==-1)
1010 hl_cave=0;
1011 g_object_set_data(G_OBJECT(combo), GD_HIGHLIGHT_CAVE, GINT_TO_POINTER(hl_cave));
1012 g_object_set_data(G_OBJECT(combo), GD_HIGHLIGHT_RANK, GINT_TO_POINTER(highlight_rank));
1013 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(hs_cave_combo_changed), NULL);
1014 text=g_strdup_printf("[%s]", gd_caveset_data->name);
1015 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
1016 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1017 g_free(text);
1019 for (iter=gd_caveset, i=1; iter!=NULL; iter=iter->next, i++) {
1020 GdCave *c=iter->data;
1022 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), c->name);
1024 /* if this one is the active, select it */
1025 if (c==cave)
1026 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
1029 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), combo, FALSE, FALSE, 6);
1030 sw=gtk_scrolled_window_new(NULL, NULL);
1031 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
1032 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1033 gtk_container_add(GTK_CONTAINER(sw), treeview);
1034 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), sw);
1036 /* clear button */
1037 if (show_clear_button) {
1038 GtkWidget *button;
1040 button=gtk_button_new_from_stock(GTK_STOCK_CLEAR);
1041 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0);
1042 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(hs_clear_highscore), combo);
1045 /* close button */
1046 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1047 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1049 gtk_window_set_default_size(GTK_WINDOW(dialog), 240, 320);
1050 gtk_widget_show_all(dialog);
1051 gtk_dialog_run(GTK_DIALOG(dialog));
1052 gtk_widget_destroy(dialog);
1054 #undef GD_LISTSTORE
1055 #undef GD_HIGHLIGHT_CAVE
1056 #undef GD_HIGHLIGHT_RANK
1058 /* try to guess which window is active */
1059 static GtkWidget *
1060 guess_active_toplevel()
1062 GtkWidget *parent=NULL;
1063 GList *toplevels, *iter;
1065 /* before doing anything, process updates, as windows may have been opened or closed right at the previous moment */
1066 gdk_window_process_all_updates();
1068 /* if we find a modal window, it is active. */
1069 toplevels=gtk_window_list_toplevels();
1070 for (iter=toplevels; iter!=NULL; iter=iter->next)
1071 if (gtk_window_get_modal(GTK_WINDOW(iter->data)))
1072 parent=iter->data;
1074 /* if no modal window found, search for a focused toplevel */
1075 if (!parent)
1076 for (iter=toplevels; iter!=NULL; iter=iter->next)
1077 if (gtk_window_has_toplevel_focus(GTK_WINDOW(iter->data)))
1078 parent=iter->data;
1080 /* if any of them is focused, just choose the last from the list as a fallback. */
1081 if (!parent && toplevels)
1082 parent=g_list_last(toplevels)->data;
1083 g_list_free(toplevels);
1085 return parent;
1089 * show a warning window
1091 static void
1092 show_message(GtkMessageType type, const char *primary, const char *secondary)
1094 GtkWidget *dialog;
1096 dialog=gtk_message_dialog_new((GtkWindow *) guess_active_toplevel(),
1097 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1098 type, GTK_BUTTONS_OK,
1099 "%s", primary);
1100 gtk_window_set_title(GTK_WINDOW(dialog), "GDash");
1101 /* secondary message exists an is not empty string: */
1102 if (secondary && secondary[0]!=0)
1103 gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG (dialog), "%s", secondary);
1104 gtk_dialog_run (GTK_DIALOG (dialog));
1105 gtk_widget_destroy (dialog);
1108 void
1109 gd_warningmessage(const char *primary, const char *secondary)
1111 show_message(GTK_MESSAGE_WARNING, primary, secondary);
1114 void
1115 gd_errormessage(const char *primary, const char *secondary)
1117 show_message(GTK_MESSAGE_ERROR, primary, secondary);
1120 void
1121 gd_infomessage(const char *primary, const char *secondary)
1123 show_message(GTK_MESSAGE_INFO, primary, secondary);
1126 /* if necessary, ask the user if he doesn't want to save changes to cave */
1127 gboolean
1128 gd_discard_changes (GtkWidget *parent)
1130 GtkWidget *dialog, *button;
1131 gboolean discard;
1133 /* save highscore on every ocassion when the caveset is to be removed from memory */
1134 gd_save_highscore(gd_user_config_dir);
1136 /* caveset is not edited, so pretend user confirmed */
1137 if (!gd_caveset_edited)
1138 return TRUE;
1140 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);
1141 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (dialog), _("If you discard the caveset, all changes and new replays will be lost."));
1142 gtk_dialog_add_button(GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1143 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
1144 /* create a discard button with a trash icon and Discard text */
1145 button=gtk_button_new_with_label(_("_Discard"));
1146 gtk_button_set_image(GTK_BUTTON (button), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
1147 gtk_widget_show (button);
1148 gtk_dialog_add_action_widget(GTK_DIALOG (dialog), button, GTK_RESPONSE_YES);
1150 discard=gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_YES;
1151 gtk_widget_destroy (dialog);
1153 /* return button pressed */
1154 return discard;
1157 gboolean
1158 gd_ask_overwrite(const char *filename)
1160 gboolean result;
1161 char *sec;
1163 /* ask if overwrite file */
1164 sec=g_strdup_printf(_("The file (%s) already exists, and will be overwritten."), filename);
1165 result=gd_question_yesno(_("The file already exists. Do you want to overwrite it?"), sec);
1166 g_free(sec);
1168 return result;
1173 static void
1174 caveset_file_operation_successful(const char *filename)
1176 /* save successful, so remember filename */
1177 /* first we make a copy, as it is possible that filename==caveset_filename (the pointers!) */
1178 char *uri;
1180 /* add to recent chooser */
1181 if (g_path_is_absolute(filename))
1182 uri=g_filename_to_uri(filename, NULL, NULL);
1183 else {
1184 /* make an absolute filename if needed */
1185 char *absolute;
1186 char *currentdir;
1188 currentdir=g_get_current_dir();
1189 absolute=g_build_path(G_DIR_SEPARATOR_S, currentdir, filename, NULL);
1190 g_free(currentdir);
1191 uri=g_filename_to_uri(absolute, NULL, NULL);
1192 g_free(absolute);
1194 gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri);
1195 g_free(uri);
1197 /* if it is a bd file, remember new filename */
1198 if (g_str_has_suffix(filename, ".bd")) {
1199 char *stored;
1201 /* first make copy, then free and set pointer. we might be called with filename=caveset_filename */
1202 stored=g_strdup(filename);
1203 g_free(caveset_filename);
1204 caveset_filename=stored;
1205 } else {
1206 g_free(caveset_filename);
1207 caveset_filename=NULL;
1212 /* save caveset to specified directory, and pop up error message if failed */
1213 static void
1214 caveset_save(const gchar *filename)
1216 gboolean saved;
1218 saved=gd_caveset_save(filename);
1219 if (!saved)
1220 gd_show_last_error(guess_active_toplevel());
1221 else
1222 caveset_file_operation_successful(filename);
1226 void
1227 gd_save_caveset_as(GtkWidget *parent)
1229 GtkWidget *dialog;
1230 GtkFileFilter *filter;
1231 char *filename=NULL, *suggested_name;
1233 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);
1234 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1236 filter=gtk_file_filter_new();
1237 gtk_file_filter_set_name(filter, _("BDCFF cave sets (*.bd)"));
1238 gtk_file_filter_add_pattern(filter, "*.bd");
1239 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1241 filter=gtk_file_filter_new();
1242 gtk_file_filter_set_name(filter, _("All files (*)"));
1243 gtk_file_filter_add_pattern(filter, "*");
1244 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1246 suggested_name=g_strdup_printf("%s.bd", gd_caveset_data->name);
1247 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), suggested_name);
1248 g_free(suggested_name);
1250 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
1251 filename=gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
1253 /* check if .bd extension should be added */
1254 if (filename) {
1255 char *suffixed;
1257 /* if it has no .bd extension, add one */
1258 if (!g_str_has_suffix(filename, ".bd")) {
1259 suffixed=g_strdup_printf("%s.bd", filename);
1261 g_free(filename);
1262 filename=suffixed;
1266 /* if we have a filename, do the save */
1267 if (filename) {
1268 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1269 /* if exists, ask if overwrite */
1270 if (gd_ask_overwrite(filename))
1271 caveset_save(filename);
1272 } else
1273 /* if did not exist, simply save */
1274 caveset_save(filename);
1276 g_free(filename);
1277 gtk_widget_destroy (dialog);
1281 void
1282 gd_save_caveset(GtkWidget *parent)
1284 if (!caveset_filename)
1285 /* if no filename remembered, rather start the save_as function, which asks for one. */
1286 gd_save_caveset_as(parent);
1287 else
1288 /* if given, save. */
1289 caveset_save(caveset_filename);
1295 /* load a caveset, and remember its filename, it is a bdcff file. */
1296 /* called after "open file" dialogs, and also from the main() of the gtk version */
1297 gboolean
1298 gd_open_caveset_in_ui(const char *filename, gboolean highscore_load_from_bdcff)
1300 gboolean loaded;
1302 gd_clear_error_flag();
1304 loaded=gd_caveset_load_from_file(filename, gd_user_config_dir);
1305 gd_main_window_set_title_animation();
1306 if (loaded)
1307 caveset_file_operation_successful(filename);
1309 /* if successful loading and this is a bd file, and we load highscores from our own config dir */
1310 if (!gd_has_new_error() && g_str_has_suffix(filename, ".bd") && !highscore_load_from_bdcff)
1311 gd_load_highscore(gd_user_config_dir);
1313 /* return true if successful, false if any error */
1314 return !gd_has_new_error();
1320 void
1321 gd_open_caveset(GtkWidget *parent, const char *directory)
1323 GtkWidget *dialog, *check;
1324 GtkFileFilter *filter;
1325 int result;
1326 char *filename=NULL;
1327 gboolean highscore_load_from_bdcff;
1328 int i;
1330 /* if caveset is edited, and user does not want to discard changes */
1331 if (!gd_discard_changes(parent))
1332 return;
1334 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);
1335 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1336 check=gtk_check_button_new_with_mnemonic(_("Load _highscores from BDCFF file"));
1338 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), check, FALSE, FALSE, 6);
1339 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), gd_use_bdcff_highscore);
1340 gtk_widget_show(check);
1342 filter=gtk_file_filter_new();
1343 gtk_file_filter_set_name(filter, _("GDash cave sets"));
1344 for (i=0; gd_caveset_extensions[i]!=NULL; i++)
1345 gtk_file_filter_add_pattern(filter, gd_caveset_extensions[i]);
1346 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
1348 /* if callback shipped with a directory name, show that directory by default */
1349 if (directory)
1350 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), directory);
1351 else
1352 if (last_folder)
1353 /* if we previously had an open command, the directory was remembered */
1354 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), last_folder);
1355 else
1356 /* otherwise user home */
1357 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog), g_get_home_dir());
1359 result=gtk_dialog_run(GTK_DIALOG (dialog));
1360 if (result==GTK_RESPONSE_ACCEPT) {
1361 filename=gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
1362 g_free (last_folder);
1363 last_folder=gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER(dialog));
1365 /* read the state of the check button */
1366 highscore_load_from_bdcff=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check));
1367 gtk_widget_destroy (dialog);
1369 /* WINDOWS GTK+ 20080926 HACK XXX XXX XXX XXX */
1370 /* gtk bug - sometimes the above widget destroy creates an error message. */
1371 /* so we delete the error flag here. */
1372 #ifdef G_OS_WIN32
1373 gd_clear_error_flag();
1374 #endif
1376 if (filename)
1377 gd_open_caveset_in_ui(filename, highscore_load_from_bdcff);
1378 g_free (filename);
1380 if (gd_has_new_error())
1381 gd_show_last_error(parent);
1384 /* load an internal caveset, after checking that the current one is to be saved or not */
1385 void
1386 gd_load_internal(GtkWidget *parent, int i)
1388 if (gd_discard_changes(parent)) {
1389 g_free(caveset_filename);
1390 caveset_filename=NULL; /* forget cave filename, as this one is not loaded from a file... */
1392 gd_caveset_load_from_internal (i, gd_user_config_dir);
1393 gd_infomessage(_("Loaded game:"), gd_caveset_data->name);
1399 /* set a label's markup */
1400 static void
1401 label_set_markup_vprintf(GtkLabel *label, const char *format, va_list args)
1403 char *text;
1405 text=g_strdup_vprintf(format, args);
1406 /* only set if not the same as the old one. saves a lot of cpu time! */
1407 if (!g_str_equal(gtk_label_get_label(label), text))
1408 gtk_label_set_markup(label, text);
1409 g_free(text);
1412 /* create a label, and set its text by a printf; the text will be centered */
1413 GtkWidget *
1414 gd_label_new_printf_centered(const char *format, ...)
1416 va_list args;
1417 GtkWidget *label;
1419 label=gtk_label_new(NULL);
1421 va_start(args, format);
1422 label_set_markup_vprintf(GTK_LABEL(label), format, args);
1423 va_end(args);
1425 return label;
1428 /* create a label, and set its text by a printf */
1429 GtkWidget *
1430 gd_label_new_printf(const char *format, ...)
1432 va_list args;
1433 GtkWidget *label;
1435 label=gtk_label_new(NULL);
1436 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1438 va_start(args, format);
1439 label_set_markup_vprintf(GTK_LABEL(label), format, args);
1440 va_end(args);
1442 return label;
1445 /* set a label's markup with a printf format string */
1446 void
1447 gd_label_set_markup_printf(GtkLabel *label, const char *format, ...)
1449 va_list args;
1451 va_start(args, format);
1452 label_set_markup_vprintf(label, format, args);
1453 va_end(args);
1458 void
1459 gd_show_errors (GtkWidget *parent)
1461 /* create text buffer */
1462 GtkTextIter iter;
1463 GtkTextBuffer *buffer=gtk_text_buffer_new(NULL);
1464 GtkWidget *dialog, *sw, *view;
1465 GList *liter;
1466 int result;
1467 GdkPixbuf *pixbuf_error, *pixbuf_warning, *pixbuf_info;
1469 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);
1470 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
1471 gtk_window_set_default_size (GTK_WINDOW (dialog), 512, 384);
1472 sw = gtk_scrolled_window_new (NULL, NULL);
1473 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), sw);
1474 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
1475 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1477 /* get text and show it */
1478 view=gtk_text_view_new_with_buffer (buffer);
1479 gtk_container_add (GTK_CONTAINER (sw), view);
1480 g_object_unref (buffer);
1482 pixbuf_error=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU, NULL);
1483 pixbuf_warning=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU, NULL);
1484 pixbuf_info=gtk_widget_render_icon(view, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_MENU, NULL);
1485 for (liter=gd_errors; liter!=NULL; liter=liter->next) {
1486 GdErrorMessage *error=liter->data;
1488 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
1489 if (error->flags>=G_LOG_LEVEL_MESSAGE)
1490 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_info);
1491 else
1492 if (error->flags<G_LOG_LEVEL_WARNING)
1493 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_error);
1494 else
1495 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf_warning);
1496 gtk_text_buffer_insert(buffer, &iter, error->message, -1);
1497 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
1499 g_object_unref(pixbuf_error);
1500 g_object_unref(pixbuf_warning);
1501 g_object_unref(pixbuf_info);
1503 /* set some tags */
1504 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
1505 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
1506 gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (view), 3);
1507 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (view), 6);
1508 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (view), 6);
1509 gtk_widget_show_all (dialog);
1510 result=gtk_dialog_run (GTK_DIALOG (dialog));
1511 gtk_widget_destroy (dialog);
1513 /* the user has seen the errors, clear the "has new error" flag */
1514 gd_clear_error_flag();
1515 /* maybe clearing the whole error list requested? */
1516 if (result==1)
1517 gd_clear_errors();
1520 void
1521 gd_show_last_error(GtkWidget *parent)
1523 GtkWidget *dialog;
1524 int result;
1525 GdErrorMessage *m;
1527 if (!gd_errors)
1528 return;
1530 /* set new error flag to false, as the user now knows that some error has happened */
1531 gd_clear_error_flag();
1533 m=g_list_last(gd_errors)->data;
1535 dialog=gtk_message_dialog_new ((GtkWindow *) parent,
1536 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1537 GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE,
1538 "%s", m->message);
1539 gtk_dialog_add_buttons(GTK_DIALOG(dialog), _("_Show all"), 1, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1540 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
1541 gtk_window_set_title(GTK_WINDOW(dialog), "GDash");
1542 result=gtk_dialog_run (GTK_DIALOG (dialog));
1543 gtk_widget_destroy (dialog);
1544 if (result==1)
1545 /* user requested to show all errors */
1546 gd_show_errors(parent);
1550 gboolean
1551 gd_question_yesno(const char *primary, const char *secondary)
1553 GtkWidget *dialog;
1554 int response;
1556 dialog=gtk_message_dialog_new ((GtkWindow *) guess_active_toplevel(), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", primary);
1557 if (secondary && !g_str_equal(secondary, ""))
1558 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
1559 response=gtk_dialog_run (GTK_DIALOG (dialog));
1560 gtk_widget_destroy (dialog);
1562 return response==GTK_RESPONSE_YES;
1570 static gboolean
1571 keysim_button_keypress_event(GtkWidget* widget, GdkEventKey* event, gpointer data)
1573 g_assert(event->type==GDK_KEY_PRESS); /* must be true. */
1575 gtk_dialog_response(GTK_DIALOG(widget), event->keyval);
1576 return TRUE; /* and say that we processed the key. */
1579 #define GDASH_KEYSIM_WHAT_FOR "gdash-keysim-what-for"
1580 static void
1581 keysim_button_clicked_cb(GtkWidget *button, gpointer data)
1583 const char *what_for=g_object_get_data(G_OBJECT(button), GDASH_KEYSIM_WHAT_FOR);
1584 guint *keyval=(guint *)data;
1585 GtkWidget *dialog, *table;
1586 int result;
1588 /* dialog which has its keypress event connected to the handler above */
1589 dialog=gtk_dialog_new_with_buttons(_("Select Key"), GTK_WINDOW(gtk_widget_get_toplevel(button)),
1590 GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
1591 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
1592 table=gtk_table_new(1,1, FALSE);
1593 gtk_table_set_row_spacings(GTK_TABLE(table), 6);
1594 gtk_table_set_col_spacings(GTK_TABLE(table), 6);
1595 gtk_container_set_border_width(GTK_CONTAINER(table), 6);
1596 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
1597 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf(_("Press key for action:")), 0, 1, 0, 1);
1598 gtk_table_attach_defaults(GTK_TABLE(table), gd_label_new_printf("<b>%s</b>", what_for), 0, 1, 1, 2);
1599 g_signal_connect(G_OBJECT(dialog), "key_press_event", G_CALLBACK(keysim_button_keypress_event), dialog);
1601 gtk_widget_show_all(dialog);
1602 result=gtk_dialog_run(GTK_DIALOG(dialog));
1603 if (result>=0) {
1604 /* if positive, it must be a keyval. gtk_response_cancel and gtk_response delete is negative. */
1605 *keyval=result;
1606 gtk_button_set_label(GTK_BUTTON(button), gdk_keyval_name(*keyval));
1608 gtk_widget_destroy(dialog);
1613 GtkWidget *
1614 gd_keysim_button(const char *what_for, guint *keyval)
1616 GtkWidget *button;
1617 char *tooltip;
1619 g_assert(keyval!=NULL);
1621 /* the button shows the current value in its name */
1622 button=gtk_button_new_with_label(gdk_keyval_name(*keyval));
1623 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(keysim_button_clicked_cb), keyval);
1624 g_object_set_data(G_OBJECT(button), GDASH_KEYSIM_WHAT_FOR, (gpointer) what_for);
1625 tooltip=g_strdup_printf(_("Click here to set the key for action: %s"), what_for);
1626 gtk_widget_set_tooltip_text(button, tooltip);
1627 g_free(tooltip);
1629 return button;
1632 #undef GDASH_KEYSIM_WHAT_FOR
1637 void
1638 gd_dialog_add_hint(GtkDialog *dialog, const char *hint)
1640 GtkWidget *hbox, *label;
1641 /* turn off separator, as it does not look nice with the hint */
1642 gtk_dialog_set_has_separator(dialog, FALSE);
1644 hbox=gtk_hbox_new(FALSE, 6);
1645 label=gd_label_new_printf_centered(hint);
1646 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1648 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, TRUE, 0);
1649 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG), FALSE, FALSE, 0);
1650 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);