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.
17 #include <glib/gi18n.h>
18 #include <glib/gstdio.h>
19 #include <gdk-pixbuf/gdk-pixbuf.h>
21 #include <gdk/gdkkeysyms.h>
33 /* pixbufs of icons and the like */
35 /* title image and icon */
38 static char *caveset_filename
=NULL
;
39 static char *last_folder
=NULL
;
44 gd_register_stock_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
;
81 factory
=gtk_icon_factory_new();
82 for (i
=0; i
< G_N_ELEMENTS (icons
); i
++) {
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
);
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.
107 gd_create_title_animation()
110 GdkPixbuf
*tile
, *tile_black
;
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
);
124 g_warning("Caveset is storing an invalid title screen image.");
125 g_string_assign(gd_caveset_data
->title_screen
, "");
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
);
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 */
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
);
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. */
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
);
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 */
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
++) {
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
);
232 * functions for the settings window.
233 * these implement the list view, which shows available graphics.
244 find_image_in_store(GtkTreeModel
*store
, const gchar
*find_name
, GtkTreeIter
*set_iter
)
248 if (gtk_tree_model_get_iter_first(store
, &iter
)) {
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
))) {
261 } while (gtk_tree_model_iter_next(store
, &iter
));
264 /* not found: return false */
269 add_image_to_store (GtkListStore
*store
, const char *filename
)
271 static GtkTreeIter iter
;
272 GdkPixbuf
*pixbuf
, *cell_pixbuf
;
275 int cell_num
=gd_elements
[O_PLAYER
].image_game
; /* load the image of the player from every pixbuf */
278 pixbuf
=gdk_pixbuf_new_from_file(filename
, &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. */
287 /* check if pixbuf is ok */
288 if (gd_is_pixbuf_ok_for_theme(pixbuf
)!=NULL
) {
289 g_object_unref(pixbuf
);
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
);
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);
309 g_object_unref(cell_pixbuf
); /* the store has its own ref */
315 add_dir_to_store(GtkListStore
*store
, const char *dirname
)
319 /* search directory */
320 dir
=g_dir_open(dirname
, 0, &error
);
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
);
335 /* unable to open directory */
336 g_warning("%s", error
->message
);
342 gd_create_themes_list()
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
);
366 /****************************
371 typedef struct pref_info
{
373 GtkWidget
*treeview
, *sizecombo_game
, *sizecombo_editor
;
374 GtkWidget
*image_game
, *image_editor
;
378 settings_window_update(pref_info
* info
)
385 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(info
->treeview
)), &model
, &iter
))
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
);
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
);
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. */
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. */
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. */
434 GSList
*formats
=gdk_pixbuf_get_formats();
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
;
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
++) {
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
);
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
);
475 /* file open dialog, with filters for image types gdk-pixbuf recognizes. */
477 gd_select_image_file(const char *title
, GtkWidget
*parent
)
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
));
499 gtk_widget_destroy (dialog
);
505 add_theme_cb(GtkWidget
*widget
, gpointer data
)
507 pref_info
*info
=(pref_info
*)data
;
510 filename
=gd_select_image_file(_("Add Theme from Image File"), info
->dialog
);
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
;
523 basename
=g_path_get_basename(filename
);
524 new_filename
=g_build_path(G_DIR_SEPARATOR_S
, gd_user_config_dir
, basename
, NULL
);
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
)) {
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
);
538 gd_errormessage(error
->message
, NULL
);
542 g_free(new_filename
);
545 gd_errormessage(_("The selected image cannot be used as a GDash theme."), error_msg
);
551 remove_theme_cb(GtkWidget
*widget
, gpointer data
)
553 pref_info
*info
=(pref_info
*)data
;
555 GtkTreeSelection
*selection
;
557 char *filename
, *seen_name
;
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
))
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 */
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
) {
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
);
583 gd_errormessage(_("Cannot delete the image file."), filename
);
588 gd_combo_box_new_from_stringv(const char **str
)
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 */
600 /* when a combo box created from a stringv gets updated. data should point to the integer to be changed. */
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. */
614 gd_preferences (GtkWidget
*parent
)
616 typedef enum _settingtype
{
626 const char *description
;
628 gboolean update
; /* if sample pixbuf should be updated when changing this option */
629 const char **stringv
;
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
},
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
},
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
;
672 GtkWidget
*sw
, *align
, *hbox
;
674 GtkTreeSelection
*selection
;
678 dialog
=gtk_dialog_new_with_buttons (_("GDash Preferences"), (GtkWindow
*) parent
, GTK_DIALOG_DESTROY_WITH_PARENT
, NULL
);
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);
707 for (i
=0; i
<G_N_ELEMENTS(options
); i
++) {
708 GtkWidget
*widget
, *hbox
;
710 switch (options
[i
].type
) {
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);
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
);
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);
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);
754 row
=-1; /* so row++ under will result in zero */
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);
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
);
826 gtk_widget_show_all(dialog
);
827 gtk_dialog_run(GTK_DIALOG(dialog
));
829 /* and now process results. ***************/
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);
839 gtk_widget_destroy (dialog
);
843 if (gd_loadcells_file(filename
)) {
844 /* if successful, remember theme setting */
846 gd_theme
=g_strdup(filename
);
848 /* unable to load; switch to default theme */
849 g_warning("%s: unable to load theme, switching to built-in", filename
);
855 /* no filename means the builtin */
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();
865 gd_sound_set_music_volume(gd_sound_music_volume_percent
);
866 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent
);
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."));
907 gtk_widget_show_all(dialog
);
908 gtk_dialog_run(GTK_DIALOG(dialog
));
910 gtk_widget_destroy(dialog
);
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"
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
));
939 gtk_list_store_clear(store
);
940 i
=gtk_combo_box_get_active(widget
);
942 scores
=gd_caveset_data
->highscore
;
944 scores
=gd_return_nth_cave(i
-1)->highscore
;
946 for (i
=0; i
<GD_HIGHSCORE_NUM
; i
++)
947 if (scores
[i
].score
>0) {
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);
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
));
964 i
=gtk_combo_box_get_active(combo
);
966 scores
=gd_caveset_data
->highscore
;
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
);
976 gd_show_highscore(GtkWidget
*parent
, GdCave
*cave
, gboolean show_clear_button
, GdCave
*highlight_cave
, int highlight_rank
)
982 GtkWidget
*treeview
, *sw
;
983 GtkCellRenderer
*renderer
;
984 GtkTreeViewColumn
*column
;
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
);
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);
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 */
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
);
1039 if (show_clear_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
);
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
);
1057 #undef GD_HIGHLIGHT_CAVE
1058 #undef GD_HIGHLIGHT_RANK
1060 /* try to guess which window is active */
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
)))
1076 /* if no modal window found, search for a focused toplevel */
1078 for (iter
=toplevels
; iter
!=NULL
; iter
=iter
->next
)
1079 if (gtk_window_has_toplevel_focus(GTK_WINDOW(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
);
1091 * show a warning window
1094 show_message(GtkMessageType type
, const char *primary
, const char *secondary
)
1098 dialog
=gtk_message_dialog_new((GtkWindow
*) guess_active_toplevel(),
1099 GTK_DIALOG_MODAL
| GTK_DIALOG_DESTROY_WITH_PARENT
,
1100 type
, GTK_BUTTONS_OK
,
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
);
1111 gd_warningmessage(const char *primary
, const char *secondary
)
1113 show_message(GTK_MESSAGE_WARNING
, primary
, secondary
);
1117 gd_errormessage(const char *primary
, const char *secondary
)
1119 show_message(GTK_MESSAGE_ERROR
, primary
, secondary
);
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 */
1130 gd_discard_changes (GtkWidget
*parent
)
1132 GtkWidget
*dialog
, *button
;
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
)
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 */
1160 gd_ask_overwrite(const char *filename
)
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
);
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!) */
1182 /* add to recent chooser */
1183 if (g_path_is_absolute(filename
))
1184 uri
=g_filename_to_uri(filename
, NULL
, NULL
);
1186 /* make an absolute filename if needed */
1190 currentdir
=g_get_current_dir();
1191 absolute
=g_build_path(G_DIR_SEPARATOR_S
, currentdir
, filename
, NULL
);
1193 uri
=g_filename_to_uri(absolute
, NULL
, NULL
);
1196 gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri
);
1199 /* if it is a bd file, remember new filename */
1200 if (g_str_has_suffix(filename
, ".bd")) {
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
;
1208 g_free(caveset_filename
);
1209 caveset_filename
=NULL
;
1214 /* save caveset to specified directory, and pop up error message if failed */
1216 caveset_save(const gchar
*filename
)
1220 saved
=gd_caveset_save(filename
);
1222 gd_show_last_error(guess_active_toplevel());
1224 caveset_file_operation_successful(filename
);
1229 gd_save_caveset_as(GtkWidget
*parent
)
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 */
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
);
1268 /* if we have a filename, do the save */
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
);
1275 /* if did not exist, simply save */
1276 caveset_save(filename
);
1279 gtk_widget_destroy (dialog
);
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
);
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 */
1300 gd_open_caveset_in_ui(const char *filename
, gboolean highscore_load_from_bdcff
)
1304 gd_clear_error_flag();
1306 loaded
=gd_caveset_load_from_file(filename
, gd_user_config_dir
);
1307 gd_main_window_set_title_animation();
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();
1323 gd_open_caveset(GtkWidget
*parent
, const char *directory
)
1325 GtkWidget
*dialog
, *check
;
1326 GtkFileFilter
*filter
;
1328 char *filename
=NULL
;
1329 gboolean highscore_load_from_bdcff
;
1332 /* if caveset is edited, and user does not want to discard changes */
1333 if (!gd_discard_changes(parent
))
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 */
1352 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(dialog
), directory
);
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
);
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. */
1375 gd_clear_error_flag();
1379 gd_open_caveset_in_ui(filename
, highscore_load_from_bdcff
);
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 */
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 */
1403 label_set_markup_vprintf(GtkLabel
*label
, const char *format
, va_list args
)
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
);
1414 /* create a label, and set its text by a printf; the text will be centered */
1416 gd_label_new_printf_centered(const char *format
, ...)
1421 label
=gtk_label_new(NULL
);
1423 va_start(args
, format
);
1424 label_set_markup_vprintf(GTK_LABEL(label
), format
, args
);
1430 /* create a label, and set its text by a printf */
1432 gd_label_new_printf(const char *format
, ...)
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
);
1447 /* set a label's markup with a printf format string */
1449 gd_label_set_markup_printf(GtkLabel
*label
, const char *format
, ...)
1453 va_start(args
, format
);
1454 label_set_markup_vprintf(label
, format
, args
);
1461 gd_show_errors (GtkWidget
*parent
)
1463 /* create text buffer */
1465 GtkTextBuffer
*buffer
=gtk_text_buffer_new(NULL
);
1466 GtkWidget
*dialog
, *sw
, *view
;
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
);
1494 if (error
->flags
<G_LOG_LEVEL_WARNING
)
1495 gtk_text_buffer_insert_pixbuf(buffer
, &iter
, pixbuf_error
);
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
);
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? */
1523 gd_show_last_error(GtkWidget
*parent
)
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
,
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
);
1547 /* user requested to show all errors */
1548 gd_show_errors(parent
);
1553 gd_question_yesno(const char *primary
, const char *secondary
)
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
;
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"
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
;
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
));
1606 /* if positive, it must be a keyval. gtk_response_cancel and gtk_response delete is negative. */
1608 gtk_button_set_label(GTK_BUTTON(button
), gdk_keyval_name(*keyval
));
1610 gtk_widget_destroy(dialog
);
1616 gd_keysim_button(const char *what_for
, guint
*keyval
)
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
);
1634 #undef GDASH_KEYSIM_WHAT_FOR
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);