clang-tidy: modernize-macro-to-enum
[geeqie.git] / src / preferences.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "preferences.h"
24
25 #include "bar-keywords.h"
26 #include "cache.h"
27 #include "color-man.h"
28 #include "editors.h"
29 #include "filedata.h"
30 #include "filefilter.h"
31 #include "fullscreen.h"
32 #include "image.h"
33 #include "image-overlay.h"
34 #include "img-view.h"
35 #include "layout-util.h"
36 #include "metadata.h"
37 #include "misc.h"
38 #include "osd.h"
39 #include "pixbuf-util.h"
40 #include "rcfile.h"
41 #include "slideshow.h"
42 #include "toolbar.h"
43 #include "trash.h"
44 #include "utilops.h"
45 #include "ui-fileops.h"
46 #include "ui-misc.h"
47 #include "ui-tabcomp.h"
48 #include "window.h"
49 #include "zonedetect.h"
50
51 #ifdef HAVE_LCMS
52 #ifdef HAVE_LCMS2
53 #include <lcms2.h>
54 #else
55 #include <lcms.h>
56 #endif
57 #endif
58
59 #ifdef HAVE_SPELL
60 #include <gspell/gspell.h>
61 #endif
62
63 enum {
64         EDITOR_NAME_MAX_LENGTH = 32,
65         EDITOR_COMMAND_MAX_LENGTH = 1024
66 };
67
68 static void image_overlay_set_text_colors();
69
70 GtkWidget *keyword_text;
71 static void config_tab_keywords_save();
72
73 struct ThumbSize
74 {
75         gint w;
76         gint h;
77 };
78
79 static ThumbSize thumb_size_list[] =
80 {
81         { 24, 24 },
82         { 32, 32 },
83         { 48, 48 },
84         { 64, 64 },
85         { 96, 72 },
86         { 96, 96 },
87         { 128, 96 },
88         { 128, 128 },
89         { 160, 120 },
90         { 160, 160 },
91         { 192, 144 },
92         { 192, 192 },
93         { 256, 192 },
94         { 256, 256 }
95 };
96
97 enum {
98         FE_ENABLE,
99         FE_EXTENSION,
100         FE_DESCRIPTION,
101         FE_CLASS,
102         FE_WRITABLE,
103         FE_ALLOW_SIDECAR
104 };
105
106 enum {
107         AE_ACTION,
108         AE_KEY,
109         AE_TOOLTIP,
110         AE_ACCEL
111 };
112
113 enum {
114         FILETYPES_COLUMN_ENABLED = 0,
115         FILETYPES_COLUMN_FILTER,
116         FILETYPES_COLUMN_DESCRIPTION,
117         FILETYPES_COLUMN_CLASS,
118         FILETYPES_COLUMN_WRITABLE,
119         FILETYPES_COLUMN_SIDECAR,
120         FILETYPES_COLUMN_COUNT
121 };
122
123 const gchar *format_class_list[] = {
124         N_("Unknown"),
125         N_("Image"),
126         N_("RAW Image"),
127         N_("Metadata"),
128         N_("Video"),
129         N_("Collection"),
130         N_("Document"),
131         N_("Archive")
132         };
133
134 /* config memory values */
135 static ConfOptions *c_options = nullptr;
136
137
138 #ifdef DEBUG
139 static gint debug_c;
140 #endif
141
142 static GtkWidget *configwindow = nullptr;
143 static GtkListStore *filter_store = nullptr;
144 static GtkTreeStore *accel_store = nullptr;
145
146 static GtkWidget *safe_delete_path_entry;
147
148 static GtkWidget *color_profile_input_file_entry[COLOR_PROFILE_INPUTS];
149 static GtkWidget *color_profile_input_name_entry[COLOR_PROFILE_INPUTS];
150 static GtkWidget *color_profile_screen_file_entry;
151 static GtkWidget *external_preview_select_entry;
152 static GtkWidget *external_preview_extract_entry;
153
154 static GtkWidget *sidecar_ext_entry;
155 static GtkWidget *help_search_engine_entry;
156
157 static GtkWidget *log_window_f1_entry;
158
159
160 enum {
161         CONFIG_WINDOW_DEF_WIDTH =               700,
162         CONFIG_WINDOW_DEF_HEIGHT =      600
163 };
164
165 /*
166  *-----------------------------------------------------------------------------
167  * option widget callbacks (private)
168  *-----------------------------------------------------------------------------
169  */
170
171 static void zoom_increment_cb(GtkWidget *spin, gpointer)
172 {
173         c_options->image.zoom_increment = static_cast<gint>(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)) * 100.0 + 0.01);
174 }
175
176 static void slideshow_delay_hours_cb(GtkWidget *spin, gpointer)
177 {
178         gint mins_secs_tenths, delay;
179
180         mins_secs_tenths = c_options->slideshow.delay %
181                                                 (3600 * SLIDESHOW_SUBSECOND_PRECISION);
182
183         delay = (gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)) *
184                                                                 (3600 * SLIDESHOW_SUBSECOND_PRECISION) +
185                                                                 mins_secs_tenths);
186
187         c_options->slideshow.delay = delay > 0 ? delay : SLIDESHOW_MIN_SECONDS *
188                                                                                                         SLIDESHOW_SUBSECOND_PRECISION;
189 }
190
191 static void slideshow_delay_minutes_cb(GtkWidget *spin, gpointer)
192 {
193         gint hours, secs_tenths, delay;
194
195         hours = c_options->slideshow.delay / (3600 * SLIDESHOW_SUBSECOND_PRECISION);
196         secs_tenths = c_options->slideshow.delay % (60 * SLIDESHOW_SUBSECOND_PRECISION);
197
198         delay = hours * (3600 * SLIDESHOW_SUBSECOND_PRECISION) +
199                                         (gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)) *
200                                         (60 * SLIDESHOW_SUBSECOND_PRECISION) + secs_tenths);
201
202         c_options->slideshow.delay = delay > 0 ? delay : SLIDESHOW_MIN_SECONDS *
203                                                                                                         SLIDESHOW_SUBSECOND_PRECISION;
204 }
205
206 static void slideshow_delay_seconds_cb(GtkWidget *spin, gpointer)
207 {
208         gint hours_mins, delay;
209
210         hours_mins = c_options->slideshow.delay / (60 * SLIDESHOW_SUBSECOND_PRECISION);
211
212         delay = (hours_mins * (60 * SLIDESHOW_SUBSECOND_PRECISION)) +
213                                                         static_cast<gint>(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)) *
214                                                         static_cast<gdouble>(SLIDESHOW_SUBSECOND_PRECISION) + 0.01);
215
216         c_options->slideshow.delay = delay > 0 ? delay : SLIDESHOW_MIN_SECONDS *
217                                                                                                         SLIDESHOW_SUBSECOND_PRECISION;
218 }
219
220 /*
221  *-----------------------------------------------------------------------------
222  * sync program to config window routine (private)
223  *-----------------------------------------------------------------------------
224  */
225
226 void config_entry_to_option(GtkWidget *entry, gchar **option, gchar *(*func)(const gchar *))
227 {
228         const gchar *buf;
229
230         g_free(*option);
231         *option = nullptr;
232         buf = gq_gtk_entry_get_text(GTK_ENTRY(entry));
233         if (buf && strlen(buf) > 0)
234                 {
235                 if (func)
236                         *option = func(buf);
237                 else
238                         *option = g_strdup(buf);
239                 }
240 }
241
242
243 static gboolean accel_apply_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
244 {
245         gchar *accel_path, *accel;
246
247         gtk_tree_model_get(model, iter, AE_ACCEL, &accel_path, AE_KEY, &accel, -1);
248
249         if (accel_path && accel_path[0])
250                 {
251                 GtkAccelKey key;
252                 gtk_accelerator_parse(accel, &key.accel_key, &key.accel_mods);
253                 gtk_accel_map_change_entry(accel_path, key.accel_key, key.accel_mods, TRUE);
254                 }
255
256         g_free(accel_path);
257         g_free(accel);
258
259         return FALSE;
260 }
261
262
263 static void config_window_apply()
264 {
265         gboolean refresh = FALSE;
266 #ifdef HAVE_LCMS2
267         int i = 0;
268 #endif
269
270         config_entry_to_option(safe_delete_path_entry, &options->file_ops.safe_delete_path, remove_trailing_slash);
271
272         if (options->file_filter.show_hidden_files != c_options->file_filter.show_hidden_files) refresh = TRUE;
273         if (options->file_filter.show_parent_directory != c_options->file_filter.show_parent_directory) refresh = TRUE;
274         if (options->file_filter.show_dot_directory != c_options->file_filter.show_dot_directory) refresh = TRUE;
275         if (options->file_sort.case_sensitive != c_options->file_sort.case_sensitive) refresh = TRUE;
276         if (options->file_filter.disable_file_extension_checks != c_options->file_filter.disable_file_extension_checks) refresh = TRUE;
277         if (options->file_filter.disable != c_options->file_filter.disable) refresh = TRUE;
278
279         options->file_ops.confirm_delete = c_options->file_ops.confirm_delete;
280         options->file_ops.enable_delete_key = c_options->file_ops.enable_delete_key;
281         options->file_ops.confirm_move_to_trash = c_options->file_ops.confirm_move_to_trash;
282         options->file_ops.use_system_trash = c_options->file_ops.use_system_trash;
283         options->file_ops.no_trash = c_options->file_ops.no_trash;
284         options->file_ops.safe_delete_folder_maxsize = c_options->file_ops.safe_delete_folder_maxsize;
285         options->tools_restore_state = c_options->tools_restore_state;
286         options->save_window_positions = c_options->save_window_positions;
287         options->use_saved_window_positions_for_new_windows = c_options->use_saved_window_positions_for_new_windows;
288         options->save_window_workspace = c_options->save_window_workspace;
289         options->save_dialog_window_positions = c_options->save_dialog_window_positions;
290         options->show_window_ids = c_options->show_window_ids;
291         options->image.scroll_reset_method = c_options->image.scroll_reset_method;
292         options->image.zoom_2pass = c_options->image.zoom_2pass;
293         options->image.fit_window_to_image = c_options->image.fit_window_to_image;
294         options->image.limit_window_size = c_options->image.limit_window_size;
295         options->image.zoom_to_fit_allow_expand = c_options->image.zoom_to_fit_allow_expand;
296         options->image.max_window_size = c_options->image.max_window_size;
297         options->image.limit_autofit_size = c_options->image.limit_autofit_size;
298         options->image.max_autofit_size = c_options->image.max_autofit_size;
299         options->image.max_enlargement_size = c_options->image.max_enlargement_size;
300         options->image.tile_size = c_options->image.tile_size;
301         options->progressive_key_scrolling = c_options->progressive_key_scrolling;
302         options->keyboard_scroll_step = c_options->keyboard_scroll_step;
303
304         if (options->thumbnails.max_width != c_options->thumbnails.max_width
305             || options->thumbnails.max_height != c_options->thumbnails.max_height
306             || options->thumbnails.quality != c_options->thumbnails.quality)
307                 {
308                 thumb_format_changed = TRUE;
309                 refresh = TRUE;
310                 options->thumbnails.max_width = c_options->thumbnails.max_width;
311                 options->thumbnails.max_height = c_options->thumbnails.max_height;
312                 options->thumbnails.quality = c_options->thumbnails.quality;
313                 }
314         options->thumbnails.enable_caching = c_options->thumbnails.enable_caching;
315         options->thumbnails.cache_into_dirs = c_options->thumbnails.cache_into_dirs;
316         options->thumbnails.use_exif = c_options->thumbnails.use_exif;
317         options->thumbnails.use_color_management = c_options->thumbnails.use_color_management;
318         options->thumbnails.collection_preview = c_options->thumbnails.collection_preview;
319         options->thumbnails.use_ft_metadata = c_options->thumbnails.use_ft_metadata;
320         options->thumbnails.spec_standard = c_options->thumbnails.spec_standard;
321         options->metadata.enable_metadata_dirs = c_options->metadata.enable_metadata_dirs;
322         options->file_filter.show_hidden_files = c_options->file_filter.show_hidden_files;
323         options->file_filter.show_parent_directory = c_options->file_filter.show_parent_directory;
324         options->file_filter.show_dot_directory = c_options->file_filter.show_dot_directory;
325         options->file_filter.disable_file_extension_checks = c_options->file_filter.disable_file_extension_checks;
326
327         options->file_sort.case_sensitive = c_options->file_sort.case_sensitive;
328         options->file_filter.disable = c_options->file_filter.disable;
329
330         config_entry_to_option(sidecar_ext_entry, &options->sidecar.ext, nullptr);
331         sidecar_ext_parse(options->sidecar.ext);
332
333         options->slideshow.random = c_options->slideshow.random;
334         options->slideshow.repeat = c_options->slideshow.repeat;
335         options->slideshow.delay = c_options->slideshow.delay;
336
337         options->mousewheel_scrolls = c_options->mousewheel_scrolls;
338         options->image_lm_click_nav = c_options->image_lm_click_nav;
339         options->image_l_click_archive = c_options->image_l_click_archive;
340         options->image_l_click_video = c_options->image_l_click_video;
341         options->image_l_click_video_editor = c_options->image_l_click_video_editor;
342
343         options->file_ops.enable_in_place_rename = c_options->file_ops.enable_in_place_rename;
344
345         options->image.tile_cache_max = c_options->image.tile_cache_max;
346         options->image.image_cache_max = c_options->image.image_cache_max;
347
348         options->image.zoom_quality = c_options->image.zoom_quality;
349
350         options->image.zoom_increment = c_options->image.zoom_increment;
351
352         options->image.zoom_style = c_options->image.zoom_style;
353
354         options->image.enable_read_ahead = c_options->image.enable_read_ahead;
355
356         options->appimage_notifications = c_options->appimage_notifications;
357
358
359         if (options->image.use_custom_border_color != c_options->image.use_custom_border_color
360             || options->image.use_custom_border_color_in_fullscreen != c_options->image.use_custom_border_color_in_fullscreen
361             || !gdk_rgba_equal(&options->image.border_color, &c_options->image.border_color))
362                 {
363                 options->image.use_custom_border_color_in_fullscreen = c_options->image.use_custom_border_color_in_fullscreen;
364                 options->image.use_custom_border_color = c_options->image.use_custom_border_color;
365                 options->image.border_color = c_options->image.border_color;
366                 layout_colors_update();
367                 view_window_colors_update();
368                 }
369
370         options->image.alpha_color_1 = c_options->image.alpha_color_1;
371         options->image.alpha_color_2 = c_options->image.alpha_color_2;
372
373         options->fullscreen.screen = c_options->fullscreen.screen;
374         options->fullscreen.clean_flip = c_options->fullscreen.clean_flip;
375         options->fullscreen.disable_saver = c_options->fullscreen.disable_saver;
376         options->fullscreen.above = c_options->fullscreen.above;
377         if (c_options->image_overlay.template_string)
378                 set_image_overlay_template_string(&options->image_overlay.template_string,
379                                                   c_options->image_overlay.template_string);
380         if (c_options->image_overlay.font)
381                 set_image_overlay_font_string(&options->image_overlay.font,
382                                                   c_options->image_overlay.font);
383         options->image_overlay.text_red = c_options->image_overlay.text_red;
384         options->image_overlay.text_green = c_options->image_overlay.text_green;
385         options->image_overlay.text_blue = c_options->image_overlay.text_blue;
386         options->image_overlay.text_alpha = c_options->image_overlay.text_alpha;
387         options->image_overlay.background_red = c_options->image_overlay.background_red;
388         options->image_overlay.background_green = c_options->image_overlay.background_green;
389         options->image_overlay.background_blue = c_options->image_overlay.background_blue;
390         options->image_overlay.background_alpha = c_options->image_overlay.background_alpha;
391         options->image_overlay.font = c_options->image_overlay.font;
392         options->update_on_time_change = c_options->update_on_time_change;
393
394         options->duplicates_similarity_threshold = c_options->duplicates_similarity_threshold;
395         options->rot_invariant_sim = c_options->rot_invariant_sim;
396
397         options->tree_descend_subdirs = c_options->tree_descend_subdirs;
398
399         options->view_dir_list_single_click_enter = c_options->view_dir_list_single_click_enter;
400         options->circular_selection_lists = c_options->circular_selection_lists;
401
402         options->open_recent_list_maxsize = c_options->open_recent_list_maxsize;
403         options->recent_folder_image_list_maxsize = c_options->recent_folder_image_list_maxsize;
404         options->dnd_icon_size = c_options->dnd_icon_size;
405         options->clipboard_selection = c_options->clipboard_selection;
406         options->dnd_default_action = c_options->dnd_default_action;
407
408         options->metadata.save_in_image_file = c_options->metadata.save_in_image_file;
409         options->metadata.save_legacy_IPTC = c_options->metadata.save_legacy_IPTC;
410         options->metadata.warn_on_write_problems = c_options->metadata.warn_on_write_problems;
411         options->metadata.save_legacy_format = c_options->metadata.save_legacy_format;
412         options->metadata.sync_grouped_files = c_options->metadata.sync_grouped_files;
413         options->metadata.confirm_write = c_options->metadata.confirm_write;
414         options->metadata.sidecar_extended_name = c_options->metadata.sidecar_extended_name;
415         options->metadata.confirm_timeout = c_options->metadata.confirm_timeout;
416         options->metadata.confirm_after_timeout = c_options->metadata.confirm_after_timeout;
417         options->metadata.confirm_on_image_change = c_options->metadata.confirm_on_image_change;
418         options->metadata.confirm_on_dir_change = c_options->metadata.confirm_on_dir_change;
419         options->metadata.keywords_case_sensitive = c_options->metadata.keywords_case_sensitive;
420         options->metadata.write_orientation = c_options->metadata.write_orientation;
421         options->metadata.check_spelling = c_options->metadata.check_spelling;
422         options->stereo.mode = (c_options->stereo.mode & (PR_STEREO_HORIZ | PR_STEREO_VERT | PR_STEREO_FIXED | PR_STEREO_ANAGLYPH | PR_STEREO_HALF)) |
423                                (c_options->stereo.tmp.mirror_right ? PR_STEREO_MIRROR_RIGHT : 0) |
424                                (c_options->stereo.tmp.flip_right   ? PR_STEREO_FLIP_RIGHT : 0) |
425                                (c_options->stereo.tmp.mirror_left  ? PR_STEREO_MIRROR_LEFT : 0) |
426                                (c_options->stereo.tmp.flip_left    ? PR_STEREO_FLIP_LEFT : 0) |
427                                (c_options->stereo.tmp.swap         ? PR_STEREO_SWAP : 0) |
428                                (c_options->stereo.tmp.temp_disable ? PR_STEREO_TEMP_DISABLE : 0);
429         options->stereo.fsmode = (c_options->stereo.fsmode & (PR_STEREO_HORIZ | PR_STEREO_VERT | PR_STEREO_FIXED | PR_STEREO_ANAGLYPH | PR_STEREO_HALF)) |
430                                (c_options->stereo.tmp.fs_mirror_right ? PR_STEREO_MIRROR_RIGHT : 0) |
431                                (c_options->stereo.tmp.fs_flip_right   ? PR_STEREO_FLIP_RIGHT : 0) |
432                                (c_options->stereo.tmp.fs_mirror_left  ? PR_STEREO_MIRROR_LEFT : 0) |
433                                (c_options->stereo.tmp.fs_flip_left    ? PR_STEREO_FLIP_LEFT : 0) |
434                                (c_options->stereo.tmp.fs_swap         ? PR_STEREO_SWAP : 0) |
435                                (c_options->stereo.tmp.fs_temp_disable ? PR_STEREO_TEMP_DISABLE : 0);
436         options->stereo.enable_fsmode = c_options->stereo.enable_fsmode;
437         options->stereo.fixed_w = c_options->stereo.fixed_w;
438         options->stereo.fixed_h = c_options->stereo.fixed_h;
439         options->stereo.fixed_x1 = c_options->stereo.fixed_x1;
440         options->stereo.fixed_y1 = c_options->stereo.fixed_y1;
441         options->stereo.fixed_x2 = c_options->stereo.fixed_x2;
442         options->stereo.fixed_y2 = c_options->stereo.fixed_y2;
443
444         options->info_keywords.height = c_options->info_keywords.height;
445         options->info_title.height = c_options->info_title.height;
446         options->info_comment.height = c_options->info_comment.height;
447         options->info_rating.height = c_options->info_rating.height;
448
449         options->show_predefined_keyword_tree = c_options->show_predefined_keyword_tree;
450         options->expand_menu_toolbar = c_options->expand_menu_toolbar;
451         options->hamburger_menu = c_options->hamburger_menu;
452
453         options->selectable_bars.menu_bar = c_options->selectable_bars.menu_bar;
454         options->selectable_bars.tool_bar = c_options->selectable_bars.tool_bar;
455         options->selectable_bars.status_bar = c_options->selectable_bars.status_bar;
456
457         options->marks_save = c_options->marks_save;
458         options->with_rename = c_options->with_rename;
459         options->collections_duplicates = c_options->collections_duplicates;
460         options->collections_on_top = c_options->collections_on_top;
461         options->hide_window_in_fullscreen = c_options->hide_window_in_fullscreen;
462         config_entry_to_option(help_search_engine_entry, &options->help_search_engine, nullptr);
463
464         options->external_preview.enable = c_options->external_preview.enable;
465         config_entry_to_option(external_preview_select_entry, &options->external_preview.select, nullptr);
466         config_entry_to_option(external_preview_extract_entry, &options->external_preview.extract, nullptr);
467
468         options->read_metadata_in_idle = c_options->read_metadata_in_idle;
469
470         options->star_rating.star = c_options->star_rating.star;
471         options->star_rating.rejected = c_options->star_rating.rejected;
472
473         options->threads.duplicates = c_options->threads.duplicates > 0 ? c_options->threads.duplicates : -1;
474
475         options->alternate_similarity_algorithm.enabled = c_options->alternate_similarity_algorithm.enabled;
476         options->alternate_similarity_algorithm.grayscale = c_options->alternate_similarity_algorithm.grayscale;
477
478 #ifdef DEBUG
479         set_debug_level(debug_c);
480         config_entry_to_option(log_window_f1_entry, &options->log_window.action, nullptr);
481 #endif
482
483 #ifdef HAVE_LCMS
484         for (i = 0; i < COLOR_PROFILE_INPUTS; i++)
485                 {
486                 config_entry_to_option(color_profile_input_name_entry[i], &options->color_profile.input_name[i], nullptr);
487                 config_entry_to_option(color_profile_input_file_entry[i], &options->color_profile.input_file[i], nullptr);
488                 }
489         config_entry_to_option(color_profile_screen_file_entry, &options->color_profile.screen_file, nullptr);
490         options->color_profile.use_x11_screen_profile = c_options->color_profile.use_x11_screen_profile;
491         if (options->color_profile.render_intent != c_options->color_profile.render_intent)
492                 {
493                 options->color_profile.render_intent = c_options->color_profile.render_intent;
494                 color_man_update();
495                 }
496 #endif
497
498         options->mouse_button_8 = c_options->mouse_button_8;
499         options->mouse_button_9 = c_options->mouse_button_9;
500
501         options->override_disable_gpu = c_options->override_disable_gpu;
502
503         config_tab_keywords_save();
504
505         image_options_sync();
506
507         if (refresh)
508                 {
509                 filter_rebuild();
510                 layout_refresh(nullptr);
511                 }
512
513         if (accel_store) gtk_tree_model_foreach(GTK_TREE_MODEL(accel_store), accel_apply_cb, nullptr);
514
515         toolbar_apply(TOOLBAR_MAIN);
516         toolbar_apply(TOOLBAR_STATUS);
517 }
518
519 /*
520  *-----------------------------------------------------------------------------
521  * config window main button callbacks (private)
522  *-----------------------------------------------------------------------------
523  */
524
525 static void config_window_close_cb(GtkWidget *, gpointer)
526 {
527         gq_gtk_widget_destroy(configwindow);
528         configwindow = nullptr;
529         filter_store = nullptr;
530 }
531
532 static void config_window_help_cb(GtkWidget *, gpointer data)
533 {
534         auto notebook = static_cast<GtkWidget *>(data);
535         gint i;
536
537         static const gchar *html_section[] =
538         {
539         "GuideOptionsGeneral.html",
540         "GuideOptionsImage.html",
541         "GuideOptionsOSD.html",
542         "GuideOptionsWindow.html",
543         "GuideOptionsKeyboard.html",
544         "GuideOptionsFiltering.html",
545         "GuideOptionsMetadata.html",
546         "GuideOptionsKeywords.html",
547         "GuideOptionsColor.html",
548         "GuideOptionsStereo.html",
549         "GuideOptionsBehavior.html",
550         "GuideOptionsToolbar.html",
551         "GuideOptionsToolbar.html",
552         "GuideOptionsAdvanced.html"
553         };
554
555         i = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
556         help_window_show(html_section[i]);
557 }
558
559 static gboolean config_window_delete(GtkWidget *, GdkEventAny *, gpointer)
560 {
561         config_window_close_cb(nullptr, nullptr);
562         return TRUE;
563 }
564
565 static void config_window_ok_cb(GtkWidget *widget, gpointer data)
566 {
567         LayoutWindow *lw;
568         auto notebook = static_cast<GtkNotebook *>(data);
569         GdkWindow *window;
570         gint x;
571         gint y;
572         gint w;
573         gint h;
574         gint page_number;
575
576         lw = static_cast<LayoutWindow *>(layout_window_list->data);
577
578         window = gtk_widget_get_window(widget);
579         gdk_window_get_root_origin(window, &x, &y);
580         w = gdk_window_get_width(window);
581         h = gdk_window_get_height(window);
582         page_number = gtk_notebook_get_current_page(notebook);
583
584         lw->options.preferences_window.x = x;
585         lw->options.preferences_window.y = y;
586         lw->options.preferences_window.w = w;
587         lw->options.preferences_window.h = h;
588         lw->options.preferences_window.page_number = page_number;
589
590         config_window_apply();
591         layout_util_sync(lw);
592         save_options(options);
593         config_window_close_cb(nullptr, nullptr);
594 }
595
596 /*
597  *-----------------------------------------------------------------------------
598  * config window setup (private)
599  *-----------------------------------------------------------------------------
600  */
601
602 static void quality_menu_cb(GtkWidget *combo, gpointer data)
603 {
604         auto option = static_cast<gint *>(data);
605
606         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
607                 {
608                 case 0:
609                 default:
610                         *option = GDK_INTERP_NEAREST;
611                         break;
612                 case 1:
613                         *option = GDK_INTERP_TILES;
614                         break;
615                 case 2:
616                         *option = GDK_INTERP_BILINEAR;
617                         break;
618                 }
619 }
620
621 static void dnd_default_action_selection_menu_cb(GtkWidget *combo, gpointer data)
622 {
623         auto option = static_cast<gint *>(data);
624
625         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
626                 {
627                 case 0:
628                 default:
629                         *option = DND_ACTION_ASK;
630                         break;
631                 case 1:
632                         *option = DND_ACTION_COPY;
633                         break;
634                 case 2:
635                         *option = DND_ACTION_MOVE;
636                         break;
637                 }
638 }
639 static void clipboard_selection_menu_cb(GtkWidget *combo, gpointer data)
640 {
641         auto option = static_cast<gint *>(data);
642
643         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
644                 {
645                 case 0:
646                         *option = CLIPBOARD_PRIMARY;
647                         break;
648                 case 1:
649                         *option = CLIPBOARD_CLIPBOARD;
650                         break;
651                 case 2:
652                         *option = CLIPBOARD_BOTH;
653                         break;
654                 default:
655                         *option = CLIPBOARD_BOTH;
656                 }
657 }
658
659 static void add_quality_menu(GtkWidget *table, gint column, gint row, const gchar *text,
660                              guint option, guint *option_c)
661 {
662         GtkWidget *combo;
663         gint current = 0;
664
665         *option_c = option;
666
667         pref_table_label(table, column, row, text, GTK_ALIGN_START);
668
669         combo = gtk_combo_box_text_new();
670
671         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Nearest (worst, but fastest)"));
672         if (option == GDK_INTERP_NEAREST) current = 0;
673         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Tiles"));
674         if (option == GDK_INTERP_TILES) current = 1;
675         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Bilinear (best, but slowest)"));
676         if (option == GDK_INTERP_BILINEAR) current = 2;
677
678         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
679
680         g_signal_connect(G_OBJECT(combo), "changed",
681                          G_CALLBACK(quality_menu_cb), option_c);
682
683         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
684         gtk_widget_show(combo);
685 }
686
687 static void add_dnd_default_action_selection_menu(GtkWidget *table, gint column, gint row, const gchar *text, DnDAction option, DnDAction *option_c)
688 {
689         GtkWidget *combo;
690         gint current = 0;
691
692         *option_c = option;
693
694         pref_table_label(table, column, row, text, GTK_ALIGN_START);
695
696         combo = gtk_combo_box_text_new();
697
698         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Ask"));
699         if (option == DND_ACTION_ASK) current = 0;
700         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Copy"));
701         if (option == DND_ACTION_COPY) current = 1;
702         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Move"));
703         if (option == DND_ACTION_MOVE) current = 2;
704
705         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
706
707         g_signal_connect(G_OBJECT(combo), "changed",
708                          G_CALLBACK(dnd_default_action_selection_menu_cb), option_c);
709
710         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
711         gtk_widget_show(combo);
712 }
713
714 static void add_clipboard_selection_menu(GtkWidget *table, gint column, gint row, const gchar *text,
715                              gint option, gint *option_c)
716 {
717         GtkWidget *combo;
718         gint current = 0;
719
720         *option_c = option;
721
722         pref_table_label(table, column, row, text, GTK_ALIGN_START);
723
724         combo = gtk_combo_box_text_new();
725
726         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Primary"));
727         if (option == CLIPBOARD_PRIMARY) current = 0;
728         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Clipboard"));
729         if (option == CLIPBOARD_CLIPBOARD) current = 1;
730         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Both"));
731         if (option == CLIPBOARD_BOTH) current = 2;
732
733         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
734
735         g_signal_connect(G_OBJECT(combo), "changed",
736                          G_CALLBACK(clipboard_selection_menu_cb), option_c);
737
738         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
739         gtk_widget_show(combo);
740 }
741
742 static void zoom_style_selection_menu_cb(GtkWidget *combo, gpointer data)
743 {
744         auto option = static_cast<gint *>(data);
745
746         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
747                 {
748                 case 0:
749                         *option = ZOOM_GEOMETRIC;
750                         break;
751                 case 1:
752                         *option = ZOOM_ARITHMETIC;
753                         break;
754                 default:
755                         *option = ZOOM_GEOMETRIC;
756                 }
757 }
758
759 static void add_zoom_style_selection_menu(GtkWidget *table, gint column, gint row, const gchar *text, ZoomStyle option, ZoomStyle *option_c)
760 {
761         GtkWidget *combo;
762         gint current = 0;
763
764         *option_c = option;
765
766         pref_table_label(table, column, row, text, GTK_ALIGN_START);
767
768         combo = gtk_combo_box_text_new();
769
770         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Geometric"));
771         if (option == ZOOM_GEOMETRIC) current = 0;
772         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Arithmetic"));
773         if (option == ZOOM_ARITHMETIC) current = 1;
774
775         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
776
777         g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(zoom_style_selection_menu_cb), option_c);
778
779         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
780         gtk_widget_show(combo);
781 }
782
783 static void mouse_buttons_selection_menu_cb(GtkWidget *combo, gpointer data)
784 {
785         ActionItem *action_item = nullptr;
786         auto option = static_cast<gchar **>(data);
787         gchar *label;
788         GList *list;
789         GList *work;
790
791         label = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
792
793         list = get_action_items();
794         work = list;
795
796         while (work)
797                 {
798                 action_item = static_cast<ActionItem *>(work->data);
799                 if (g_strcmp0(action_item->label, label) == 0)
800                         {
801                         break;
802                         }
803                 work=work->next;
804                 }
805
806         g_free(*option);
807         *option = g_strdup(action_item->name);
808         g_free(label);
809         action_items_free(list);
810 }
811
812 static void add_mouse_selection_menu(GtkWidget *table, gint column, gint row, const gchar *text, gchar *option, gchar **option_c)
813 {
814         ActionItem *action_item;
815         gint current = 0;
816         gint i = 0;
817         GList *list;
818         GList *work;
819         GtkWidget *combo;
820
821         *option_c = option;
822
823         pref_table_label(table, column, row, text, GTK_ALIGN_START);
824
825         combo = gtk_combo_box_text_new();
826
827         list = get_action_items();
828         work = list;
829         while (work)
830                 {
831                 action_item = static_cast<ActionItem *>(work->data);
832                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), action_item->label);
833
834                 if (g_strcmp0(action_item->name, option) == 0)
835                         {
836                         current = i;
837                         }
838                 i++;
839                 work = work->next;
840                 }
841
842         action_items_free(list);
843
844         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
845
846         g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(mouse_buttons_selection_menu_cb), option_c);
847
848         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
849         gtk_widget_show(combo);
850 }
851
852 static void thumb_size_menu_cb(GtkWidget *combo, gpointer)
853 {
854         gint n;
855
856         n = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
857         if (n < 0) return;
858
859         if (static_cast<guint>(n) < sizeof(thumb_size_list) / sizeof(ThumbSize))
860                 {
861                 c_options->thumbnails.max_width = thumb_size_list[n].w;
862                 c_options->thumbnails.max_height = thumb_size_list[n].h;
863                 }
864         else
865                 {
866                 c_options->thumbnails.max_width = options->thumbnails.max_width;
867                 c_options->thumbnails.max_height = options->thumbnails.max_height;
868                 }
869 }
870
871 static void add_thumb_size_menu(GtkWidget *table, gint column, gint row, gchar *text)
872 {
873         GtkWidget *combo;
874         gint current;
875         gint i;
876
877         c_options->thumbnails.max_width = options->thumbnails.max_width;
878         c_options->thumbnails.max_height = options->thumbnails.max_height;
879
880         pref_table_label(table, column, row, text, GTK_ALIGN_START);
881
882         combo = gtk_combo_box_text_new();
883
884         current = -1;
885         for (i = 0; static_cast<guint>(i) < sizeof(thumb_size_list) / sizeof(ThumbSize); i++)
886                 {
887                 gint w, h;
888                 gchar *buf;
889
890                 w = thumb_size_list[i].w;
891                 h = thumb_size_list[i].h;
892
893                 buf = g_strdup_printf("%d x %d", w, h);
894                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), buf);
895                 g_free(buf);
896
897                 if (w == options->thumbnails.max_width && h == options->thumbnails.max_height) current = i;
898                 }
899
900         if (current == -1)
901                 {
902                 gchar *buf;
903
904                 buf = g_strdup_printf("%s %d x %d", _("Custom"), options->thumbnails.max_width, options->thumbnails.max_height);
905                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), buf);
906                 g_free(buf);
907
908                 current = i;
909                 }
910
911         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
912         g_signal_connect(G_OBJECT(combo), "changed",
913                          G_CALLBACK(thumb_size_menu_cb), NULL);
914
915         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
916         gtk_widget_show(combo);
917 }
918
919 static void stereo_mode_menu_cb(GtkWidget *combo, gpointer data)
920 {
921         auto option = static_cast<gint *>(data);
922
923         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
924                 {
925                 case 0:
926                 default:
927                         *option = PR_STEREO_NONE;
928                         break;
929                 case 1:
930                         *option = PR_STEREO_ANAGLYPH_RC;
931                         break;
932                 case 2:
933                         *option = PR_STEREO_ANAGLYPH_GM;
934                         break;
935                 case 3:
936                         *option = PR_STEREO_ANAGLYPH_YB;
937                         break;
938                 case 4:
939                         *option = PR_STEREO_ANAGLYPH_GRAY_RC;
940                         break;
941                 case 5:
942                         *option = PR_STEREO_ANAGLYPH_GRAY_GM;
943                         break;
944                 case 6:
945                         *option = PR_STEREO_ANAGLYPH_GRAY_YB;
946                         break;
947                 case 7:
948                         *option = PR_STEREO_ANAGLYPH_DB_RC;
949                         break;
950                 case 8:
951                         *option = PR_STEREO_ANAGLYPH_DB_GM;
952                         break;
953                 case 9:
954                         *option = PR_STEREO_ANAGLYPH_DB_YB;
955                         break;
956                 case 10:
957                         *option = PR_STEREO_HORIZ;
958                         break;
959                 case 11:
960                         *option = PR_STEREO_HORIZ | PR_STEREO_HALF;
961                         break;
962                 case 12:
963                         *option = PR_STEREO_VERT;
964                         break;
965                 case 13:
966                         *option = PR_STEREO_VERT | PR_STEREO_HALF;
967                         break;
968                 case 14:
969                         *option = PR_STEREO_FIXED;
970                         break;
971                 }
972 }
973
974 static void add_stereo_mode_menu(GtkWidget *table, gint column, gint row, const gchar *text,
975                              gint option, gint *option_c, gboolean add_fixed)
976 {
977         GtkWidget *combo;
978         gint current = 0;
979
980         *option_c = option;
981
982         pref_table_label(table, column, row, text, GTK_ALIGN_START);
983
984         combo = gtk_combo_box_text_new();
985
986         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Single image"));
987
988         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Red-Cyan"));
989         if (option & PR_STEREO_ANAGLYPH_RC) current = 1;
990         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Green-Magenta"));
991         if (option & PR_STEREO_ANAGLYPH_GM) current = 2;
992         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Yellow-Blue"));
993         if (option & PR_STEREO_ANAGLYPH_YB) current = 3;
994         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Gray Red-Cyan"));
995         if (option & PR_STEREO_ANAGLYPH_GRAY_RC) current = 4;
996         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Gray Green-Magenta"));
997         if (option & PR_STEREO_ANAGLYPH_GRAY_GM) current = 5;
998         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Gray Yellow-Blue"));
999         if (option & PR_STEREO_ANAGLYPH_GRAY_YB) current = 6;
1000         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Dubois Red-Cyan"));
1001         if (option & PR_STEREO_ANAGLYPH_DB_RC) current = 7;
1002         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Dubois Green-Magenta"));
1003         if (option & PR_STEREO_ANAGLYPH_DB_GM) current = 8;
1004         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Anaglyph Dubois Yellow-Blue"));
1005         if (option & PR_STEREO_ANAGLYPH_DB_YB) current = 9;
1006
1007         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Side by Side"));
1008         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Side by Side Half size"));
1009         if (option & PR_STEREO_HORIZ)
1010                 {
1011                 current = 10;
1012                 if (option & PR_STEREO_HALF) current = 11;
1013                 }
1014
1015         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Top - Bottom"));
1016         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Top - Bottom Half size"));
1017         if (option & PR_STEREO_VERT)
1018                 {
1019                 current = 12;
1020                 if (option & PR_STEREO_HALF) current = 13;
1021                 }
1022
1023         if (add_fixed)
1024                 {
1025                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Fixed position"));
1026                 if (option & PR_STEREO_FIXED) current = 14;
1027                 }
1028
1029         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
1030
1031         g_signal_connect(G_OBJECT(combo), "changed",
1032                          G_CALLBACK(stereo_mode_menu_cb), option_c);
1033
1034         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
1035         gtk_widget_show(combo);
1036 }
1037
1038 static void video_menu_cb(GtkWidget *combo, gpointer data)
1039 {
1040         auto option = static_cast<gchar **>(data);
1041
1042         auto ed = static_cast<EditorDescription *>(g_list_nth_data(editor_list_get(), gtk_combo_box_get_active(GTK_COMBO_BOX(combo))));
1043         *option = ed->key;
1044 }
1045
1046 static void video_menu_populate(gpointer data, gpointer user_data)
1047 {
1048         auto combo = static_cast<GtkWidget *>(user_data);
1049         auto ed = static_cast<EditorDescription *>(data);
1050
1051         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), ed->name);
1052 }
1053
1054 static void add_video_menu(GtkWidget *table, gint column, gint row, const gchar *text,
1055                              gchar *option, gchar **option_c)
1056 {
1057         GtkWidget *combo;
1058         gint current;
1059 /* use lists since they are sorted */
1060         GList *eds = editor_list_get();
1061
1062         *option_c = option;
1063
1064         pref_table_label(table, column, row, text, GTK_ALIGN_START);
1065
1066         combo = gtk_combo_box_text_new();
1067         g_list_foreach(eds,video_menu_populate,combo);
1068         current = option ? g_list_index(eds,g_hash_table_lookup(editors,option)): -1;
1069
1070         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
1071
1072         g_signal_connect(G_OBJECT(combo), "changed",
1073                          G_CALLBACK(video_menu_cb), option_c);
1074
1075         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
1076         gtk_widget_show(combo);
1077 }
1078
1079 static void filter_store_populate()
1080 {
1081         GList *work;
1082
1083         if (!filter_store) return;
1084
1085         gtk_list_store_clear(filter_store);
1086
1087         work = filter_get_list();
1088         while (work)
1089                 {
1090                 FilterEntry *fe;
1091                 GtkTreeIter iter;
1092
1093                 fe = static_cast<FilterEntry *>(work->data);
1094                 work = work->next;
1095
1096                 gtk_list_store_append(filter_store, &iter);
1097                 gtk_list_store_set(filter_store, &iter, 0, fe, -1);
1098                 }
1099 }
1100
1101 static void filter_store_ext_edit_cb(GtkCellRendererText *, gchar *path_str, gchar *new_text, gpointer data)
1102 {
1103         auto model = static_cast<GtkWidget *>(data);
1104         auto fe = static_cast<FilterEntry *>(data);
1105         GtkTreePath *tpath;
1106         GtkTreeIter iter;
1107
1108         if (!new_text || strlen(new_text) < 1) return;
1109
1110         tpath = gtk_tree_path_new_from_string(path_str);
1111         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1112         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1113
1114         g_free(fe->extensions);
1115         fe->extensions = g_strdup(new_text);
1116
1117         gtk_tree_path_free(tpath);
1118         filter_rebuild();
1119 }
1120
1121 static void filter_store_class_edit_cb(GtkCellRendererText *, gchar *path_str, gchar *new_text, gpointer data)
1122 {
1123         auto model = static_cast<GtkWidget *>(data);
1124         auto fe = static_cast<FilterEntry *>(data);
1125         GtkTreePath *tpath;
1126         GtkTreeIter iter;
1127         gint i;
1128
1129         if (!new_text || !new_text[0]) return;
1130
1131         tpath = gtk_tree_path_new_from_string(path_str);
1132         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1133         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1134
1135         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1136                 {
1137                 if (strcmp(new_text, _(format_class_list[i])) == 0)
1138                         {
1139                         fe->file_class = static_cast<FileFormatClass>(i);
1140                         break;
1141                         }
1142                 }
1143
1144         gtk_tree_path_free(tpath);
1145         filter_rebuild();
1146 }
1147
1148 static void filter_store_desc_edit_cb(GtkCellRendererText *, gchar *path_str, gchar *new_text, gpointer data)
1149 {
1150         auto model = static_cast<GtkWidget *>(data);
1151         FilterEntry *fe;
1152         GtkTreePath *tpath;
1153         GtkTreeIter iter;
1154
1155         if (!new_text || !new_text[0]) return;
1156
1157         tpath = gtk_tree_path_new_from_string(path_str);
1158         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1159         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1160
1161         g_free(fe->description);
1162         fe->description = g_strdup(new_text);
1163
1164         gtk_tree_path_free(tpath);
1165 }
1166
1167 static void filter_store_enable_cb(GtkCellRendererToggle *, gchar *path_str, gpointer data)
1168 {
1169         auto model = static_cast<GtkWidget *>(data);
1170         FilterEntry *fe;
1171         GtkTreePath *tpath;
1172         GtkTreeIter iter;
1173
1174         tpath = gtk_tree_path_new_from_string(path_str);
1175         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1176         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1177
1178         fe->enabled = !fe->enabled;
1179
1180         gtk_tree_path_free(tpath);
1181         filter_rebuild();
1182 }
1183
1184 static void filter_store_writable_cb(GtkCellRendererToggle *, gchar *path_str, gpointer data)
1185 {
1186         auto model = static_cast<GtkWidget *>(data);
1187         FilterEntry *fe;
1188         GtkTreePath *tpath;
1189         GtkTreeIter iter;
1190
1191         tpath = gtk_tree_path_new_from_string(path_str);
1192         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1193         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1194
1195         fe->writable = !fe->writable;
1196         if (fe->writable) fe->allow_sidecar = FALSE;
1197
1198         gtk_tree_path_free(tpath);
1199         filter_rebuild();
1200 }
1201
1202 static void filter_store_sidecar_cb(GtkCellRendererToggle *, gchar *path_str, gpointer data)
1203 {
1204         auto model = static_cast<GtkWidget *>(data);
1205         FilterEntry *fe;
1206         GtkTreePath *tpath;
1207         GtkTreeIter iter;
1208
1209         tpath = gtk_tree_path_new_from_string(path_str);
1210         gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, tpath);
1211         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &fe, -1);
1212
1213         fe->allow_sidecar = !fe->allow_sidecar;
1214         if (fe->allow_sidecar) fe->writable = FALSE;
1215
1216         gtk_tree_path_free(tpath);
1217         filter_rebuild();
1218 }
1219
1220 static void filter_set_func(GtkTreeViewColumn *, GtkCellRenderer *cell,
1221                             GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1222 {
1223         FilterEntry *fe;
1224
1225         gtk_tree_model_get(tree_model, iter, 0, &fe, -1);
1226
1227         switch (GPOINTER_TO_INT(data))
1228                 {
1229                 case FE_ENABLE:
1230                         g_object_set(GTK_CELL_RENDERER(cell),
1231                                      "active", fe->enabled, NULL);
1232                         break;
1233                 case FE_EXTENSION:
1234                         g_object_set(GTK_CELL_RENDERER(cell),
1235                                      "text", fe->extensions, NULL);
1236                         break;
1237                 case FE_DESCRIPTION:
1238                         g_object_set(GTK_CELL_RENDERER(cell),
1239                                      "text", fe->description, NULL);
1240                         break;
1241                 case FE_CLASS:
1242                         g_object_set(GTK_CELL_RENDERER(cell),
1243                                      "text", _(format_class_list[fe->file_class]), NULL);
1244                         break;
1245                 case FE_WRITABLE:
1246                         g_object_set(GTK_CELL_RENDERER(cell),
1247                                      "active", fe->writable, NULL);
1248                         break;
1249                 case FE_ALLOW_SIDECAR:
1250                         g_object_set(GTK_CELL_RENDERER(cell),
1251                                      "active", fe->allow_sidecar, NULL);
1252                         break;
1253                 }
1254 }
1255
1256 static gboolean filter_add_scroll(gpointer data)
1257 {
1258         GtkTreePath *path;
1259         GList *list_cells;
1260         GtkCellRenderer *cell;
1261         GtkTreeViewColumn *column;
1262         gint rows;
1263         GtkTreeIter iter;
1264         GtkTreeModel *store;
1265         gboolean valid;
1266         FilterEntry *filter;
1267
1268         rows = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(filter_store), nullptr);
1269         path = gtk_tree_path_new_from_indices(rows-1, -1);
1270
1271         column = gtk_tree_view_get_column(GTK_TREE_VIEW(data), 0);
1272
1273         list_cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1274         cell = static_cast<GtkCellRenderer *>(g_list_last(list_cells)->data);
1275
1276         store = gtk_tree_view_get_model(GTK_TREE_VIEW(data));
1277         valid = gtk_tree_model_get_iter_first(store, &iter);
1278
1279         while (valid)
1280                 {
1281                 gtk_tree_model_get(store, &iter, 0, &filter, -1);
1282
1283                 if (g_strcmp0(filter->extensions, ".new") == 0)
1284                         {
1285                         path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
1286                         break;
1287                         }
1288
1289                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1290                 }
1291
1292         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(data),
1293                                                                 path, column, FALSE, 0.0, 0.0 );
1294         gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(data),
1295                                                                 path, column, cell, TRUE);
1296
1297         gtk_tree_path_free(path);
1298         g_list_free(list_cells);
1299
1300         return(G_SOURCE_REMOVE);
1301 }
1302
1303 static void filter_add_cb(GtkWidget *, gpointer data)
1304 {
1305         filter_add_unique("description", ".new", FORMAT_CLASS_IMAGE, TRUE, FALSE, TRUE);
1306         filter_store_populate();
1307
1308         g_idle_add(static_cast<GSourceFunc>(filter_add_scroll), data);
1309 }
1310
1311 static void filter_remove_cb(GtkWidget *, gpointer data)
1312 {
1313         auto filter_view = static_cast<GtkWidget *>(data);
1314         GtkTreeSelection *selection;
1315         GtkTreeIter iter;
1316         FilterEntry *fe;
1317
1318         if (!filter_store) return;
1319         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_view));
1320         if (!gtk_tree_selection_get_selected(selection, nullptr, &iter)) return;
1321         gtk_tree_model_get(GTK_TREE_MODEL(filter_store), &iter, 0, &fe, -1);
1322         if (!fe) return;
1323
1324         filter_remove_entry(fe);
1325         filter_rebuild();
1326         filter_store_populate();
1327 }
1328
1329 static gboolean filter_default_ok_scroll(GtkTreeView *data)
1330 {
1331         GtkTreeIter iter;
1332         GtkTreePath *path;
1333         GtkTreeViewColumn *column;
1334
1335         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(filter_store), &iter);
1336         path = gtk_tree_model_get_path(GTK_TREE_MODEL(filter_store), &iter);
1337         column = gtk_tree_view_get_column(GTK_TREE_VIEW(data),0);
1338
1339         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(data),
1340                                      path, column,
1341                                      FALSE, 0.0, 0.0);
1342
1343         gtk_tree_path_free(path);
1344
1345         return(G_SOURCE_REMOVE);
1346 }
1347
1348 static void filter_default_ok_cb(GenericDialog *gd, gpointer)
1349 {
1350         filter_reset();
1351         filter_add_defaults();
1352         filter_rebuild();
1353         filter_store_populate();
1354
1355         g_idle_add(reinterpret_cast<GSourceFunc>(filter_default_ok_scroll), gd->data);
1356 }
1357
1358 static void dummy_cancel_cb(GenericDialog *, gpointer)
1359 {
1360         /* no op, only so cancel button appears */
1361 }
1362
1363 static void filter_default_cb(GtkWidget *widget, gpointer data)
1364 {
1365         GenericDialog *gd;
1366
1367         gd = generic_dialog_new(_("Reset filters"),
1368                                 "reset_filter", widget, TRUE,
1369                                 dummy_cancel_cb, data);
1370         generic_dialog_add_message(gd, GQ_ICON_DIALOG_QUESTION, _("Reset filters"),
1371                                    _("This will reset the file filters to the defaults.\nContinue?"), TRUE);
1372         generic_dialog_add_button(gd, GQ_ICON_OK, "OK", filter_default_ok_cb, TRUE);
1373         gtk_widget_show(gd->dialog);
1374 }
1375
1376 static void filter_disable_cb(GtkWidget *widget, gpointer data)
1377 {
1378         auto frame = static_cast<GtkWidget *>(data);
1379
1380         gtk_widget_set_sensitive(frame,
1381                                  !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
1382 }
1383
1384 static void safe_delete_view_cb(GtkWidget *, gpointer)
1385 {
1386         layout_set_path(nullptr, gq_gtk_entry_get_text(GTK_ENTRY(safe_delete_path_entry)));
1387 }
1388
1389 static void safe_delete_clear_ok_cb(GenericDialog *, gpointer)
1390 {
1391         file_util_trash_clear();
1392 }
1393
1394 static void safe_delete_clear_cb(GtkWidget *widget, gpointer)
1395 {
1396         GenericDialog *gd;
1397         GtkWidget *entry;
1398         gd = generic_dialog_new(_("Clear trash"),
1399                                 "clear_trash", widget, TRUE,
1400                                 dummy_cancel_cb, nullptr);
1401         generic_dialog_add_message(gd, GQ_ICON_DIALOG_QUESTION, _("Clear trash"),
1402                                     _("This will remove the trash contents."), FALSE);
1403         generic_dialog_add_button(gd, GQ_ICON_OK, "OK", safe_delete_clear_ok_cb, TRUE);
1404         entry = gtk_entry_new();
1405         gtk_widget_set_can_focus(entry, FALSE);
1406         gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
1407         if (options->file_ops.safe_delete_path) gq_gtk_entry_set_text(GTK_ENTRY(entry), options->file_ops.safe_delete_path);
1408         gq_gtk_box_pack_start(GTK_BOX(gd->vbox), entry, FALSE, FALSE, 0);
1409         gtk_widget_show(entry);
1410         gtk_widget_show(gd->dialog);
1411 }
1412
1413 static void image_overlay_template_view_changed_cb(GtkWidget *, gpointer data)
1414 {
1415         GtkWidget *pTextView;
1416         GtkTextBuffer *pTextBuffer;
1417         GtkTextIter iStart;
1418         GtkTextIter iEnd;
1419
1420         pTextView = GTK_WIDGET(data);
1421
1422         pTextBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pTextView));
1423         gtk_text_buffer_get_start_iter(pTextBuffer, &iStart);
1424         gtk_text_buffer_get_end_iter(pTextBuffer, &iEnd);
1425
1426         set_image_overlay_template_string(&c_options->image_overlay.template_string,
1427                                           gtk_text_buffer_get_text(pTextBuffer, &iStart, &iEnd, TRUE));
1428 }
1429
1430 static void image_overlay_default_template_ok_cb(GenericDialog *, gpointer data)
1431 {
1432         auto text_view = static_cast<GtkTextView *>(data);
1433         GtkTextBuffer *buffer;
1434
1435         set_default_image_overlay_template_string(&options->image_overlay.template_string);
1436         if (!configwindow) return;
1437
1438         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
1439         gtk_text_buffer_set_text(buffer, options->image_overlay.template_string, -1);
1440 }
1441
1442 static void image_overlay_default_template_cb(GtkWidget *widget, gpointer data)
1443 {
1444         GenericDialog *gd;
1445
1446         gd = generic_dialog_new(_("Reset image overlay template string"),
1447                                 "reset_image_overlay_template_string", widget, TRUE,
1448                                 dummy_cancel_cb, data);
1449         generic_dialog_add_message(gd, GQ_ICON_DIALOG_QUESTION, _("Reset image overlay template string"),
1450                                    _("This will reset the image overlay template string to the default.\nContinue?"), TRUE);
1451         generic_dialog_add_button(gd, GQ_ICON_OK, "OK", image_overlay_default_template_ok_cb, TRUE);
1452         gtk_widget_show(gd->dialog);
1453 }
1454
1455 static void image_overlay_help_cb(GtkWidget *, gpointer)
1456 {
1457         help_window_show("GuideOptionsOSD.html");
1458 }
1459
1460 static void font_activated_cb(GtkFontChooser *widget, gchar *fontname, gpointer)
1461 {
1462         g_free(c_options->image_overlay.font);
1463         c_options->image_overlay.font = g_strdup(fontname);
1464         g_free(fontname);
1465
1466         gq_gtk_widget_destroy(GTK_WIDGET(widget));
1467 }
1468
1469 static void font_response_cb(GtkDialog *dialog, gint response_id, gpointer)
1470 {
1471         gchar *font;
1472
1473         g_free(c_options->image_overlay.font);
1474         c_options->image_overlay.font = g_strdup(options->image_overlay.font);
1475
1476         if (response_id == GTK_RESPONSE_OK)
1477                 {
1478                 font = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(dialog));
1479                 g_free(c_options->image_overlay.font);
1480                 c_options->image_overlay.font = g_strdup(font);
1481                 g_free(font);
1482                 }
1483
1484         g_object_unref(dialog);
1485 }
1486
1487 static void image_overlay_set_font_cb(GtkWidget *widget, gpointer)
1488 {
1489         GtkWidget *dialog;
1490
1491         dialog = gtk_font_chooser_dialog_new("Image Overlay Font", GTK_WINDOW(gtk_widget_get_toplevel(widget)));
1492         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1493         gtk_font_chooser_set_font(GTK_FONT_CHOOSER(dialog), options->image_overlay.font);
1494
1495         g_signal_connect(dialog, "font-activated", G_CALLBACK(font_activated_cb), nullptr);
1496         g_signal_connect(dialog, "response", G_CALLBACK(font_response_cb), nullptr);
1497
1498         gtk_widget_show(dialog);
1499 }
1500
1501 static void text_color_activated_cb(GtkColorChooser *chooser, GdkRGBA *color, gpointer)
1502 {
1503         c_options->image_overlay.text_red = color->red * 255;
1504         c_options->image_overlay.text_green = color->green * 255;
1505         c_options->image_overlay.text_blue = color->blue * 255;
1506         c_options->image_overlay.text_alpha = color->alpha * 255;
1507
1508         gq_gtk_widget_destroy(GTK_WIDGET(chooser));
1509 }
1510
1511 static void text_color_response_cb(GtkDialog *dialog, gint response_id, gpointer)
1512 {
1513         GdkRGBA color;
1514
1515         c_options->image_overlay.text_red = options->image_overlay.text_red;
1516         c_options->image_overlay.text_green = options->image_overlay.text_green;
1517         c_options->image_overlay.text_blue = options->image_overlay.text_blue;
1518         c_options->image_overlay.text_alpha = options->image_overlay.text_alpha;
1519
1520         if (response_id == GTK_RESPONSE_OK)
1521                 {
1522                 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &color);
1523                 c_options->image_overlay.text_red = color.red * 255;
1524                 c_options->image_overlay.text_green = color.green * 255;
1525                 c_options->image_overlay.text_blue = color.blue * 255;
1526                 c_options->image_overlay.text_alpha = color.alpha * 255;
1527                 }
1528
1529         g_object_unref(dialog);
1530 }
1531
1532 static void image_overlay_set_text_color_cb(GtkWidget *widget, gpointer)
1533 {
1534         GtkWidget *dialog;
1535         GdkRGBA color;
1536
1537         dialog = gtk_color_chooser_dialog_new(_("Image Overlay Text Color"), GTK_WINDOW(gtk_widget_get_toplevel(widget)));
1538         color.red = options->image_overlay.text_red;
1539         color.green = options->image_overlay.text_green;
1540         color.blue = options->image_overlay.text_blue;
1541         color.alpha = options->image_overlay.text_alpha;
1542         gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(dialog), &color);
1543         gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(dialog), TRUE);
1544
1545         g_signal_connect(dialog, "color-activated", G_CALLBACK(text_color_activated_cb), nullptr);
1546         g_signal_connect(dialog, "response", G_CALLBACK(text_color_response_cb), nullptr);
1547
1548         gtk_widget_show(dialog);
1549 }
1550
1551 static void bg_color_activated_cb(GtkColorChooser *chooser, GdkRGBA *color, gpointer)
1552 {
1553         c_options->image_overlay.background_red = color->red * 255;
1554         c_options->image_overlay.background_green = color->green * 255;
1555         c_options->image_overlay.background_blue = color->blue * 255;
1556         c_options->image_overlay.background_alpha = color->alpha * 255;
1557
1558         gq_gtk_widget_destroy(GTK_WIDGET(chooser));
1559 }
1560
1561 static void bg_color_response_cb(GtkDialog *dialog, gint response_id, gpointer)
1562 {
1563         GdkRGBA color;
1564
1565         c_options->image_overlay.background_red = options->image_overlay.background_red;
1566         c_options->image_overlay.background_green = options->image_overlay.background_green;
1567         c_options->image_overlay.background_blue = options->image_overlay.background_blue;
1568         c_options->image_overlay.background_alpha = options->image_overlay.background_alpha;
1569
1570         if (response_id == GTK_RESPONSE_OK)
1571                 {
1572                 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &color);
1573                 c_options->image_overlay.background_red = color.red * 255;
1574                 c_options->image_overlay.background_green = color.green * 255;
1575                 c_options->image_overlay.background_blue = color.blue * 255;
1576                 c_options->image_overlay.background_alpha = color.alpha * 255;
1577                 }
1578         gq_gtk_widget_destroy(GTK_WIDGET(dialog));
1579 }
1580
1581 static void image_overlay_set_background_color_cb(GtkWidget *widget, gpointer)
1582 {
1583         GtkWidget *dialog;
1584         GdkRGBA color;
1585
1586         dialog = gtk_color_chooser_dialog_new(_("Image Overlay Background Color"), GTK_WINDOW(gtk_widget_get_toplevel(widget)));
1587         color.red = options->image_overlay.background_red;
1588         color.green = options->image_overlay.background_green;
1589         color.blue = options->image_overlay.background_blue;
1590         color.alpha = options->image_overlay.background_alpha;
1591         gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(dialog), &color);
1592         gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(dialog), TRUE);
1593
1594         g_signal_connect(dialog, "color-activated", G_CALLBACK(bg_color_activated_cb), nullptr);
1595         g_signal_connect(dialog, "response", G_CALLBACK(bg_color_response_cb), nullptr);
1596
1597         gtk_widget_show(dialog);
1598 }
1599
1600 static void accel_store_populate()
1601 {
1602         LayoutWindow *lw;
1603         GList *groups, *actions;
1604         GtkAction *action;
1605         const gchar *accel_path;
1606         GtkAccelKey key;
1607         GtkTreeIter iter;
1608
1609         if (!accel_store || !layout_window_list || !layout_window_list->data) return;
1610
1611         gtk_tree_store_clear(accel_store);
1612         lw = static_cast<LayoutWindow *>(layout_window_list->data); /* get the actions from the first window, it should not matter, they should be the same in all windows */
1613
1614         g_assert(lw && lw->ui_manager);
1615         groups = gtk_ui_manager_get_action_groups(lw->ui_manager);
1616         while (groups)
1617                 {
1618                 actions = gtk_action_group_list_actions(GTK_ACTION_GROUP(groups->data));
1619                 while (actions)
1620                         {
1621                         action = GTK_ACTION(actions->data);
1622                         accel_path = gtk_action_get_accel_path(action);
1623                         if (accel_path && gtk_accel_map_lookup_entry(accel_path, &key))
1624                                 {
1625                                 gchar *label, *label2, *tooltip, *accel;
1626                                 g_object_get(action,
1627                                              "tooltip", &tooltip,
1628                                              "label", &label,
1629                                              NULL);
1630
1631                                 if (pango_parse_markup(label, -1, '_', nullptr, &label2, nullptr, nullptr) && label2)
1632                                         {
1633                                         g_free(label);
1634                                         label = label2;
1635                                         }
1636
1637                                 accel = gtk_accelerator_name(key.accel_key, key.accel_mods);
1638
1639                                 if (tooltip)
1640                                         {
1641                                         gtk_tree_store_append(accel_store, &iter, nullptr);
1642                                         gtk_tree_store_set(accel_store, &iter,
1643                                                            AE_ACTION, label,
1644                                                            AE_KEY, accel,
1645                                                            AE_TOOLTIP, tooltip ? tooltip : "",
1646                                                            AE_ACCEL, accel_path,
1647                                                            -1);
1648                                         }
1649
1650                                 g_free(accel);
1651                                 g_free(label);
1652                                 g_free(tooltip);
1653                                 }
1654                         actions = actions->next;
1655                         }
1656
1657                 groups = groups->next;
1658                 }
1659 }
1660
1661 static void accel_store_cleared_cb(GtkCellRendererAccel *, gchar *, gpointer)
1662 {
1663
1664 }
1665
1666 static gboolean accel_remove_key_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
1667 {
1668         auto accel1 = static_cast<gchar *>(data);
1669         gchar *accel2;
1670         GtkAccelKey key1;
1671         GtkAccelKey key2;
1672
1673         gtk_tree_model_get(model, iter, AE_KEY, &accel2, -1);
1674
1675         gtk_accelerator_parse(accel1, &key1.accel_key, &key1.accel_mods);
1676         gtk_accelerator_parse(accel2, &key2.accel_key, &key2.accel_mods);
1677
1678         if (key1.accel_key == key2.accel_key && key1.accel_mods == key2.accel_mods)
1679                 {
1680                 gtk_tree_store_set(accel_store, iter, AE_KEY, "",  -1);
1681                 DEBUG_1("accelerator key '%s' is already used, removing.", accel1);
1682                 }
1683
1684         g_free(accel2);
1685
1686         return FALSE;
1687 }
1688
1689
1690 static void accel_store_edited_cb(GtkCellRendererAccel *, gchar *path_string, guint accel_key, GdkModifierType accel_mods, guint, gpointer)
1691 {
1692         auto model = reinterpret_cast<GtkTreeModel *>(accel_store);
1693         GtkTreeIter iter;
1694         gchar *acc;
1695         gchar *accel_path;
1696         GtkAccelKey old_key, key;
1697         GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
1698
1699         gtk_tree_model_get_iter(model, &iter, path);
1700         gtk_tree_model_get(model, &iter, AE_ACCEL, &accel_path, -1);
1701
1702         /* test if the accelerator can be stored without conflicts*/
1703         gtk_accel_map_lookup_entry(accel_path, &old_key);
1704
1705         /* change the key and read it back (change may fail on keys hardcoded in gtk)*/
1706         gtk_accel_map_change_entry(accel_path, accel_key, accel_mods, TRUE);
1707         gtk_accel_map_lookup_entry(accel_path, &key);
1708
1709         /* restore the original for now, the key will be really changed when the changes are confirmed */
1710         gtk_accel_map_change_entry(accel_path, old_key.accel_key, old_key.accel_mods, TRUE);
1711
1712         acc = gtk_accelerator_name(key.accel_key, key.accel_mods);
1713         gtk_tree_model_foreach(GTK_TREE_MODEL(accel_store), accel_remove_key_cb, acc);
1714
1715         gtk_tree_store_set(accel_store, &iter, AE_KEY, acc, -1);
1716         gtk_tree_path_free(path);
1717         g_free(acc);
1718 }
1719
1720 static gboolean accel_default_scroll(GtkTreeView *data)
1721 {
1722         GtkTreeIter iter;
1723         GtkTreePath *path;
1724         GtkTreeViewColumn *column;
1725
1726         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(accel_store), &iter);
1727         path = gtk_tree_model_get_path(GTK_TREE_MODEL(accel_store), &iter);
1728         column = gtk_tree_view_get_column(GTK_TREE_VIEW(data),0);
1729
1730         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(data),
1731                                      path, column,
1732                                      FALSE, 0.0, 0.0);
1733
1734         gtk_tree_path_free(path);
1735
1736         return(G_SOURCE_REMOVE);
1737 }
1738
1739 static void accel_default_cb(GtkWidget *, gpointer data)
1740 {
1741         accel_store_populate();
1742
1743         g_idle_add(reinterpret_cast<GSourceFunc>(accel_default_scroll), data);
1744 }
1745
1746 void accel_clear_selection(GtkTreeModel *, GtkTreePath *, GtkTreeIter *iter, gpointer)
1747 {
1748         gtk_tree_store_set(accel_store, iter, AE_KEY, "", -1);
1749 }
1750
1751 void accel_reset_selection(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
1752 {
1753         GtkAccelKey key;
1754         gchar *accel_path, *accel;
1755
1756         gtk_tree_model_get(model, iter, AE_ACCEL, &accel_path, -1);
1757         gtk_accel_map_lookup_entry(accel_path, &key);
1758         accel = gtk_accelerator_name(key.accel_key, key.accel_mods);
1759
1760         gtk_tree_model_foreach(GTK_TREE_MODEL(accel_store), accel_remove_key_cb, accel);
1761
1762         gtk_tree_store_set(accel_store, iter, AE_KEY, accel, -1);
1763         g_free(accel_path);
1764         g_free(accel);
1765 }
1766
1767 static void accel_clear_cb(GtkWidget *, gpointer data)
1768 {
1769         GtkTreeSelection *selection;
1770
1771         if (!accel_store) return;
1772         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data));
1773         gtk_tree_selection_selected_foreach(selection, &accel_clear_selection, nullptr);
1774 }
1775
1776 static void accel_reset_cb(GtkWidget *, gpointer data)
1777 {
1778         GtkTreeSelection *selection;
1779
1780         if (!accel_store) return;
1781         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data));
1782         gtk_tree_selection_selected_foreach(selection, &accel_reset_selection, nullptr);
1783 }
1784
1785
1786
1787 static GtkWidget *scrolled_notebook_page(GtkWidget *notebook, const gchar *title)
1788 {
1789         GtkWidget *label;
1790         GtkWidget *vbox;
1791         GtkWidget *scrolled;
1792         GtkWidget *viewport;
1793
1794         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
1795         gtk_container_set_border_width(GTK_CONTAINER(scrolled), PREF_PAD_BORDER);
1796         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1797                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1798         label = gtk_label_new(title);
1799         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled, label);
1800         gtk_widget_show(scrolled);
1801
1802         viewport = gtk_viewport_new(nullptr, nullptr);
1803         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
1804         gq_gtk_container_add(GTK_WIDGET(scrolled), viewport);
1805         gtk_widget_show(viewport);
1806
1807         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1808         gq_gtk_container_add(GTK_WIDGET(viewport), vbox);
1809         gtk_widget_show(vbox);
1810
1811         return vbox;
1812 }
1813
1814 static void cache_standard_cb(GtkWidget *widget, gpointer)
1815 {
1816         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
1817                 {
1818                 c_options->thumbnails.spec_standard =TRUE;
1819                 c_options->thumbnails.cache_into_dirs = FALSE;
1820                 }
1821 }
1822
1823 static void cache_geeqie_cb(GtkWidget *widget, gpointer)
1824 {
1825         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
1826                 {
1827                 c_options->thumbnails.spec_standard =FALSE;
1828                 c_options->thumbnails.cache_into_dirs = FALSE;
1829                 }
1830 }
1831
1832 static void cache_local_cb(GtkWidget *widget, gpointer)
1833 {
1834         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
1835                 {
1836                 c_options->thumbnails.cache_into_dirs = TRUE;
1837                 c_options->thumbnails.spec_standard =FALSE;
1838                 }
1839 }
1840
1841 static void help_search_engine_entry_icon_cb(GtkEntry *, GtkEntryIconPosition pos, GdkEvent *, gpointer userdata)
1842 {
1843         if (pos == GTK_ENTRY_ICON_PRIMARY)
1844                 {
1845                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), HELP_SEARCH_ENGINE);
1846                 }
1847         else
1848                 {
1849                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), "");
1850                 }
1851 }
1852
1853 static void star_rating_star_icon_cb(GtkEntry *, GtkEntryIconPosition pos, GdkEvent *, gpointer userdata)
1854 {
1855         gchar *rating_symbol;
1856
1857         if (pos == GTK_ENTRY_ICON_PRIMARY)
1858                 {
1859                 rating_symbol = g_strdup_printf("U+%X", STAR_RATING_STAR);
1860                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), rating_symbol);
1861                 g_free(rating_symbol);
1862                 }
1863         else
1864                 {
1865                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), "U+");
1866                 gtk_widget_grab_focus(GTK_WIDGET(userdata));
1867                 gtk_editable_select_region(GTK_EDITABLE(userdata), 2, 2);
1868                 }
1869 }
1870
1871 static void star_rating_rejected_icon_cb(GtkEntry *, GtkEntryIconPosition pos, GdkEvent *, gpointer userdata)
1872 {
1873         gchar *rating_symbol;
1874
1875         if (pos == GTK_ENTRY_ICON_PRIMARY)
1876                 {
1877                 rating_symbol = g_strdup_printf("U+%X", STAR_RATING_REJECTED);
1878                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), rating_symbol);
1879                 g_free(rating_symbol);
1880                 }
1881         else
1882                 {
1883                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), "U+");
1884                 gtk_widget_grab_focus(GTK_WIDGET(userdata));
1885                 gtk_editable_select_region(GTK_EDITABLE(userdata), 2, 2);
1886                 }
1887 }
1888
1889 static guint star_rating_symbol_test(GtkWidget *, gpointer data)
1890 {
1891         auto hbox = static_cast<GtkContainer *>(data);
1892         GString *str = g_string_new(nullptr);
1893         GtkEntry *hex_code_entry;
1894         gchar *hex_code_full;
1895         gchar **hex_code;
1896         GList *list;
1897         guint64 hex_value = 0;
1898
1899         list = gtk_container_get_children(hbox);
1900
1901         hex_code_entry = static_cast<GtkEntry *>(g_list_nth_data(list, 2));
1902         hex_code_full = g_strdup(gq_gtk_entry_get_text(hex_code_entry));
1903
1904         hex_code = g_strsplit(hex_code_full, "+", 2);
1905         if (hex_code[0] && hex_code[1])
1906                 {
1907                 hex_value = strtoull(hex_code[1], nullptr, 16);
1908                 }
1909         if (!hex_value || hex_value > 0x10FFFF)
1910                 {
1911                 hex_value = 0x003F; // Unicode 'Question Mark'
1912                 }
1913         str = g_string_append_unichar(str, static_cast<gunichar>(hex_value));
1914         gtk_label_set_text(static_cast<GtkLabel *>(g_list_nth_data(list, 1)), str->str);
1915
1916         g_strfreev(hex_code);
1917         g_string_free(str, TRUE);
1918         g_free(hex_code_full);
1919
1920         return hex_value;
1921 }
1922
1923 static void star_rating_star_test_cb(GtkWidget *widget, gpointer data)
1924 {
1925         guint64 star_symbol;
1926
1927         star_symbol = star_rating_symbol_test(widget, data);
1928         c_options->star_rating.star = star_symbol;
1929 }
1930
1931 static void star_rating_rejected_test_cb(GtkWidget *widget, gpointer data)
1932 {
1933         guint64 rejected_symbol;
1934
1935         rejected_symbol = star_rating_symbol_test(widget, data);
1936         c_options->star_rating.rejected = rejected_symbol;
1937 }
1938
1939 /* general options tab */
1940 static void timezone_database_install_cb(GtkWidget *widget, gpointer data);
1941 struct TZData
1942 {
1943         GenericDialog *gd;
1944         GCancellable *cancellable;
1945
1946         GtkWidget *progress;
1947         GFile *tmp_g_file;
1948         GFile *timezone_database_gq;
1949         gchar *timezone_database_user;
1950 };
1951
1952 static void config_tab_general(GtkWidget *notebook)
1953 {
1954         GtkWidget *vbox;
1955         GtkWidget *hbox;
1956         GtkWidget *group;
1957         GtkWidget *group_frame;
1958         GtkWidget *subgroup;
1959         GtkWidget *button;
1960         GtkWidget *ct_button;
1961         GtkWidget *table;
1962         GtkWidget *spin;
1963         gint hours, minutes, remainder;
1964         gdouble seconds;
1965         GtkWidget *star_rating_entry;
1966         GString *str;
1967         gchar *rating_symbol;
1968         gchar *path;
1969         gchar *basename;
1970         gchar *download_locn;
1971         GNetworkMonitor *net_mon;
1972         GSocketConnectable *tz_org;
1973         gboolean internet_available = FALSE;
1974         TZData *tz;
1975
1976         vbox = scrolled_notebook_page(notebook, _("General"));
1977
1978         group = pref_group_new(vbox, FALSE, _("Thumbnails"), GTK_ORIENTATION_VERTICAL);
1979
1980         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1981         add_thumb_size_menu(table, 0, 0, _("Size:"));
1982         add_quality_menu(table, 0, 1, _("Quality:"), options->thumbnails.quality, &c_options->thumbnails.quality);
1983
1984         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1985         pref_label_new(hbox, _("Custom size: "));
1986         pref_spin_new_int(hbox, _("Width:"), nullptr, 1, 512, 1, options->thumbnails.max_width, &c_options->thumbnails.max_width);
1987         pref_spin_new_int(hbox, _("Height:"), nullptr, 1, 512, 1, options->thumbnails.max_height, &c_options->thumbnails.max_height);
1988
1989         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails and sim. files"),
1990                                           options->thumbnails.enable_caching, &c_options->thumbnails.enable_caching);
1991
1992         subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1993         pref_checkbox_link_sensitivity(ct_button, subgroup);
1994
1995         c_options->thumbnails.spec_standard = options->thumbnails.spec_standard;
1996         c_options->thumbnails.cache_into_dirs = options->thumbnails.cache_into_dirs;
1997         group_frame = pref_frame_new(subgroup, TRUE, _("Use Geeqie thumbnail style and cache"),
1998                                                                                 GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1999         button = pref_radiobutton_new(group_frame, nullptr,  get_thumbnails_cache_dir(),
2000                                                         !options->thumbnails.spec_standard && !options->thumbnails.cache_into_dirs,
2001                                                         G_CALLBACK(cache_geeqie_cb), nullptr);
2002
2003         group_frame = pref_frame_new(subgroup, TRUE,
2004                                                         _("Store thumbnails local to image folder (non-standard)"),
2005                                                         GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2006         pref_radiobutton_new(group_frame, button, "*/.thumbnails",
2007                                                         !options->thumbnails.spec_standard && options->thumbnails.cache_into_dirs,
2008                                                         G_CALLBACK(cache_local_cb), nullptr);
2009
2010         group_frame = pref_frame_new(subgroup, TRUE,
2011                                                         _("Use standard thumbnail style and cache, shared with other applications"),
2012                                                         GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2013         pref_radiobutton_new(group_frame, button, get_thumbnails_standard_cache_dir(),
2014                                                         options->thumbnails.spec_standard && !options->thumbnails.cache_into_dirs,
2015                                                         G_CALLBACK(cache_standard_cb), nullptr);
2016
2017         pref_checkbox_new_int(group, _("Use EXIF thumbnails when available (EXIF thumbnails may be outdated)"),
2018                               options->thumbnails.use_exif, &c_options->thumbnails.use_exif);
2019
2020         pref_checkbox_new_int(group, _("Thumbnail color management"),
2021                                 options->thumbnails.use_color_management, &c_options->thumbnails.use_color_management);
2022
2023         spin = pref_spin_new_int(group, _("Collection preview:"), nullptr,
2024                                  1, 999, 1,
2025                                  options->thumbnails.collection_preview, &c_options->thumbnails.collection_preview);
2026         gtk_widget_set_tooltip_text(spin, _("The maximum number of thumbnails shown in a Collection preview montage"));
2027
2028 #ifdef HAVE_FFMPEGTHUMBNAILER_METADATA
2029         pref_checkbox_new_int(group, _("Use embedded metadata in video files as thumbnails when available"),
2030                               options->thumbnails.use_ft_metadata, &c_options->thumbnails.use_ft_metadata);
2031 #endif
2032
2033         pref_spacer(group, PREF_PAD_GROUP);
2034
2035         group = pref_group_new(vbox, FALSE, _("Star Rating"), GTK_ORIENTATION_VERTICAL);
2036
2037         c_options->star_rating.star = options->star_rating.star;
2038         c_options->star_rating.rejected = options->star_rating.rejected;
2039
2040         str = g_string_new(nullptr);
2041         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2042         pref_label_new(hbox, _("Star character: "));
2043         str = g_string_append_unichar(str, options->star_rating.star);
2044         pref_label_new(hbox, g_strdup(str->str));
2045         rating_symbol = g_strdup_printf("U+%X", options->star_rating.star);
2046         star_rating_entry = gtk_entry_new();
2047         gq_gtk_entry_set_text(GTK_ENTRY(star_rating_entry), rating_symbol);
2048         gq_gtk_box_pack_start(GTK_BOX(hbox), star_rating_entry, FALSE, FALSE, 0);
2049         gtk_entry_set_width_chars(GTK_ENTRY(star_rating_entry), 15);
2050         gtk_widget_show(star_rating_entry);
2051         button = pref_button_new(nullptr, nullptr, _("Set"),
2052                                         G_CALLBACK(star_rating_star_test_cb), hbox);
2053         gtk_widget_set_tooltip_text(button, _("Display selected character"));
2054         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2055         gtk_widget_show(button);
2056         gtk_widget_set_tooltip_text(star_rating_entry, _("Hexadecimal representation of a Unicode character. A list of all Unicode characters may be found on the Internet."));
2057         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(star_rating_entry),
2058                                                 GTK_ENTRY_ICON_SECONDARY, GQ_ICON_CLEAR);
2059         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(star_rating_entry),
2060                                                 GTK_ENTRY_ICON_SECONDARY, _("Clear"));
2061         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(star_rating_entry),
2062                                                 GTK_ENTRY_ICON_PRIMARY, GQ_ICON_REVERT);
2063         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(star_rating_entry),
2064                                                 GTK_ENTRY_ICON_PRIMARY, _("Default"));
2065         g_signal_connect(GTK_ENTRY(star_rating_entry), "icon-press",
2066                                                 G_CALLBACK(star_rating_star_icon_cb),
2067                                                 star_rating_entry);
2068
2069         g_string_free(str, TRUE);
2070         g_free(rating_symbol);
2071
2072         str = g_string_new(nullptr);
2073         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2074         pref_label_new(hbox, _("Rejected character: "));
2075         str = g_string_append_unichar(str, options->star_rating.rejected);
2076         pref_label_new(hbox, g_strdup(str->str));
2077         rating_symbol = g_strdup_printf("U+%X", options->star_rating.rejected);
2078         star_rating_entry = gtk_entry_new();
2079         gq_gtk_entry_set_text(GTK_ENTRY(star_rating_entry), rating_symbol);
2080         gq_gtk_box_pack_start(GTK_BOX(hbox), star_rating_entry, FALSE, FALSE, 0);
2081         gtk_entry_set_width_chars(GTK_ENTRY(star_rating_entry), 15);
2082         gtk_widget_show(star_rating_entry);
2083         button = pref_button_new(nullptr, nullptr, _("Set"),
2084                                         G_CALLBACK(star_rating_rejected_test_cb), hbox);
2085         gtk_widget_set_tooltip_text(button, _("Display selected character"));
2086         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2087         gtk_widget_show(button);
2088         gtk_widget_set_tooltip_text(star_rating_entry, _("Hexadecimal representation of a Unicode character. A list of all Unicode characters may be found on the Internet."));
2089         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(star_rating_entry),
2090                                                 GTK_ENTRY_ICON_SECONDARY, GQ_ICON_CLEAR);
2091         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(star_rating_entry),
2092                                                 GTK_ENTRY_ICON_SECONDARY, _("Clear"));
2093         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(star_rating_entry),
2094                                                 GTK_ENTRY_ICON_PRIMARY, GQ_ICON_REVERT);
2095         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(star_rating_entry),
2096                                                 GTK_ENTRY_ICON_PRIMARY, _("Default"));
2097         g_signal_connect(GTK_ENTRY(star_rating_entry), "icon-press",
2098                                                 G_CALLBACK(star_rating_rejected_icon_cb),
2099                                                 star_rating_entry);
2100
2101         g_string_free(str, TRUE);
2102         g_free(rating_symbol);
2103
2104         pref_spacer(group, PREF_PAD_GROUP);
2105
2106         group = pref_group_new(vbox, FALSE, _("Slide show"), GTK_ORIENTATION_VERTICAL);
2107
2108         c_options->slideshow.delay = options->slideshow.delay;
2109         hours = options->slideshow.delay / (3600 * SLIDESHOW_SUBSECOND_PRECISION);
2110         remainder = options->slideshow.delay % (3600 * SLIDESHOW_SUBSECOND_PRECISION);
2111         minutes = remainder / (60 * SLIDESHOW_SUBSECOND_PRECISION);
2112         seconds = static_cast<gdouble>(remainder % (60 * SLIDESHOW_SUBSECOND_PRECISION)) /
2113                                                                                         SLIDESHOW_SUBSECOND_PRECISION;
2114
2115         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2116
2117         spin = pref_spin_new(hbox, _("Delay between image change hrs:mins:secs.dec"), nullptr,
2118                                                                                 0, 23, 1.0, 0,
2119                                                                                 options->slideshow.delay ? hours : 0.0,
2120                                                                                 G_CALLBACK(slideshow_delay_hours_cb), nullptr);
2121         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_ALWAYS);
2122         spin = pref_spin_new(hbox, ":" , nullptr,
2123                                                                                 0, 59, 1.0, 0,
2124                                                                                 options->slideshow.delay ? minutes: 0.0,
2125                                                                                 G_CALLBACK(slideshow_delay_minutes_cb), nullptr);
2126         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_ALWAYS);
2127         spin = pref_spin_new(hbox, ":", nullptr,
2128                                                                                 SLIDESHOW_MIN_SECONDS, 59, 1.0, 1,
2129                                                                                 options->slideshow.delay ? seconds : 10.0,
2130                                                                                 G_CALLBACK(slideshow_delay_seconds_cb), nullptr);
2131         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_ALWAYS);
2132
2133         pref_checkbox_new_int(group, _("Random"), options->slideshow.random, &c_options->slideshow.random);
2134         pref_checkbox_new_int(group, _("Repeat"), options->slideshow.repeat, &c_options->slideshow.repeat);
2135
2136         pref_spacer(group, PREF_PAD_GROUP);
2137
2138         group = pref_group_new(vbox, FALSE, _("Image loading and caching"), GTK_ORIENTATION_VERTICAL);
2139
2140         pref_spin_new_int(group, _("Decoded image cache size (MiB):"), nullptr,
2141                           0, 99999, 1, options->image.image_cache_max, &c_options->image.image_cache_max);
2142         pref_checkbox_new_int(group, _("Preload next image"),
2143                               options->image.enable_read_ahead, &c_options->image.enable_read_ahead);
2144
2145         pref_checkbox_new_int(group, _("Refresh on file change"),
2146                               options->update_on_time_change, &c_options->update_on_time_change);
2147
2148
2149         pref_spacer(group, PREF_PAD_GROUP);
2150
2151         group = pref_group_new(vbox, FALSE, _("Menu style"), GTK_ORIENTATION_VERTICAL);
2152
2153         pref_checkbox_new_int(group, _("☰ style menu button (NOTE! Geeqie must be restarted for change to take effect)"),
2154                                 options->hamburger_menu, &c_options->hamburger_menu);
2155         gtk_widget_set_tooltip_text(group, _("Use a ☰ style menu button instead of the classic style across the top of the frame"));
2156
2157         pref_spacer(group, PREF_PAD_GROUP);
2158
2159         group = pref_group_new(vbox, FALSE, _("Expand toolbar"), GTK_ORIENTATION_VERTICAL);
2160
2161         pref_checkbox_new_int(group, _("Expand menu/toolbar (NOTE! Geeqie must be restarted for change to take effect)"),
2162                                 options->expand_menu_toolbar, &c_options->expand_menu_toolbar);
2163         gtk_widget_set_tooltip_text(group, _("Expand the menu/toolbar to the full width of the window"));
2164
2165         pref_spacer(group, PREF_PAD_GROUP);
2166
2167         group = pref_group_new(vbox, FALSE, _("Hide Selectable Bars"), GTK_ORIENTATION_VERTICAL);
2168
2169         pref_checkbox_new_int(group, _("Menu bar"),
2170                                 options->selectable_bars.menu_bar, &c_options->selectable_bars.menu_bar);
2171
2172         pref_checkbox_new_int(group, _("Tool bar"),
2173                                 options->selectable_bars.tool_bar, &c_options->selectable_bars.tool_bar);
2174
2175         pref_checkbox_new_int(group, _("Status bar"),
2176                                 options->selectable_bars.status_bar, &c_options->selectable_bars.status_bar);
2177         gtk_widget_set_tooltip_text(group, _("The Hide Selectable Bars menu item (default keystroke is control-backtick) will toggle the display of the bars selected here"));
2178
2179         pref_spacer(group, PREF_PAD_GROUP);
2180
2181         if (g_getenv("APPDIR") && strstr(g_getenv("APPDIR"), "/tmp/.mount_Geeqie"))
2182                 {
2183                 group = pref_group_new(vbox, FALSE, _("AppImage updates notifications"), GTK_ORIENTATION_VERTICAL);
2184                 hbox = pref_box_new(group, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2185                 pref_checkbox_new_int(group, _("Enable"), options->appimage_notifications, &c_options->appimage_notifications);
2186                 gtk_widget_set_tooltip_text(group, _("Show a notification on start-up if the server has a newer version than the current. Requires an Internet connection"));
2187
2188                 pref_spacer(group, PREF_PAD_GROUP);
2189                 }
2190
2191
2192         net_mon = g_network_monitor_get_default();
2193         tz_org = g_network_address_parse_uri(TIMEZONE_DATABASE_WEB, 80, nullptr);
2194         if (tz_org)
2195                 {
2196                 internet_available = g_network_monitor_can_reach(net_mon, tz_org, nullptr, nullptr);
2197                 g_object_unref(tz_org);
2198                 }
2199
2200         group = pref_group_new(vbox, FALSE, _("Timezone database"), GTK_ORIENTATION_VERTICAL);
2201         hbox = pref_box_new(group, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2202
2203         if (!internet_available)
2204                 {
2205                 gtk_widget_set_sensitive(group, FALSE);
2206                 }
2207
2208         tz = g_new0(TZData, 1);
2209
2210         path = path_from_utf8(TIMEZONE_DATABASE_WEB);
2211         basename = g_path_get_basename(path);
2212         tz->timezone_database_user = g_build_filename(get_rc_dir(), TIMEZONE_DATABASE_FILE, NULL);
2213         g_free(path);
2214         g_free(basename);
2215
2216         if (isfile(tz->timezone_database_user))
2217                 {
2218                 button = pref_button_new(GTK_WIDGET(hbox), nullptr, _("Update"), G_CALLBACK(timezone_database_install_cb), tz);
2219                 }
2220         else
2221                 {
2222                 button = pref_button_new(GTK_WIDGET(hbox), nullptr, _("Install"), G_CALLBACK(timezone_database_install_cb), tz);
2223                 }
2224
2225         download_locn = g_strconcat(_("Download database from: "), TIMEZONE_DATABASE_WEB, NULL);
2226         pref_label_new(GTK_WIDGET(hbox), download_locn);
2227         g_free(download_locn);
2228
2229         if (!internet_available)
2230                 {
2231                 gtk_widget_set_tooltip_text(button, _("No Internet connection!\nThe timezone database is used to display exif time and date\ncorrected for UTC offset and Daylight Saving Time"));
2232                 }
2233         else
2234                 {
2235                 gtk_widget_set_tooltip_text(button, _("The timezone database is used to display exif time and date\ncorrected for UTC offset and Daylight Saving Time"));
2236                 }
2237         gtk_widget_show(button);
2238
2239         pref_spacer(group, PREF_PAD_GROUP);
2240
2241         group = pref_group_new(vbox, FALSE, _("On-line help search engine"), GTK_ORIENTATION_VERTICAL);
2242
2243         help_search_engine_entry = gtk_entry_new();
2244         gq_gtk_entry_set_text(GTK_ENTRY(help_search_engine_entry), options->help_search_engine);
2245         gq_gtk_box_pack_start(GTK_BOX(group), help_search_engine_entry, FALSE, FALSE, 0);
2246         gtk_widget_show(help_search_engine_entry);
2247
2248         gtk_widget_set_tooltip_text(help_search_engine_entry, _("The format varies between search engines, e.g the format may be:\nhttps://www.search_engine.com/search?q=site:geeqie.org/help\nhttps://www.search_engine.com/?q=site:geeqie.org/help"));
2249
2250         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(help_search_engine_entry),
2251                                                 GTK_ENTRY_ICON_SECONDARY, GQ_ICON_CLEAR);
2252         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(help_search_engine_entry),
2253                                                 GTK_ENTRY_ICON_SECONDARY, _("Clear"));
2254         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(help_search_engine_entry),
2255                                                 GTK_ENTRY_ICON_PRIMARY, GQ_ICON_REVERT);
2256         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(help_search_engine_entry),
2257                                                 GTK_ENTRY_ICON_PRIMARY, _("Default"));
2258         g_signal_connect(GTK_ENTRY(help_search_engine_entry), "icon-press",
2259                                                 G_CALLBACK(help_search_engine_entry_icon_cb),
2260                                                 help_search_engine_entry);
2261 }
2262
2263 /* image tab */
2264 static void config_tab_image(GtkWidget *notebook)
2265 {
2266         GtkWidget *hbox;
2267         GtkWidget *vbox;
2268         GtkWidget *group;
2269         GtkWidget *ct_button;
2270         GtkWidget *enlargement_button;
2271         GtkWidget *table;
2272         GtkWidget *spin;
2273
2274         vbox = scrolled_notebook_page(notebook, _("Image"));
2275
2276         group = pref_group_new(vbox, FALSE, _("Zoom"), GTK_ORIENTATION_VERTICAL);
2277
2278         table = pref_table_new(group, 2, 1, FALSE, FALSE);
2279         add_quality_menu(table, 0, 0, _("Quality:"), options->image.zoom_quality, &c_options->image.zoom_quality);
2280
2281         pref_checkbox_new_int(group, _("Two pass rendering (apply HQ zoom and color correction in second pass)"),
2282                               options->image.zoom_2pass, &c_options->image.zoom_2pass);
2283
2284         c_options->image.zoom_increment = options->image.zoom_increment;
2285         spin = pref_spin_new(group, _("Zoom increment:"), nullptr,
2286                              0.01, 4.0, 0.01, 2, static_cast<gdouble>(options->image.zoom_increment) / 100.0,
2287                              G_CALLBACK(zoom_increment_cb), nullptr);
2288         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_ALWAYS);
2289
2290         c_options->image.zoom_style = options->image.zoom_style;
2291         table = pref_table_new(group, 2, 1, FALSE, FALSE);
2292         add_zoom_style_selection_menu(table, 0, 0, _("Zoom style:"), options->image.zoom_style, &c_options->image.zoom_style);
2293
2294         group = pref_group_new(vbox, FALSE, _("Fit image to window"), GTK_ORIENTATION_VERTICAL);
2295
2296         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2297         enlargement_button = pref_checkbox_new_int(hbox, _("Allow enlargement of image (max. size in %)"),
2298                               options->image.zoom_to_fit_allow_expand, &c_options->image.zoom_to_fit_allow_expand);
2299         spin = pref_spin_new_int(hbox, nullptr, nullptr,
2300                                  100, 999, 1,
2301                                  options->image.max_enlargement_size, &c_options->image.max_enlargement_size);
2302         pref_checkbox_link_sensitivity(enlargement_button, spin);
2303         gtk_widget_set_tooltip_text(GTK_WIDGET(hbox), _("Enable this to allow Geeqie to increase the image size for images that are smaller than the current view area when the zoom is set to \"Fit image to window\". This value sets the maximum expansion permitted in percent i.e. 100% is full-size."));
2304
2305         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2306         ct_button = pref_checkbox_new_int(hbox, _("Virtual window size (% of actual window):"),
2307                                           options->image.limit_autofit_size, &c_options->image.limit_autofit_size);
2308         spin = pref_spin_new_int(hbox, nullptr, nullptr,
2309                                  10, 150, 1,
2310                                  options->image.max_autofit_size, &c_options->image.max_autofit_size);
2311         pref_checkbox_link_sensitivity(ct_button, spin);
2312         gtk_widget_set_tooltip_text(GTK_WIDGET(hbox), _("This value will set the virtual size of the window when \"Fit image to window\" is set. Instead of using the actual size of the window, the specified percentage of the window will be used. It allows one to keep a border around the image (values lower than 100%) or to auto zoom the image (values greater than 100%). It affects fullscreen mode too."));
2313
2314         group = pref_group_new(vbox, FALSE, _("Tile size"), GTK_ORIENTATION_VERTICAL);
2315
2316         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2317         spin = pref_spin_new_int(hbox, _("Pixels"), _("(Requires restart)"),
2318                                  128, 4096, 128,
2319                                  options->image.tile_size, &c_options->image.tile_size);
2320         gtk_widget_set_tooltip_text(GTK_WIDGET(hbox), _("This value changes the size of the tiles large images are split into. Increasing the size of the tiles will reduce the tiling effect seen on image changes, but will also slightly increase the delay before the first part of a large image is seen."));
2321
2322         group = pref_group_new(vbox, FALSE, _("Appearance"), GTK_ORIENTATION_VERTICAL);
2323
2324         pref_checkbox_new_int(group, _("Use custom border color in window mode"),
2325                               options->image.use_custom_border_color, &c_options->image.use_custom_border_color);
2326
2327         pref_checkbox_new_int(group, _("Use custom border color in fullscreen mode"),
2328                               options->image.use_custom_border_color_in_fullscreen, &c_options->image.use_custom_border_color_in_fullscreen);
2329
2330         pref_color_button_new(group, _("Border color"), &options->image.border_color,
2331                               G_CALLBACK(pref_color_button_set_cb), &c_options->image.border_color);
2332
2333         c_options->image.border_color = options->image.border_color;
2334
2335         pref_color_button_new(group, _("Alpha channel color 1"), &options->image.alpha_color_1,
2336                               G_CALLBACK(pref_color_button_set_cb), &c_options->image.alpha_color_1);
2337
2338         pref_color_button_new(group, _("Alpha channel color 2"), &options->image.alpha_color_2,
2339                               G_CALLBACK(pref_color_button_set_cb), &c_options->image.alpha_color_2);
2340
2341         c_options->image.alpha_color_1 = options->image.alpha_color_1;
2342         c_options->image.alpha_color_2 = options->image.alpha_color_2;
2343 }
2344
2345 /* windows tab */
2346
2347 static void save_default_window_layout_cb(GtkWidget *, gpointer)
2348 {
2349         LayoutWindow *lw = nullptr;
2350         gchar *default_path;
2351         gchar *tmp_id;
2352
2353         /* Get current lw */
2354         layout_valid(&lw);
2355
2356         tmp_id = lw->options.id;
2357         lw->options.id = g_strdup("");
2358
2359         default_path = g_build_filename(get_rc_dir(), DEFAULT_WINDOW_LAYOUT, NULL);
2360         save_default_layout_options_to_file(default_path, options, lw);
2361         g_free(lw->options.id);
2362         lw->options.id = tmp_id;
2363         g_free(default_path);
2364 }
2365
2366 static gboolean popover_cb(gpointer data)
2367 {
2368         auto popover = static_cast<GtkPopover *>(data);
2369
2370         gtk_popover_popdown(popover);
2371
2372         return FALSE;
2373 }
2374
2375 static void default_layout_changed_cb(GtkWidget *, GtkPopover *popover)
2376 {
2377         gtk_popover_popup(popover);
2378
2379         g_timeout_add(2000, popover_cb, popover);
2380 }
2381
2382 static GtkWidget *create_popover(GtkWidget *parent, GtkWidget *child, GtkPositionType pos)
2383 {
2384         GtkWidget *popover;
2385
2386         popover = gtk_popover_new(parent);
2387         gtk_popover_set_position(GTK_POPOVER (popover), pos);
2388         gq_gtk_container_add(GTK_WIDGET(popover), child);
2389         gtk_container_set_border_width(GTK_CONTAINER (popover), 6);
2390         gtk_widget_show (child);
2391
2392         return popover;
2393 }
2394
2395 static void config_tab_windows(GtkWidget *notebook)
2396 {
2397         GtkWidget *hbox;
2398         GtkWidget *vbox;
2399         GtkWidget *group;
2400         GtkWidget *subgroup;
2401         GtkWidget *button;
2402         GtkWidget *checkbox;
2403         GtkWidget *ct_button;
2404         GtkWidget *spin;
2405
2406         vbox = scrolled_notebook_page(notebook, _("Windows"));
2407
2408         group = pref_group_new(vbox, FALSE, _("State"), GTK_ORIENTATION_VERTICAL);
2409
2410         ct_button = pref_checkbox_new_int(group, _("Remember session"),
2411                                           options->save_window_positions, &c_options->save_window_positions);
2412
2413         checkbox = pref_checkbox_new_int(group, _("Use saved window positions also for new windows"),
2414                                        options->use_saved_window_positions_for_new_windows, &c_options->use_saved_window_positions_for_new_windows);
2415         pref_checkbox_link_sensitivity(ct_button, checkbox);
2416
2417         checkbox = pref_checkbox_new_int(group, _("Remember window workspace"),
2418                               options->save_window_workspace, &c_options->save_window_workspace);
2419         pref_checkbox_link_sensitivity(ct_button, checkbox);
2420
2421         pref_checkbox_new_int(group, _("Remember tool state (float/hidden)"),
2422                               options->tools_restore_state, &c_options->tools_restore_state);
2423
2424         pref_checkbox_new_int(group, _("Remember dialog window positions"),
2425                               options->save_dialog_window_positions, &c_options->save_dialog_window_positions);
2426
2427         pref_checkbox_new_int(group, _("Show window IDs"),
2428                               options->show_window_ids, &c_options->show_window_ids);
2429
2430         subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2431         pref_label_new(subgroup, _("Use current layout for default: "));
2432         button = pref_button_new(subgroup, nullptr, _("Set"), G_CALLBACK(save_default_window_layout_cb), nullptr);
2433
2434         GtkWidget *popover;
2435
2436         popover = create_popover(button, gtk_label_new(_("Current window layout\nhas been set as default")), GTK_POS_TOP);
2437         gtk_popover_set_modal(GTK_POPOVER (popover), FALSE);
2438         g_signal_connect(button, "clicked", G_CALLBACK(default_layout_changed_cb), popover);
2439
2440         group = pref_group_new(vbox, FALSE, _("Size"), GTK_ORIENTATION_VERTICAL);
2441
2442         pref_checkbox_new_int(group, _("Fit window to image when tools are hidden/floating"),
2443                               options->image.fit_window_to_image, &c_options->image.fit_window_to_image);
2444
2445         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2446         ct_button = pref_checkbox_new_int(hbox, _("Limit size when auto-sizing window (%):"),
2447                                           options->image.limit_window_size, &c_options->image.limit_window_size);
2448         spin = pref_spin_new_int(hbox, nullptr, nullptr,
2449                                  10, 150, 1,
2450                                  options->image.max_window_size, &c_options->image.max_window_size);
2451         pref_checkbox_link_sensitivity(ct_button, spin);
2452
2453         group = pref_group_new(vbox, FALSE, _("Full screen"), GTK_ORIENTATION_VERTICAL);
2454
2455         c_options->fullscreen.screen = options->fullscreen.screen;
2456         c_options->fullscreen.above = options->fullscreen.above;
2457         hbox = fullscreen_prefs_selection_new(_("Location:"), &c_options->fullscreen.screen, &c_options->fullscreen.above);
2458         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2459         gtk_widget_show(hbox);
2460
2461         pref_checkbox_new_int(group, _("Smooth image flip"),
2462                               options->fullscreen.clean_flip, &c_options->fullscreen.clean_flip);
2463         pref_checkbox_new_int(group, _("Disable screen saver"),
2464                               options->fullscreen.disable_saver, &c_options->fullscreen.disable_saver);
2465 }
2466
2467 enum {
2468         PRE_FORMATTED_COLUMNS = 5
2469 };
2470 static void config_tab_osd(GtkWidget *notebook)
2471 {
2472         GtkWidget *hbox;
2473         GtkWidget *vbox;
2474         GtkWidget *group;
2475         GtkWidget *button;
2476         GtkWidget *image_overlay_template_view;
2477         GtkWidget *scrolled;
2478         GtkWidget *scrolled_pre_formatted;
2479         GtkTextBuffer *buffer;
2480         GtkWidget *label;
2481         GtkWidget *subgroup;
2482
2483         vbox = scrolled_notebook_page(notebook, _("OSD"));
2484
2485         image_overlay_template_view = gtk_text_view_new();
2486
2487         group = pref_group_new(vbox, FALSE, _("Overlay Screen Display"), GTK_ORIENTATION_VERTICAL);
2488
2489         subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2490
2491         scrolled_pre_formatted = osd_new(PRE_FORMATTED_COLUMNS, image_overlay_template_view);
2492         gtk_widget_set_size_request(scrolled_pre_formatted, 200, 150);
2493         gq_gtk_box_pack_start(GTK_BOX(subgroup), scrolled_pre_formatted, FALSE, FALSE, 0);
2494         gtk_widget_show(scrolled_pre_formatted);
2495         gtk_widget_show(subgroup);
2496
2497         pref_line(group, PREF_PAD_GAP);
2498
2499         pref_label_new(group, _("Image overlay template"));
2500
2501         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
2502         gtk_widget_set_size_request(scrolled, 200, 150);
2503         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
2504         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
2505                                                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2506         gq_gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 5);
2507         gtk_widget_show(scrolled);
2508
2509         gtk_widget_set_tooltip_markup(image_overlay_template_view,
2510                                         _("Extensive formatting options are shown in the Help file"));
2511
2512         gq_gtk_container_add(GTK_WIDGET(scrolled), image_overlay_template_view);
2513         gtk_widget_show(image_overlay_template_view);
2514
2515         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
2516
2517         button = pref_button_new(nullptr, GQ_ICON_SELECT_FONT, _("Font"),
2518                                  G_CALLBACK(image_overlay_set_font_cb), notebook);
2519
2520         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2521         gtk_widget_show(button);
2522
2523         button = pref_button_new(nullptr, GQ_ICON_SELECT_COLOR, _("Text"), G_CALLBACK(image_overlay_set_text_color_cb), nullptr);
2524         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2525         gtk_widget_show(button);
2526
2527         button = pref_button_new(nullptr, GQ_ICON_SELECT_COLOR, _("Background"), G_CALLBACK(image_overlay_set_background_color_cb), nullptr);
2528         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2529         gtk_widget_show(button);
2530         image_overlay_set_text_colors();
2531
2532         button = pref_button_new(nullptr, nullptr, _("Defaults"),
2533                                  G_CALLBACK(image_overlay_default_template_cb), image_overlay_template_view);
2534         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2535         gtk_widget_show(button);
2536
2537         button = pref_button_new(nullptr, GQ_ICON_HELP, _("Help"),
2538                                  G_CALLBACK(image_overlay_help_cb), nullptr);
2539         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2540         gtk_widget_show(button);
2541
2542         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(image_overlay_template_view));
2543         if (options->image_overlay.template_string) gtk_text_buffer_set_text(buffer, options->image_overlay.template_string, -1);
2544         g_signal_connect(G_OBJECT(buffer), "changed",
2545                          G_CALLBACK(image_overlay_template_view_changed_cb), image_overlay_template_view);
2546
2547         pref_line(group, PREF_PAD_GAP);
2548
2549         group = pref_group_new(vbox, FALSE, _("Exif, XMP or IPTC tags"), GTK_ORIENTATION_VERTICAL);
2550         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2551         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2552         gtk_widget_show(hbox);
2553         label = gtk_label_new(_("%Exif.Image.Orientation%"));
2554         gq_gtk_box_pack_start(GTK_BOX(hbox),label, FALSE,FALSE,0);
2555         gtk_widget_show(label);
2556         pref_spacer(group,TRUE);
2557
2558         group = pref_group_new(vbox, FALSE, _("Field separators"), GTK_ORIENTATION_VERTICAL);
2559         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2560         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2561         gtk_widget_show(hbox);
2562         label = gtk_label_new(_("Separator shown only if both fields are non-null:\n%formatted.ShutterSpeed%|%formatted.ISOSpeedRating%"));
2563         gq_gtk_box_pack_start(GTK_BOX(hbox),label, FALSE,FALSE,0);
2564         gtk_widget_show(label);
2565         pref_spacer(group,TRUE);
2566
2567         group = pref_group_new(vbox, FALSE, _("Field maximum length"), GTK_ORIENTATION_VERTICAL);
2568         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2569         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2570         gtk_widget_show(hbox);
2571         label = gtk_label_new(_("%path:39%"));
2572         gq_gtk_box_pack_start(GTK_BOX(hbox),label, FALSE,FALSE,0);
2573         gtk_widget_show(label);
2574         pref_spacer(group,TRUE);
2575
2576         group = pref_group_new(vbox, FALSE, _("Pre- and post- text"), GTK_ORIENTATION_VERTICAL);
2577         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2578         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2579         gtk_widget_show(hbox);
2580         label = gtk_label_new(_("Text shown only if the field is non-null:\n%formatted.Aperture:F no. * setting%\n %formatted.Aperture:10:F no. * setting%"));
2581         gq_gtk_box_pack_start(GTK_BOX(hbox),label, FALSE,FALSE,0);
2582         gtk_widget_show(label);
2583         pref_spacer(group,TRUE);
2584
2585         group = pref_group_new(vbox, FALSE, _("Pango markup"), GTK_ORIENTATION_VERTICAL);
2586         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2587         gq_gtk_box_pack_start(GTK_BOX(group), hbox, FALSE, FALSE, 0);
2588         gtk_widget_show(hbox);
2589         label = gtk_label_new(_("<b>bold</b>\n<u>underline</u>\n<i>italic</i>\n<s>strikethrough</s>"));
2590         gq_gtk_box_pack_start(GTK_BOX(hbox),label, FALSE,FALSE,0);
2591         gtk_widget_show(label);
2592 }
2593
2594 static GtkTreeModel *create_class_model()
2595 {
2596         GtkListStore *model;
2597         GtkTreeIter iter;
2598         gint i;
2599
2600         /* create list store */
2601         model = gtk_list_store_new(1, G_TYPE_STRING);
2602         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
2603                 {
2604                 gtk_list_store_append(model, &iter);
2605                 gtk_list_store_set(model, &iter, 0, _(format_class_list[i]), -1);
2606                 }
2607         return GTK_TREE_MODEL (model);
2608 }
2609
2610
2611 /* filtering tab */
2612 static gint filter_table_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
2613 {
2614         gint n = GPOINTER_TO_INT(data);
2615         gint ret = 0;
2616         FilterEntry *filter_a;
2617         FilterEntry *filter_b;
2618
2619         gtk_tree_model_get(model, a, 0, &filter_a, -1);
2620         gtk_tree_model_get(model, b, 0, &filter_b, -1);
2621
2622         switch (n)
2623                 {
2624                 case FILETYPES_COLUMN_ENABLED:
2625                         {
2626                         ret = filter_a->enabled - filter_b->enabled;
2627                         break;
2628                         }
2629                 case FILETYPES_COLUMN_FILTER:
2630                         {
2631                         ret = g_utf8_collate(filter_a->extensions, filter_b->extensions);
2632                         break;
2633                         }
2634                 case FILETYPES_COLUMN_DESCRIPTION:
2635                         {
2636                         ret = g_utf8_collate(filter_a->description, filter_b->description);
2637                         break;
2638                         }
2639                 case FILETYPES_COLUMN_CLASS:
2640                         {
2641                         ret = g_strcmp0(format_class_list[filter_a->file_class], format_class_list[filter_b->file_class]);
2642                         break;
2643                         }
2644                 case FILETYPES_COLUMN_WRITABLE:
2645                         {
2646                         ret = filter_a->writable - filter_b->writable;
2647                         break;
2648                         }
2649                 case FILETYPES_COLUMN_SIDECAR:
2650                         {
2651                         ret = filter_a->allow_sidecar - filter_b->allow_sidecar;
2652                         break;
2653                         }
2654                 default:
2655                         g_return_val_if_reached(0);
2656                 }
2657
2658         return ret;
2659 }
2660
2661 static gboolean search_function_cb(GtkTreeModel *model, gint, const gchar *key, GtkTreeIter *iter, gpointer)
2662 {
2663         FilterEntry *fe;
2664         gboolean ret = TRUE;
2665
2666         gtk_tree_model_get(model, iter, 0, &fe, -1);
2667
2668         if (g_strstr_len(fe->extensions, -1, key))
2669                 {
2670                 ret = FALSE;
2671                 }
2672
2673         return ret;
2674 }
2675
2676 static void config_tab_files(GtkWidget *notebook)
2677 {
2678         GtkWidget *hbox;
2679         GtkWidget *frame;
2680         GtkWidget *vbox;
2681         GtkWidget *group;
2682         GtkWidget *button;
2683         GtkWidget *ct_button;
2684         GtkWidget *scrolled;
2685         GtkWidget *filter_view;
2686         GtkCellRenderer *renderer;
2687         GtkTreeSelection *selection;
2688         GtkTreeViewColumn *column;
2689
2690         vbox = scrolled_notebook_page(notebook, _("File Filters"));
2691
2692         group = pref_box_new(vbox, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2693
2694         pref_checkbox_new_int(group, _("Show hidden files or folders"),
2695                               options->file_filter.show_hidden_files, &c_options->file_filter.show_hidden_files);
2696         pref_checkbox_new_int(group, _("Show parent folder (..)"),
2697                               options->file_filter.show_parent_directory, &c_options->file_filter.show_parent_directory);
2698         pref_checkbox_new_int(group, _("Case sensitive sort (Search and Collection windows, and tab completion)"), options->file_sort.case_sensitive, &c_options->file_sort.case_sensitive);
2699         pref_checkbox_new_int(group, _("Disable file extension checks"),
2700                               options->file_filter.disable_file_extension_checks, &c_options->file_filter.disable_file_extension_checks);
2701
2702         ct_button = pref_checkbox_new_int(group, _("Disable File Filtering"),
2703                                           options->file_filter.disable, &c_options->file_filter.disable);
2704
2705
2706         group = pref_group_new(vbox, FALSE, _("Grouping sidecar extensions"), GTK_ORIENTATION_VERTICAL);
2707
2708         sidecar_ext_entry = gtk_entry_new();
2709         gq_gtk_entry_set_text(GTK_ENTRY(sidecar_ext_entry), options->sidecar.ext);
2710         gq_gtk_box_pack_start(GTK_BOX(group), sidecar_ext_entry, FALSE, FALSE, 0);
2711         gtk_widget_show(sidecar_ext_entry);
2712
2713         group = pref_group_new(vbox, TRUE, _("File types"), GTK_ORIENTATION_VERTICAL);
2714
2715         frame = pref_group_parent(group);
2716         g_signal_connect(G_OBJECT(ct_button), "toggled",
2717                          G_CALLBACK(filter_disable_cb), frame);
2718         gtk_widget_set_sensitive(frame, !options->file_filter.disable);
2719
2720         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
2721         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
2722         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
2723         gq_gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 0);
2724         gtk_widget_show(scrolled);
2725
2726         filter_store = gtk_list_store_new(1, G_TYPE_POINTER);
2727         filter_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_store));
2728         g_object_unref(filter_store);
2729         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_view));
2730         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE);
2731
2732         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(filter_view), FALSE);
2733
2734         column = gtk_tree_view_column_new();
2735         gtk_tree_view_column_set_title(column, _("Enabled"));
2736         gtk_tree_view_column_set_resizable(column, TRUE);
2737
2738         renderer = gtk_cell_renderer_toggle_new();
2739         g_signal_connect(G_OBJECT(renderer), "toggled",
2740                          G_CALLBACK(filter_store_enable_cb), filter_store);
2741         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2742         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2743                                                 GINT_TO_POINTER(FE_ENABLE), nullptr);
2744         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_ENABLED, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_ENABLED), nullptr);
2745         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_ENABLED);
2746         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2747
2748         column = gtk_tree_view_column_new();
2749         gtk_tree_view_column_set_title(column, _("Filter"));
2750         gtk_tree_view_column_set_resizable(column, TRUE);
2751         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_FILTER, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_FILTER), nullptr);
2752         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_FILTER);
2753
2754         renderer = gtk_cell_renderer_text_new();
2755         g_signal_connect(G_OBJECT(renderer), "edited",
2756                          G_CALLBACK(filter_store_ext_edit_cb), filter_store);
2757         gtk_tree_view_column_pack_start(column, renderer, TRUE);
2758         g_object_set(G_OBJECT(renderer), "editable", static_cast<gboolean>TRUE, NULL);
2759         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2760                                                 GINT_TO_POINTER(FE_EXTENSION), nullptr);
2761         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2762
2763         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(filter_view), TRUE);
2764         gtk_tree_view_set_search_column(GTK_TREE_VIEW(filter_view), FILETYPES_COLUMN_FILTER);
2765         gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(filter_view), search_function_cb, nullptr, nullptr);
2766
2767         column = gtk_tree_view_column_new();
2768         gtk_tree_view_column_set_title(column, _("Description"));
2769         gtk_tree_view_column_set_resizable(column, TRUE);
2770         gtk_tree_view_column_set_fixed_width(column, 200);
2771         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2772
2773         renderer = gtk_cell_renderer_text_new();
2774         g_signal_connect(G_OBJECT(renderer), "edited",
2775                          G_CALLBACK(filter_store_desc_edit_cb), filter_store);
2776         g_object_set(G_OBJECT(renderer), "editable", static_cast<gboolean>TRUE, NULL);
2777         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2778         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2779                                                 GINT_TO_POINTER(FE_DESCRIPTION), nullptr);
2780         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2781         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_DESCRIPTION, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_DESCRIPTION), nullptr);
2782         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_DESCRIPTION);
2783
2784         column = gtk_tree_view_column_new();
2785         gtk_tree_view_column_set_title(column, _("Class"));
2786         gtk_tree_view_column_set_resizable(column, TRUE);
2787         renderer = gtk_cell_renderer_combo_new();
2788         g_object_set(G_OBJECT(renderer), "editable", static_cast<gboolean>TRUE,
2789                                          "model", create_class_model(),
2790                                          "text-column", 0,
2791                                          "has-entry", FALSE,
2792                                          NULL);
2793
2794         g_signal_connect(G_OBJECT(renderer), "edited",
2795                          G_CALLBACK(filter_store_class_edit_cb), filter_store);
2796         gtk_tree_view_column_pack_start(column, renderer, TRUE);
2797         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2798                                                 GINT_TO_POINTER(FE_CLASS), nullptr);
2799         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2800         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_CLASS, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_CLASS), nullptr);
2801         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_CLASS);
2802
2803         column = gtk_tree_view_column_new();
2804         gtk_tree_view_column_set_title(column, _("Writable"));
2805         gtk_tree_view_column_set_resizable(column, FALSE);
2806         renderer = gtk_cell_renderer_toggle_new();
2807         g_signal_connect(G_OBJECT(renderer), "toggled",
2808                          G_CALLBACK(filter_store_writable_cb), filter_store);
2809         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2810         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2811                                                 GINT_TO_POINTER(FE_WRITABLE), nullptr);
2812         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2813         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_WRITABLE, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_WRITABLE), nullptr);
2814         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_WRITABLE);
2815
2816         column = gtk_tree_view_column_new();
2817         gtk_tree_view_column_set_title(column, _("Sidecar is allowed"));
2818         gtk_tree_view_column_set_resizable(column, FALSE);
2819         renderer = gtk_cell_renderer_toggle_new();
2820         g_signal_connect(G_OBJECT(renderer), "toggled",
2821                          G_CALLBACK(filter_store_sidecar_cb), filter_store);
2822         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2823         gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
2824                                                 GINT_TO_POINTER(FE_ALLOW_SIDECAR), nullptr);
2825         gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
2826         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_SIDECAR, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_SIDECAR), nullptr);
2827         gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_SIDECAR);
2828
2829         filter_store_populate();
2830         gq_gtk_container_add(GTK_WIDGET(scrolled), filter_view);
2831         gtk_widget_show(filter_view);
2832
2833         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
2834
2835         button = pref_button_new(nullptr, nullptr, _("Defaults"),
2836                                  G_CALLBACK(filter_default_cb), filter_view);
2837         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2838         gtk_widget_show(button);
2839
2840         button = pref_button_new(nullptr, GQ_ICON_REMOVE, _("Remove"),
2841                                  G_CALLBACK(filter_remove_cb), filter_view);
2842         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2843         gtk_widget_show(button);
2844
2845         button = pref_button_new(nullptr, GQ_ICON_ADD, _("Add"),
2846                                  G_CALLBACK(filter_add_cb), filter_view);
2847         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
2848         gtk_widget_show(button);
2849 }
2850
2851 /* metadata tab */
2852 static void config_tab_metadata(GtkWidget *notebook)
2853 {
2854         GtkWidget *vbox;
2855         GtkWidget *hbox;
2856         GtkWidget *group;
2857         GtkWidget *ct_button;
2858         GtkWidget *label;
2859         GtkWidget *tmp_widget;
2860         char *markup;
2861         GtkWidget *text_label;
2862
2863         vbox = scrolled_notebook_page(notebook, _("Metadata"));
2864
2865
2866         group = pref_group_new(vbox, FALSE, _("Metadata writing sequence"), GTK_ORIENTATION_VERTICAL);
2867 #ifndef HAVE_EXIV2
2868         label = pref_label_new(group, _("Warning: Geeqie is built without Exiv2. Some options are disabled."));
2869 #endif
2870         label = pref_label_new(group, _("When writing metadata, Geeqie will follow these steps, if selected. This process will stop when the first successful write occurs."));
2871         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
2872         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
2873
2874         gtk_widget_set_tooltip_text(label, _("A flowchart of the sequence is shown in the Help file"));
2875
2876         ct_button = pref_checkbox_new_int(group, "", options->metadata.save_in_image_file, &c_options->metadata.save_in_image_file);
2877         text_label = gtk_bin_get_child(GTK_BIN(ct_button));
2878         markup = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>%s", _("Step 1"), _(") Save metadata in either the image file or the sidecar file, according to the XMP standard"));
2879         gtk_label_set_markup (GTK_LABEL(text_label), markup);
2880         g_free(markup);
2881         markup = g_markup_printf_escaped ("%s<span style=\"italic\">%s</span>%s<span style=\"italic\">%s</span>%s", _("The destination is dependent on the settings in the "), _("Writable"), _(" and "), _("Sidecar Is Allowed"), _(" columns of the File Filters tab)"));
2882         gtk_widget_set_tooltip_markup(ct_button, markup);
2883         g_free(markup);
2884
2885 #ifndef HAVE_EXIV2
2886         gtk_widget_set_sensitive(ct_button, FALSE);
2887 #endif
2888
2889         tmp_widget = pref_checkbox_new_int(group, "", options->metadata.enable_metadata_dirs, &c_options->metadata.enable_metadata_dirs);
2890         text_label = gtk_bin_get_child(GTK_BIN(tmp_widget));
2891         markup = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>%s<span style=\"italic\">%s</span>%s", _("Step 2"), _(") Save metadata in the folder "),".metadata,", _(" local to the image folder (non-standard)"));
2892         gtk_label_set_markup (GTK_LABEL(text_label), markup);
2893         g_free(markup);
2894
2895         label = pref_label_new(group, "");
2896         markup = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>%s<span style=\"italic\">%s</span>%s", _("Step 3"), _(") Save metadata in Geeqie private directory "), get_metadata_cache_dir(), "/");
2897         gtk_label_set_markup (GTK_LABEL(label), markup);
2898         g_free(markup);
2899
2900         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
2901         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
2902         gtk_widget_set_margin_start(label, 22);
2903         pref_spacer(group, PREF_PAD_GROUP);
2904
2905         group = pref_group_new(vbox, FALSE, _("Step 1 Options:"), GTK_ORIENTATION_VERTICAL);
2906 #ifndef HAVE_EXIV2
2907         gtk_widget_set_sensitive(group, FALSE);
2908 #endif
2909
2910         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_SPACE);
2911         pref_checkbox_link_sensitivity(ct_button, hbox);
2912
2913         tmp_widget=     pref_checkbox_new_int(hbox, _("Store metadata also in IPTC tags (converted according to the IPTC4XMP standard)"), options->metadata.save_legacy_IPTC, &c_options->metadata.save_legacy_IPTC);
2914         gtk_widget_set_tooltip_text(tmp_widget, _("A simplified conversion list is in the Help file"));
2915
2916         pref_checkbox_new_int(hbox, _("Warn if the image or sidecar file is not writable"), options->metadata.warn_on_write_problems, &c_options->metadata.warn_on_write_problems);
2917
2918         pref_checkbox_new_int(hbox, _("Ask before writing to image files"), options->metadata.confirm_write, &c_options->metadata.confirm_write);
2919
2920         tmp_widget=     pref_checkbox_new_int(hbox, "", options->metadata.sidecar_extended_name, &c_options->metadata.sidecar_extended_name);
2921         gtk_widget_set_tooltip_text(tmp_widget, _("This file naming convention is used by Darktable"));
2922         text_label = gtk_bin_get_child(GTK_BIN(tmp_widget));
2923         markup = g_markup_printf_escaped ("%s<span style=\"italic\">%s</span>%s<span style=\"italic\">%s</span>%s", _("Create sidecar files named "), "image.ext.xmp", _(" (as opposed to the normal "), "image.xmp", ")");
2924         gtk_label_set_markup (GTK_LABEL(text_label), markup);
2925         g_free(markup);
2926
2927         pref_spacer(group, PREF_PAD_GROUP);
2928
2929         group = pref_group_new(vbox, FALSE, _("Steps 2 and 3 Option:"), GTK_ORIENTATION_VERTICAL);
2930 #ifndef HAVE_EXIV2
2931         gtk_widget_set_sensitive(group, FALSE);
2932 #endif
2933
2934         pref_checkbox_new_int(group, _("Use GQview legacy metadata format instead of XMP (supports only Keywords and Comments)"), options->metadata.save_legacy_format, &c_options->metadata.save_legacy_format);
2935
2936         pref_spacer(group, PREF_PAD_GROUP);
2937
2938         group = pref_group_new(vbox, FALSE, _("Miscellaneous"), GTK_ORIENTATION_VERTICAL);
2939         tmp_widget = pref_checkbox_new_int(group, _("Write the same description tags to all grouped sidecars"), options->metadata.sync_grouped_files, &c_options->metadata.sync_grouped_files);
2940         gtk_widget_set_tooltip_text(tmp_widget, _("See the Help file for a list of the tags used"));
2941
2942         tmp_widget = pref_checkbox_new_int(group, _("Permit Keywords to be case-sensitive"), options->metadata.keywords_case_sensitive, &c_options->metadata.keywords_case_sensitive);
2943         gtk_widget_set_tooltip_text(tmp_widget, _("When selected, \"Place\" and \"place\" are two different keywords"));
2944
2945         ct_button = pref_checkbox_new_int(group, _("Write altered image orientation to the metadata"), options->metadata.write_orientation, &c_options->metadata.write_orientation);
2946         gtk_widget_set_tooltip_text(ct_button, _("If checked, the results of orientation commands (Rotate, Mirror and Flip) issued on an image will be written to metadata\nNote: If this option is not checked, the results of orientation commands will be lost when Geeqie closes"));
2947
2948 #ifndef HAVE_EXIV2
2949         gtk_widget_set_sensitive(ct_button, FALSE);
2950 #endif
2951
2952         pref_spacer(group, PREF_PAD_GROUP);
2953
2954         group = pref_group_new(vbox, FALSE, _("Auto-save options"), GTK_ORIENTATION_VERTICAL);
2955
2956         ct_button = pref_checkbox_new_int(group, _("Write metadata after timeout"), options->metadata.confirm_after_timeout, &c_options->metadata.confirm_after_timeout);
2957
2958         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2959         pref_checkbox_link_sensitivity(ct_button, hbox);
2960
2961         pref_spin_new_int(hbox, _("Timeout (seconds):"), nullptr, 0, 900, 1, options->metadata.confirm_timeout, &c_options->metadata.confirm_timeout);
2962
2963         pref_checkbox_new_int(group, _("Write metadata on image change"), options->metadata.confirm_on_image_change, &c_options->metadata.confirm_on_image_change);
2964
2965         pref_checkbox_new_int(group, _("Write metadata on directory change"), options->metadata.confirm_on_dir_change, &c_options->metadata.confirm_on_dir_change);
2966
2967         pref_spacer(group, PREF_PAD_GROUP);
2968
2969 #ifdef HAVE_SPELL
2970         group = pref_group_new(vbox, FALSE, _("Spelling checks"), GTK_ORIENTATION_VERTICAL);
2971
2972         ct_button = pref_checkbox_new_int(group, _("Check spelling - Requires restart"), options->metadata.check_spelling, &c_options->metadata.check_spelling);
2973         gtk_widget_set_tooltip_text(ct_button, _("Spelling checks are performed on info sidebar panes Comment, Headline and Title"));
2974 #endif
2975
2976         pref_spacer(group, PREF_PAD_GROUP);
2977
2978         group = pref_group_new(vbox, FALSE, _("Pre-load metadata"), GTK_ORIENTATION_VERTICAL);
2979
2980         ct_button = pref_checkbox_new_int(group, _("Read metadata in background"), options->read_metadata_in_idle, &c_options->read_metadata_in_idle);
2981         gtk_widget_set_tooltip_text(ct_button,"On folder change, read DateTimeOriginal, DateTimeDigitized and Star Rating in the idle loop.\nIf this is not selected, initial loading of the folder will be faster but sorting on these items will be slower");
2982 }
2983
2984 /* keywords tab */
2985
2986 struct KeywordFindData
2987 {
2988         GenericDialog *gd;
2989
2990         GList *list;
2991         GList *list_dir;
2992
2993         GtkWidget *button_close;
2994         GtkWidget *button_stop;
2995         GtkWidget *button_start;
2996         GtkWidget *progress;
2997         GtkWidget *spinner;
2998
2999         GtkWidget *group;
3000         GtkWidget *entry;
3001
3002         gboolean recurse;
3003
3004         guint idle_id; /* event source id */
3005 };
3006
3007 enum {
3008         KEYWORD_DIALOG_WIDTH = 400
3009 };
3010
3011 static void keywords_find_folder(KeywordFindData *kfd, FileData *dir_fd)
3012 {
3013         GList *list_d = nullptr;
3014         GList *list_f = nullptr;
3015
3016         if (kfd->recurse)
3017                 {
3018                 filelist_read(dir_fd, &list_f, &list_d);
3019                 }
3020         else
3021                 {
3022                 filelist_read(dir_fd, &list_f, nullptr);
3023                 }
3024
3025         list_f = filelist_filter(list_f, FALSE);
3026         list_d = filelist_filter(list_d, TRUE);
3027
3028         kfd->list = g_list_concat(list_f, kfd->list);
3029         kfd->list_dir = g_list_concat(list_d, kfd->list_dir);
3030 }
3031
3032 static void keywords_find_reset(KeywordFindData *kfd)
3033 {
3034         filelist_free(kfd->list);
3035         kfd->list = nullptr;
3036
3037         filelist_free(kfd->list_dir);
3038         kfd->list_dir = nullptr;
3039 }
3040
3041 static void keywords_find_close_cb(GenericDialog *, gpointer data)
3042 {
3043         auto kfd = static_cast<KeywordFindData *>(data);
3044
3045         if (!gtk_widget_get_sensitive(kfd->button_close)) return;
3046
3047         keywords_find_reset(kfd);
3048         generic_dialog_close(kfd->gd);
3049         g_free(kfd);
3050 }
3051
3052 static void keywords_find_finish(KeywordFindData *kfd)
3053 {
3054         keywords_find_reset(kfd);
3055
3056         gq_gtk_entry_set_text(GTK_ENTRY(kfd->progress), _("done"));
3057         gtk_spinner_stop(GTK_SPINNER(kfd->spinner));
3058
3059         gtk_widget_set_sensitive(kfd->group, TRUE);
3060         gtk_widget_set_sensitive(kfd->button_start, TRUE);
3061         gtk_widget_set_sensitive(kfd->button_stop, FALSE);
3062         gtk_widget_set_sensitive(kfd->button_close, TRUE);
3063 }
3064
3065 static void keywords_find_stop_cb(GenericDialog *, gpointer data)
3066 {
3067         auto kfd = static_cast<KeywordFindData *>(data);
3068
3069         g_idle_remove_by_data(kfd);
3070
3071         keywords_find_finish(kfd);
3072 }
3073
3074 static gboolean keywords_find_file(gpointer data)
3075 {
3076         auto kfd = static_cast<KeywordFindData *>(data);
3077         GtkTextIter iter;
3078         GtkTextBuffer *buffer;
3079         gchar *tmp;
3080         GList *keywords;
3081
3082         if (kfd->list)
3083                 {
3084                 FileData *fd;
3085
3086                 fd = static_cast<FileData *>(kfd->list->data);
3087                 kfd->list = g_list_remove(kfd->list, fd);
3088
3089                 keywords = metadata_read_list(fd, KEYWORD_KEY, METADATA_PLAIN);
3090                 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text));
3091
3092                 while (keywords)
3093                         {
3094                         gtk_text_buffer_get_end_iter(buffer, &iter);
3095                         tmp = g_strconcat(static_cast<const gchar *>(keywords->data), "\n", NULL);
3096                         gtk_text_buffer_insert(buffer, &iter, tmp, -1);
3097                         g_free(tmp);
3098                         keywords = keywords->next;
3099                         }
3100
3101                 gq_gtk_entry_set_text(GTK_ENTRY(kfd->progress), fd->path);
3102                 file_data_unref(fd);
3103                 g_list_free_full(keywords, g_free);
3104
3105                 return (G_SOURCE_CONTINUE);
3106                 }
3107
3108         if (kfd->list_dir)
3109                 {
3110                 FileData *fd;
3111
3112                 fd = static_cast<FileData *>(kfd->list_dir->data);
3113                 kfd->list_dir = g_list_remove(kfd->list_dir, fd);
3114
3115                 keywords_find_folder(kfd, fd);
3116
3117                 file_data_unref(fd);
3118
3119                 return G_SOURCE_CONTINUE;
3120                 }
3121
3122         keywords_find_finish(kfd);
3123
3124         return G_SOURCE_REMOVE;
3125 }
3126
3127 static void keywords_find_start_cb(GenericDialog *, gpointer data)
3128 {
3129         auto kfd = static_cast<KeywordFindData *>(data);
3130         gchar *path;
3131
3132         if (kfd->list || !gtk_widget_get_sensitive(kfd->button_start)) return;
3133
3134         path = remove_trailing_slash((gq_gtk_entry_get_text(GTK_ENTRY(kfd->entry))));
3135         parse_out_relatives(path);
3136
3137         if (!isdir(path))
3138                 {
3139                 warning_dialog(_("Invalid folder"),
3140                                 _("The specified folder can not be found."),
3141                                 GQ_ICON_DIALOG_WARNING, kfd->gd->dialog);
3142                 }
3143         else
3144                 {
3145                 FileData *dir_fd;
3146
3147                 gtk_widget_set_sensitive(kfd->group, FALSE);
3148                 gtk_widget_set_sensitive(kfd->button_start, FALSE);
3149                 gtk_widget_set_sensitive(kfd->button_stop, TRUE);
3150                 gtk_widget_set_sensitive(kfd->button_close, FALSE);
3151                 gtk_spinner_start(GTK_SPINNER(kfd->spinner));
3152
3153                 dir_fd = file_data_new_dir(path);
3154                 keywords_find_folder(kfd, dir_fd);
3155                 file_data_unref(dir_fd);
3156                 kfd->idle_id = g_idle_add(keywords_find_file, kfd);
3157                 }
3158
3159         g_free(path);
3160 }
3161
3162 static void keywords_find_dialog(GtkWidget *widget, const gchar *path)
3163 {
3164         KeywordFindData *kfd;
3165         GtkWidget *hbox;
3166         GtkWidget *label;
3167
3168         kfd = g_new0(KeywordFindData, 1);
3169
3170         kfd->gd = generic_dialog_new(_("Search for keywords"),
3171                                                                         "search_for_keywords",
3172                                                                         widget, FALSE,
3173                                                                         nullptr, kfd);
3174         gtk_window_set_default_size(GTK_WINDOW(kfd->gd->dialog), KEYWORD_DIALOG_WIDTH, -1);
3175         kfd->gd->cancel_cb = keywords_find_close_cb;
3176         kfd->button_close = generic_dialog_add_button(kfd->gd, GQ_ICON_CLOSE, _("Close"),
3177                                                      keywords_find_close_cb, FALSE);
3178         kfd->button_start = generic_dialog_add_button(kfd->gd, GQ_ICON_OK, _("S_tart"),
3179                                                      keywords_find_start_cb, FALSE);
3180         kfd->button_stop = generic_dialog_add_button(kfd->gd, GQ_ICON_STOP, _("Stop"),
3181                                                     keywords_find_stop_cb, FALSE);
3182         gtk_widget_set_sensitive(kfd->button_stop, FALSE);
3183
3184         generic_dialog_add_message(kfd->gd, nullptr, _("Search for keywords"), nullptr, FALSE);
3185
3186         hbox = pref_box_new(kfd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3187         pref_spacer(hbox, PREF_PAD_INDENT);
3188         kfd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
3189
3190         hbox = pref_box_new(kfd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3191         pref_label_new(hbox, _("Folder:"));
3192
3193         label = tab_completion_new(&kfd->entry, path, nullptr, nullptr, nullptr, nullptr);
3194         tab_completion_add_select_button(kfd->entry,_("Select folder") , TRUE);
3195         gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
3196         gtk_widget_show(label);
3197
3198         pref_checkbox_new_int(kfd->group, _("Include subfolders"), FALSE, &kfd->recurse);
3199
3200         pref_line(kfd->gd->vbox, PREF_PAD_SPACE);
3201         hbox = pref_box_new(kfd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3202
3203         kfd->progress = gtk_entry_new();
3204         gtk_widget_set_can_focus(kfd->progress, FALSE);
3205         gtk_editable_set_editable(GTK_EDITABLE(kfd->progress), FALSE);
3206         gq_gtk_entry_set_text(GTK_ENTRY(kfd->progress), _("click start to begin"));
3207         gq_gtk_box_pack_start(GTK_BOX(hbox), kfd->progress, TRUE, TRUE, 0);
3208         gtk_widget_show(kfd->progress);
3209
3210         kfd->spinner = gtk_spinner_new();
3211         gq_gtk_box_pack_start(GTK_BOX(hbox), kfd->spinner, FALSE, FALSE, 0);
3212         gtk_widget_show(kfd->spinner);
3213
3214         kfd->list = nullptr;
3215
3216         gtk_widget_show(kfd->gd->dialog);
3217 }
3218
3219 static void keywords_find_cb(GtkWidget *widget, gpointer)
3220 {
3221         const gchar *path = layout_get_path(nullptr);
3222
3223         if (!path || !*path) path = homedir();
3224         keywords_find_dialog(widget, path);
3225 }
3226
3227 static void config_tab_keywords_save()
3228 {
3229         GtkTextIter start, end;
3230         GtkTextBuffer *buffer;
3231         GList *kw_list = nullptr;
3232         GList *work;
3233         gchar *buffer_text;
3234         gchar *kw_split;
3235         gboolean found;
3236
3237         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text));
3238         gtk_text_buffer_get_bounds(buffer, &start, &end);
3239
3240         buffer_text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
3241
3242         kw_split = strtok(buffer_text, "\n");
3243         while (kw_split != nullptr)
3244                 {
3245                 work = kw_list;
3246                 found = FALSE;
3247                 while (work)
3248                         {
3249                         if (g_strcmp0(static_cast<const gchar *>(work->data), kw_split) == 0)
3250                                 {
3251                                 found = TRUE;
3252                                 break;
3253                                 }
3254                         work = work->next;
3255                         }
3256                 if (!found)
3257                         {
3258                         kw_list = g_list_append(kw_list, g_strdup(kw_split));
3259                         }
3260                 kw_split = strtok(nullptr, "\n");
3261                 }
3262
3263         keyword_list_set(kw_list);
3264
3265         g_list_free_full(kw_list, g_free);
3266         g_free(buffer_text);
3267 }
3268
3269 static void config_tab_keywords(GtkWidget *notebook)
3270 {
3271         GtkWidget *hbox;
3272         GtkWidget *vbox;
3273         GtkWidget *group;
3274         GtkWidget *button;
3275         GtkWidget *scrolled;
3276         GtkTextIter iter;
3277         GtkTextBuffer *buffer;
3278         gchar *tmp;
3279 #ifdef HAVE_SPELL
3280         GspellTextView *gspell_view;
3281 #endif
3282
3283         vbox = scrolled_notebook_page(notebook, _("Keywords"));
3284
3285         group = pref_group_new(vbox, TRUE, _("Edit keywords autocompletion list"), GTK_ORIENTATION_VERTICAL);
3286
3287         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
3288
3289         button = pref_button_new(hbox, GQ_ICON_RUN, _("Search"),
3290                                    G_CALLBACK(keywords_find_cb), keyword_text);
3291         gtk_widget_set_tooltip_text(button, "Search for existing keywords");
3292
3293
3294         keyword_text = gtk_text_view_new();
3295         gtk_widget_set_size_request(keyword_text, 20, 20);
3296         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
3297         gq_gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 0);
3298         gtk_widget_show(scrolled);
3299
3300 #ifdef HAVE_SPELL
3301         if (options->metadata.check_spelling)
3302                 {
3303                 gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(keyword_text));
3304                 gspell_text_view_basic_setup(gspell_view);
3305                 }
3306 #endif
3307
3308         gq_gtk_container_add(GTK_WIDGET(scrolled), keyword_text);
3309         gtk_widget_show(keyword_text);
3310
3311         gtk_text_view_set_editable(GTK_TEXT_VIEW(keyword_text), TRUE);
3312
3313         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(keyword_text));
3314         gtk_text_buffer_create_tag(buffer, "monospace",
3315                                 "family", "monospace", NULL);
3316
3317         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(keyword_text), GTK_WRAP_WORD);
3318         gtk_text_buffer_get_start_iter(buffer, &iter);
3319         gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);
3320         gchar *path;
3321
3322         path = g_build_filename(get_rc_dir(), "keywords", NULL);
3323
3324         GList *kwl = keyword_list_get();
3325         kwl = g_list_first(kwl);
3326         while (kwl)
3327         {
3328                 gtk_text_buffer_get_end_iter (buffer, &iter);
3329             tmp = g_strconcat(static_cast<const gchar *>(kwl->data), "\n", NULL);
3330                 gtk_text_buffer_insert(buffer, &iter, tmp, -1);
3331                 kwl = kwl->next;
3332                 g_free(tmp);
3333         }
3334
3335         gtk_text_buffer_set_modified(buffer, FALSE);
3336
3337         g_free(path);
3338 }
3339
3340 /* metadata tab */
3341 #ifdef HAVE_LCMS
3342 static void intent_menu_cb(GtkWidget *combo, gpointer data)
3343 {
3344         auto option = static_cast<gint *>(data);
3345
3346         switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
3347                 {
3348                 case 0:
3349                 default:
3350                         *option = INTENT_PERCEPTUAL;
3351                         break;
3352                 case 1:
3353                         *option = INTENT_RELATIVE_COLORIMETRIC;
3354                         break;
3355                 case 2:
3356                         *option = INTENT_SATURATION;
3357                         break;
3358                 case 3:
3359                         *option = INTENT_ABSOLUTE_COLORIMETRIC;
3360                         break;
3361                 }
3362 }
3363
3364 static void add_intent_menu(GtkWidget *table, gint column, gint row, const gchar *text,
3365                              gint option, gint *option_c)
3366 {
3367         GtkWidget *combo;
3368         gint current = 0;
3369
3370         *option_c = option;
3371
3372         pref_table_label(table, column, row, text, GTK_ALIGN_START);
3373
3374         combo = gtk_combo_box_text_new();
3375
3376         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Perceptual"));
3377         if (option == INTENT_PERCEPTUAL) current = 0;
3378         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Relative Colorimetric"));
3379         if (option == INTENT_RELATIVE_COLORIMETRIC) current = 1;
3380         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Saturation"));
3381         if (option == INTENT_SATURATION) current = 2;
3382         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Absolute Colorimetric"));
3383         if (option == INTENT_ABSOLUTE_COLORIMETRIC) current = 3;
3384
3385         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
3386
3387         gtk_widget_set_tooltip_text(combo,"Refer to the lcms documentation for the defaults used when the selected Intent is not available");
3388
3389         g_signal_connect(G_OBJECT(combo), "changed",
3390                          G_CALLBACK(intent_menu_cb), option_c);
3391
3392         gq_gtk_grid_attach(GTK_GRID(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, static_cast<GtkAttachOptions>(0), 0, 0);
3393         gtk_widget_show(combo);
3394 }
3395 #endif
3396
3397 static void config_tab_color(GtkWidget *notebook)
3398 {
3399         GtkWidget *label;
3400         GtkWidget *vbox;
3401         GtkWidget *group;
3402         GtkWidget *tabcomp;
3403         GtkWidget *table;
3404         gint i;
3405
3406         vbox = scrolled_notebook_page(notebook, _("Color management"));
3407
3408         group =  pref_group_new(vbox, FALSE, _("Input profiles"), GTK_ORIENTATION_VERTICAL);
3409 #ifndef HAVE_LCMS
3410         gtk_widget_set_sensitive(pref_group_parent(group), FALSE);
3411 #endif
3412
3413         table = pref_table_new(group, 3, COLOR_PROFILE_INPUTS + 1, FALSE, FALSE);
3414         gtk_grid_set_column_spacing(GTK_GRID(table), PREF_PAD_GAP);
3415
3416         label = pref_table_label(table, 0, 0, _("Type"), GTK_ALIGN_START);
3417         pref_label_bold(label, TRUE, FALSE);
3418
3419         label = pref_table_label(table, 1, 0, _("Menu name"), GTK_ALIGN_START);
3420         pref_label_bold(label, TRUE, FALSE);
3421
3422         label = pref_table_label(table, 2, 0, _("File"), GTK_ALIGN_START);
3423         pref_label_bold(label, TRUE, FALSE);
3424
3425         for (i = 0; i < COLOR_PROFILE_INPUTS; i++)
3426                 {
3427                 GtkWidget *entry;
3428                 gchar *buf;
3429
3430                 buf = g_strdup_printf(_("Input %d:"), i + COLOR_PROFILE_FILE);
3431                 pref_table_label(table, 0, i + 1, buf, GTK_ALIGN_END);
3432                 g_free(buf);
3433
3434                 entry = gtk_entry_new();
3435                 gtk_entry_set_max_length(GTK_ENTRY(entry), EDITOR_NAME_MAX_LENGTH);
3436                 if (options->color_profile.input_name[i])
3437                         {
3438                         gq_gtk_entry_set_text(GTK_ENTRY(entry), options->color_profile.input_name[i]);
3439                         }
3440                 gq_gtk_grid_attach(GTK_GRID(table), entry, 1, 2, i + 1, i + 2, static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND), static_cast<GtkAttachOptions>(0), 0, 0);
3441                 gtk_widget_show(entry);
3442                 color_profile_input_name_entry[i] = entry;
3443
3444                 tabcomp = tab_completion_new(&entry, options->color_profile.input_file[i], nullptr, ".icc", "ICC Files", nullptr);
3445                 tab_completion_add_select_button(entry, _("Select color profile"), FALSE);
3446                 gtk_widget_set_size_request(entry, 160, -1);
3447                 gq_gtk_grid_attach(GTK_GRID(table), tabcomp, 2, 3, i + 1, i + 2, static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND), static_cast<GtkAttachOptions>(0), 0, 0);
3448                 gtk_widget_show(tabcomp);
3449                 color_profile_input_file_entry[i] = entry;
3450                 }
3451
3452         group =  pref_group_new(vbox, FALSE, _("Screen profile"), GTK_ORIENTATION_VERTICAL);
3453 #ifndef HAVE_LCMS
3454         gtk_widget_set_sensitive(pref_group_parent(group), FALSE);
3455 #endif
3456         pref_checkbox_new_int(group, _("Use system screen profile if available"),
3457                               options->color_profile.use_x11_screen_profile, &c_options->color_profile.use_x11_screen_profile);
3458
3459         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3460
3461         pref_table_label(table, 0, 0, _("Screen:"), GTK_ALIGN_END);
3462         tabcomp = tab_completion_new(&color_profile_screen_file_entry,
3463                                      options->color_profile.screen_file, nullptr, ".icc", "ICC Files", nullptr);
3464         tab_completion_add_select_button(color_profile_screen_file_entry, _("Select color profile"), FALSE);
3465         gtk_widget_set_size_request(color_profile_screen_file_entry, 160, -1);
3466 #ifdef HAVE_LCMS
3467         add_intent_menu(table, 0, 1, _("Render Intent:"), options->color_profile.render_intent, &c_options->color_profile.render_intent);
3468 #endif
3469         gq_gtk_grid_attach(GTK_GRID(table), tabcomp, 1, 2, 0, 1, static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND), static_cast<GtkAttachOptions>(0), 0, 0);
3470
3471         gtk_widget_show(tabcomp);
3472 }
3473
3474 /* advanced entry tab */
3475 static void use_geeqie_trash_cb(GtkWidget *widget, gpointer)
3476 {
3477         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
3478                 {
3479                 c_options->file_ops.use_system_trash = FALSE;
3480                 c_options->file_ops.no_trash = FALSE;
3481                 }
3482 }
3483
3484 static void use_system_trash_cb(GtkWidget *widget, gpointer)
3485 {
3486         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
3487                 {
3488                 c_options->file_ops.use_system_trash = TRUE;
3489                 c_options->file_ops.no_trash = FALSE;
3490                 }
3491 }
3492
3493 static void use_no_cache_cb(GtkWidget *widget, gpointer)
3494 {
3495         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
3496                 {
3497                 c_options->file_ops.no_trash = TRUE;
3498                 }
3499 }
3500
3501 static void config_tab_behavior(GtkWidget *notebook)
3502 {
3503         GtkWidget *hbox;
3504         GtkWidget *vbox;
3505         GtkWidget *group;
3506         GtkWidget *button;
3507         GtkWidget *tabcomp;
3508         GtkWidget *ct_button;
3509         GtkWidget *spin;
3510         GtkWidget *table;
3511         GtkWidget *marks;
3512         GtkWidget *with_rename;
3513         GtkWidget *collections_on_top;
3514         GtkWidget *hide_window_in_fullscreen;
3515         GtkWidget *checkbox;
3516         GtkWidget *tmp;
3517
3518         vbox = scrolled_notebook_page(notebook, _("Behavior"));
3519
3520         group = pref_group_new(vbox, FALSE, _("Delete"), GTK_ORIENTATION_VERTICAL);
3521
3522         pref_checkbox_new_int(group, _("Confirm permanent file delete"),
3523                               options->file_ops.confirm_delete, &c_options->file_ops.confirm_delete);
3524         pref_checkbox_new_int(group, _("Confirm move file to Trash"),
3525                               options->file_ops.confirm_move_to_trash, &c_options->file_ops.confirm_move_to_trash);
3526         pref_checkbox_new_int(group, _("Enable Delete key"),
3527                               options->file_ops.enable_delete_key, &c_options->file_ops.enable_delete_key);
3528
3529         ct_button = pref_radiobutton_new(group, nullptr, _("Use Geeqie trash location"),
3530                                         !options->file_ops.use_system_trash && !options->file_ops.no_trash, G_CALLBACK(use_geeqie_trash_cb),nullptr);
3531
3532         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3533         pref_checkbox_link_sensitivity(ct_button, hbox);
3534
3535         pref_spacer(hbox, PREF_PAD_INDENT - PREF_PAD_SPACE);
3536         pref_label_new(hbox, _("Folder:"));
3537
3538         tabcomp = tab_completion_new(&safe_delete_path_entry, options->file_ops.safe_delete_path, nullptr, nullptr, nullptr, nullptr);
3539         tab_completion_add_select_button(safe_delete_path_entry, nullptr, TRUE);
3540         gq_gtk_box_pack_start(GTK_BOX(hbox), tabcomp, TRUE, TRUE, 0);
3541         gtk_widget_show(tabcomp);
3542
3543         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
3544         pref_checkbox_link_sensitivity(ct_button, hbox);
3545
3546         pref_spacer(hbox, PREF_PAD_INDENT - PREF_PAD_GAP);
3547         spin = pref_spin_new_int(hbox, _("Maximum size:"), _("MiB"),
3548                                  0, 2048, 1, options->file_ops.safe_delete_folder_maxsize, &c_options->file_ops.safe_delete_folder_maxsize);
3549         gtk_widget_set_tooltip_markup(spin, _("Set to 0 for unlimited size"));
3550         button = pref_button_new(nullptr, nullptr, _("View"),
3551                                  G_CALLBACK(safe_delete_view_cb), nullptr);
3552         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3553         gtk_widget_show(button);
3554
3555         button = pref_button_new(nullptr, GQ_ICON_CLEAR, nullptr,
3556                                  G_CALLBACK(safe_delete_clear_cb), nullptr);
3557         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3558
3559         c_options->file_ops.no_trash = options->file_ops.no_trash;
3560         c_options->file_ops.use_system_trash = options->file_ops.use_system_trash;
3561
3562         pref_radiobutton_new(group, ct_button, _("Use system Trash bin"),
3563                                         options->file_ops.use_system_trash && !options->file_ops.no_trash, G_CALLBACK(use_system_trash_cb), nullptr);
3564
3565         pref_radiobutton_new(group, ct_button, _("Use no trash at all"),
3566                         options->file_ops.no_trash, G_CALLBACK(use_no_cache_cb), nullptr);
3567
3568         gtk_widget_show(button);
3569
3570         pref_spacer(group, PREF_PAD_GROUP);
3571
3572
3573         group = pref_group_new(vbox, FALSE, _("Behavior"), GTK_ORIENTATION_VERTICAL);
3574
3575         pref_checkbox_new_int(group, _("Descend folders in tree view"),
3576                               options->tree_descend_subdirs, &c_options->tree_descend_subdirs);
3577
3578         pref_checkbox_new_int(group, _("In place renaming"),
3579                               options->file_ops.enable_in_place_rename, &c_options->file_ops.enable_in_place_rename);
3580
3581         pref_checkbox_new_int(group, _("List directory view uses single click to enter"),
3582                               options->view_dir_list_single_click_enter, &c_options->view_dir_list_single_click_enter);
3583
3584         tmp = pref_checkbox_new_int(group, _("Circular selection lists"),
3585                               options->circular_selection_lists, &c_options->circular_selection_lists);
3586         gtk_widget_set_tooltip_text(tmp, _("Traverse selection lists in a circular manner"));
3587
3588         marks = pref_checkbox_new_int(group, _("Save marks on exit"),
3589                                 options->marks_save, &c_options->marks_save);
3590         gtk_widget_set_tooltip_text(marks,"Note that marks linked to a keyword will be saved irrespective of this setting");
3591
3592         with_rename = pref_checkbox_new_int(group, _("Use \"With Rename\" as default for Copy/Move dialogs"),
3593                                 options->with_rename, &c_options->with_rename);
3594         gtk_widget_set_tooltip_text(with_rename,"Change the default button for Copy/Move dialogs");
3595
3596         collections_on_top = pref_checkbox_new_int(group, _("Permit duplicates in Collections"),
3597                                 options->collections_duplicates, &c_options->collections_duplicates);
3598         gtk_widget_set_tooltip_text(collections_on_top,"Allow the same image to be in a Collection more than once");
3599
3600         collections_on_top = pref_checkbox_new_int(group, _("Open collections on top"),
3601                                 options->collections_on_top, &c_options->collections_on_top);
3602         gtk_widget_set_tooltip_text(collections_on_top,"Open collections window on top");
3603
3604         hide_window_in_fullscreen = pref_checkbox_new_int(group, _("Hide window in fullscreen"),
3605                                 options->hide_window_in_fullscreen, &c_options->hide_window_in_fullscreen);
3606         gtk_widget_set_tooltip_text(hide_window_in_fullscreen,"When alt-tabbing, prevent Geeqie window showing twice");
3607
3608         pref_spin_new_int(group, _("Recent folder list maximum size"), nullptr,
3609                           1, 50, 1, options->open_recent_list_maxsize, &c_options->open_recent_list_maxsize);
3610
3611         tmp = pref_spin_new_int(group, _("Recent folder-image list maximum size"), nullptr, 0, 50, 1, options->recent_folder_image_list_maxsize, &c_options->recent_folder_image_list_maxsize);
3612         gtk_widget_set_tooltip_text(tmp, _("List of the last image viewed in each recent folder.\nRe-opening a folder will set focus to the last image viewed."));
3613
3614         pref_spin_new_int(group, _("Drag'n drop icon size"), nullptr,
3615                           16, 256, 16, options->dnd_icon_size, &c_options->dnd_icon_size);
3616
3617         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3618         add_dnd_default_action_selection_menu(table, 0, 0, _("Drag`n drop default action:"), options->dnd_default_action, &c_options->dnd_default_action);
3619
3620         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3621         add_clipboard_selection_menu(table, 0, 0, _("Copy path clipboard selection:"), options->clipboard_selection, &c_options->clipboard_selection);
3622
3623         pref_spacer(group, PREF_PAD_GROUP);
3624
3625         group = pref_group_new(vbox, FALSE, _("Navigation"), GTK_ORIENTATION_VERTICAL);
3626
3627         pref_checkbox_new_int(group, _("Progressive keyboard scrolling"),
3628                               options->progressive_key_scrolling, &c_options->progressive_key_scrolling);
3629         pref_spin_new_int(group, _("Keyboard scrolling step multiplier:"), nullptr,
3630                           1, 32, 1, options->keyboard_scroll_step, reinterpret_cast<int *>(&c_options->keyboard_scroll_step));
3631         pref_checkbox_new_int(group, _("Mouse wheel scrolls image"),
3632                               options->mousewheel_scrolls, &c_options->mousewheel_scrolls);
3633         pref_checkbox_new_int(group, _("Navigation by left or middle click on image"),
3634                               options->image_lm_click_nav, &c_options->image_lm_click_nav);
3635         pref_checkbox_new_int(group, _("Open archive by left click on image"),
3636                               options->image_l_click_archive, &c_options->image_l_click_archive);
3637         pref_checkbox_new_int(group, _("Play video by left click on image"),
3638                               options->image_l_click_video, &c_options->image_l_click_video);
3639         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3640         add_video_menu(table, 0, 0, _("Play with:"), options->image_l_click_video_editor, &c_options->image_l_click_video_editor);
3641
3642         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3643         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3644         add_mouse_selection_menu(table, 0, 0, _("Mouse button Back:"), options->mouse_button_8, &c_options->mouse_button_8);
3645         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3646         add_mouse_selection_menu(table, 0, 0, _("Mouse button Forward:"), options->mouse_button_9, &c_options->mouse_button_9);
3647
3648         pref_spacer(group, PREF_PAD_GROUP);
3649
3650         group = pref_group_new(vbox, FALSE, _("GPU"), GTK_ORIENTATION_VERTICAL);
3651
3652         checkbox = pref_checkbox_new_int(group, _("Override disable GPU"),
3653                                 options->override_disable_gpu, &c_options->override_disable_gpu);
3654         gtk_widget_set_tooltip_text(checkbox, "Contact the developers for usage");
3655
3656 #ifdef DEBUG
3657         pref_spacer(group, PREF_PAD_GROUP);
3658
3659         group = pref_group_new(vbox, FALSE, _("Debugging"), GTK_ORIENTATION_VERTICAL);
3660
3661         pref_spin_new_int(group, _("Debug level:"), nullptr,
3662                           DEBUG_LEVEL_MIN, DEBUG_LEVEL_MAX, 1, get_debug_level(), &debug_c);
3663
3664         pref_checkbox_new_int(group, _("Timer data"),
3665                         options->log_window.timer_data, &c_options->log_window.timer_data);
3666
3667         pref_spin_new_int(group, _("Log Window max. lines:"), nullptr,
3668                           1, 99999, 1, options->log_window_lines, &options->log_window_lines);
3669
3670         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
3671         pref_label_new(hbox, _("Log Window F1 command: "));
3672         log_window_f1_entry = gtk_entry_new();
3673         gq_gtk_entry_set_text(GTK_ENTRY(log_window_f1_entry), options->log_window.action);
3674         gq_gtk_box_pack_start(GTK_BOX(hbox), log_window_f1_entry, FALSE, FALSE, 0);
3675         gtk_entry_set_width_chars(GTK_ENTRY(log_window_f1_entry), 15);
3676         gtk_widget_show(log_window_f1_entry);
3677 #endif
3678 }
3679
3680 /* accelerators tab */
3681
3682 static gboolean accel_search_function_cb(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer)
3683 {
3684         gboolean ret = TRUE;
3685         gchar *key_nocase;
3686         gchar *text;
3687         gchar *text_nocase;
3688
3689         gtk_tree_model_get(model, iter, column, &text, -1);
3690         text_nocase = g_utf8_casefold(text, -1);
3691         key_nocase = g_utf8_casefold(key, -1);
3692
3693         if (g_strstr_len(text_nocase, -1, key_nocase))
3694                 {
3695                 ret = FALSE;
3696                 }
3697
3698         g_free(key_nocase);
3699         g_free(text);
3700         g_free(text_nocase);
3701
3702         return ret;
3703 }
3704
3705 static void accel_row_activated_cb(GtkTreeView *tree_view, GtkTreePath *, GtkTreeViewColumn *column, gpointer)
3706 {
3707         GList *list;
3708         gint col_num = 0;
3709
3710         list = gtk_tree_view_get_columns(tree_view);
3711         col_num = g_list_index(list, column);
3712
3713         g_list_free(list);
3714
3715         gtk_tree_view_set_search_column(tree_view, col_num);
3716 }
3717
3718 static void config_tab_accelerators(GtkWidget *notebook)
3719 {
3720         GtkWidget *hbox;
3721         GtkWidget *vbox;
3722         GtkWidget *group;
3723         GtkWidget *button;
3724         GtkWidget *scrolled;
3725         GtkWidget *accel_view;
3726         GtkCellRenderer *renderer;
3727         GtkTreeSelection *selection;
3728         GtkTreeViewColumn *column;
3729
3730         vbox = scrolled_notebook_page(notebook, _("Keyboard"));
3731
3732         group = pref_group_new(vbox, TRUE, _("Accelerators"), GTK_ORIENTATION_VERTICAL);
3733
3734         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
3735         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
3736         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
3737         gq_gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 0);
3738         gtk_widget_show(scrolled);
3739
3740         accel_store = gtk_tree_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
3741
3742         accel_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(accel_store));
3743         g_object_unref(accel_store);
3744         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(accel_view));
3745         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
3746
3747         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(accel_view), FALSE);
3748
3749         renderer = gtk_cell_renderer_text_new();
3750
3751         column = gtk_tree_view_column_new_with_attributes(_("Action"),
3752                                                           renderer,
3753                                                           "text", AE_ACTION,
3754                                                           NULL);
3755
3756         gtk_tree_view_column_set_sort_column_id(column, AE_ACTION);
3757         gtk_tree_view_column_set_resizable(column, TRUE);
3758         gtk_tree_view_append_column(GTK_TREE_VIEW(accel_view), column);
3759
3760
3761         renderer = gtk_cell_renderer_accel_new();
3762         g_signal_connect(G_OBJECT(renderer), "accel-cleared",
3763                          G_CALLBACK(accel_store_cleared_cb), accel_store);
3764         g_signal_connect(G_OBJECT(renderer), "accel-edited",
3765                          G_CALLBACK(accel_store_edited_cb), accel_store);
3766
3767
3768         g_object_set (renderer,
3769                       "editable", TRUE,
3770                       "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
3771                       NULL);
3772
3773         column = gtk_tree_view_column_new_with_attributes(_("KEY"),
3774                                                           renderer,
3775                                                           "text", AE_KEY,
3776                                                           NULL);
3777
3778         gtk_tree_view_column_set_sort_column_id(column, AE_KEY);
3779         gtk_tree_view_column_set_resizable(column, TRUE);
3780         gtk_tree_view_append_column(GTK_TREE_VIEW(accel_view), column);
3781
3782         renderer = gtk_cell_renderer_text_new();
3783
3784         column = gtk_tree_view_column_new_with_attributes(_("Tooltip"),
3785                                                           renderer,
3786                                                           "text", AE_TOOLTIP,
3787                                                           NULL);
3788
3789         gtk_tree_view_column_set_sort_column_id(column, AE_TOOLTIP);
3790         gtk_tree_view_column_set_resizable(column, TRUE);
3791         gtk_tree_view_append_column(GTK_TREE_VIEW(accel_view), column);
3792
3793         renderer = gtk_cell_renderer_text_new();
3794
3795         column = gtk_tree_view_column_new_with_attributes("Accel",
3796                                                           renderer,
3797                                                           "text", AE_ACCEL,
3798                                                           NULL);
3799
3800         gtk_tree_view_column_set_sort_column_id(column, AE_ACCEL);
3801         gtk_tree_view_column_set_resizable(column, TRUE);
3802         gtk_tree_view_append_column(GTK_TREE_VIEW(accel_view), column);
3803
3804         /* Search on text in column */
3805         gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(accel_view), TRUE);
3806         g_signal_connect(accel_view, "row_activated", G_CALLBACK(accel_row_activated_cb), accel_store);
3807         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(accel_view), TRUE);
3808         gtk_tree_view_set_search_column(GTK_TREE_VIEW(accel_view), AE_TOOLTIP);
3809         gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(accel_view), accel_search_function_cb, nullptr, nullptr);
3810
3811         accel_store_populate();
3812         gq_gtk_container_add(GTK_WIDGET(scrolled), accel_view);
3813         gtk_widget_show(accel_view);
3814
3815         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
3816
3817         button = pref_button_new(nullptr, nullptr, _("Defaults"),
3818                                  G_CALLBACK(accel_default_cb), accel_view);
3819         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3820         gtk_widget_show(button);
3821
3822         button = pref_button_new(nullptr, nullptr, _("Reset selected"),
3823                                  G_CALLBACK(accel_reset_cb), accel_view);
3824         gtk_widget_set_tooltip_text(button, _("Will only reset changes made before the settings are saved"));
3825         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3826         gtk_widget_show(button);
3827
3828         button = pref_button_new(nullptr, nullptr, _("Clear selected"),
3829                                  G_CALLBACK(accel_clear_cb), accel_view);
3830         gq_gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3831         gtk_widget_show(button);
3832 }
3833
3834 /* toolbar main tab */
3835 static void config_tab_toolbar_main(GtkWidget *notebook)
3836 {
3837         GtkWidget *vbox;
3838         GtkWidget *toolbardata;
3839         LayoutWindow *lw;
3840
3841         lw = static_cast<LayoutWindow *>(layout_window_list->data);
3842
3843         vbox = scrolled_notebook_page(notebook, _("Toolbar Main"));
3844
3845         toolbardata = toolbar_select_new(lw, TOOLBAR_MAIN);
3846         gq_gtk_box_pack_start(GTK_BOX(vbox), toolbardata, TRUE, TRUE, 0);
3847         gtk_widget_show(vbox);
3848 }
3849
3850 /* toolbar status tab */
3851 static void config_tab_toolbar_status(GtkWidget *notebook)
3852 {
3853         GtkWidget *vbox;
3854         GtkWidget *toolbardata;
3855         LayoutWindow *lw;
3856
3857         lw = static_cast<LayoutWindow *>(layout_window_list->data);
3858
3859         vbox = scrolled_notebook_page(notebook, _("Toolbar Status"));
3860
3861         toolbardata = toolbar_select_new(lw, TOOLBAR_STATUS);
3862         gq_gtk_box_pack_start(GTK_BOX(vbox), toolbardata, TRUE, TRUE, 0);
3863         gtk_widget_show(vbox);
3864 }
3865
3866 /* advanced tab */
3867 static gint extension_sort_cb(gconstpointer a, gconstpointer b)
3868 {
3869         return g_strcmp0(static_cast<const gchar *>(a), static_cast<const gchar *>(b));
3870 }
3871
3872 static void config_tab_advanced(GtkWidget *notebook)
3873 {
3874         gchar **extensions;
3875         GdkPixbufFormat *fm;
3876         gint i;
3877         GList *extensions_list = nullptr;
3878         GSList *formats_list;
3879         GString *types_string = g_string_new(nullptr);
3880         GtkWidget *alternate_checkbox;
3881         GtkWidget *dupes_threads_spin;
3882         GtkWidget *group;
3883         GtkWidget *subgroup;
3884         GtkWidget *tabcomp;
3885         GtkWidget *threads_string_label;
3886         GtkWidget *types_string_label;
3887         GtkWidget *vbox;
3888
3889         vbox = scrolled_notebook_page(notebook, _("Advanced"));
3890         group = pref_group_new(vbox, FALSE, _("External preview extraction"), GTK_ORIENTATION_VERTICAL);
3891
3892         pref_checkbox_new_int(group, _("Use external preview extraction -  Requires restart"), options->external_preview.enable, &c_options->external_preview.enable);
3893
3894         pref_spacer(group, PREF_PAD_GROUP);
3895
3896         formats_list = gdk_pixbuf_get_formats();
3897
3898         while (formats_list)
3899                 {
3900                 fm = static_cast<GdkPixbufFormat *>(formats_list->data);
3901                 extensions = gdk_pixbuf_format_get_extensions(fm);
3902
3903                 i = 0;
3904                 while (extensions[i])
3905                         {
3906                         extensions_list = g_list_insert_sorted(extensions_list, g_strdup(extensions[i]), extension_sort_cb);
3907                         i++;
3908                         }
3909
3910                 g_strfreev(extensions);
3911                 formats_list = formats_list->next;
3912                 }
3913
3914         while (extensions_list)
3915                 {
3916                 if (types_string->len == 0)
3917                         {
3918                         types_string = g_string_append(types_string, static_cast<const gchar *>(extensions_list->data));
3919                         }
3920                 else
3921                         {
3922                         types_string = g_string_append(types_string, ", ");
3923                         types_string = g_string_append(types_string, static_cast<const gchar *>(extensions_list->data));
3924                         }
3925
3926                 extensions_list = extensions_list->next;
3927                 }
3928
3929         types_string = g_string_prepend(types_string, _("Usable file types:\n"));
3930         types_string_label = pref_label_new(group, types_string->str);
3931         gtk_label_set_line_wrap(GTK_LABEL(types_string_label), TRUE);
3932
3933         pref_spacer(group, PREF_PAD_GROUP);
3934
3935         group = pref_group_new(vbox, FALSE, _("File identification tool"), GTK_ORIENTATION_VERTICAL);
3936         external_preview_select_entry = gtk_entry_new();
3937         tabcomp = tab_completion_new(&external_preview_select_entry, options->external_preview.select, nullptr, nullptr, nullptr, nullptr);
3938         tab_completion_add_select_button(external_preview_select_entry, _("Select file identification tool"), FALSE);
3939         gq_gtk_box_pack_start(GTK_BOX(group), tabcomp, TRUE, TRUE, 0);
3940         gtk_widget_show(tabcomp);
3941
3942         group = pref_group_new(vbox, FALSE, _("Preview extraction tool"), GTK_ORIENTATION_VERTICAL);
3943         external_preview_extract_entry = gtk_entry_new();
3944         tabcomp = tab_completion_new(&external_preview_extract_entry, options->external_preview.extract, nullptr, nullptr, nullptr, nullptr);
3945         tab_completion_add_select_button(external_preview_extract_entry, _("Select preview extraction tool"), FALSE);
3946         gq_gtk_box_pack_start(GTK_BOX(group), tabcomp, TRUE, TRUE, 0);
3947         gtk_widget_show(tabcomp);
3948
3949         gtk_widget_show(vbox);
3950
3951         g_slist_free(formats_list);
3952         g_list_free_full(extensions_list, g_free);
3953         g_string_free(types_string, TRUE);
3954
3955         pref_spacer(group, PREF_PAD_GROUP);
3956
3957         pref_line(vbox, PREF_PAD_SPACE);
3958         group = pref_group_new(vbox, FALSE, _("Thread pool limits"), GTK_ORIENTATION_VERTICAL);
3959
3960         threads_string_label = pref_label_new(group, "This option limits the number of threads (or cpu cores) that Geeqie will use when running duplicate checks.\nThe value 0 means all available cores will be used.");
3961         gtk_label_set_line_wrap(GTK_LABEL(threads_string_label), TRUE);
3962
3963         pref_spacer(vbox, PREF_PAD_GROUP);
3964
3965         dupes_threads_spin = pref_spin_new_int(vbox, _("Duplicate check:"), _("max. threads"), 0, get_cpu_cores(), 1, options->threads.duplicates, &c_options->threads.duplicates);
3966         gtk_widget_set_tooltip_markup(dupes_threads_spin, _("Set to 0 for unlimited"));
3967
3968         pref_spacer(group, PREF_PAD_GROUP);
3969
3970         pref_line(vbox, PREF_PAD_SPACE);
3971
3972         group = pref_group_new(vbox, FALSE, _("Alternate similarity alogorithm"), GTK_ORIENTATION_VERTICAL);
3973
3974         alternate_checkbox = pref_checkbox_new_int(group, _("Enable alternate similarity algorithm"), options->alternate_similarity_algorithm.enabled, &c_options->alternate_similarity_algorithm.enabled);
3975
3976         subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
3977         pref_checkbox_link_sensitivity(alternate_checkbox, subgroup);
3978
3979         alternate_checkbox = pref_checkbox_new_int(subgroup, _("Use grayscale"), options->alternate_similarity_algorithm.grayscale, &c_options->alternate_similarity_algorithm.grayscale);
3980         gtk_widget_set_tooltip_text(alternate_checkbox, _("Reduce fingerprint to grayscale"));
3981 }
3982
3983 /* stereo tab */
3984 static void config_tab_stereo(GtkWidget *notebook)
3985 {
3986         GtkWidget *vbox;
3987         GtkWidget *group;
3988         GtkWidget *group2;
3989         GtkWidget *table;
3990         GtkWidget *box;
3991         GtkWidget *box2;
3992         GtkWidget *fs_button;
3993         vbox = scrolled_notebook_page(notebook, _("Stereo"));
3994
3995         group = pref_group_new(vbox, FALSE, _("Windowed stereo mode"), GTK_ORIENTATION_VERTICAL);
3996
3997         table = pref_table_new(group, 2, 1, FALSE, FALSE);
3998         add_stereo_mode_menu(table, 0, 0, _("Windowed stereo mode"), options->stereo.mode, &c_options->stereo.mode, FALSE);
3999
4000         table = pref_table_new(group, 2, 2, TRUE, FALSE);
4001         box = pref_table_box(table, 0, 0, GTK_ORIENTATION_HORIZONTAL, nullptr);
4002         pref_checkbox_new_int(box, _("Mirror left image"),
4003                               options->stereo.mode & PR_STEREO_MIRROR_LEFT, &c_options->stereo.tmp.mirror_left);
4004         box = pref_table_box(table, 1, 0, GTK_ORIENTATION_HORIZONTAL, nullptr);
4005         pref_checkbox_new_int(box, _("Flip left image"),
4006                               options->stereo.mode & PR_STEREO_FLIP_LEFT, &c_options->stereo.tmp.flip_left);
4007         box = pref_table_box(table, 0, 1, GTK_ORIENTATION_HORIZONTAL, nullptr);
4008         pref_checkbox_new_int(box, _("Mirror right image"),
4009                               options->stereo.mode & PR_STEREO_MIRROR_RIGHT, &c_options->stereo.tmp.mirror_right);
4010         box = pref_table_box(table, 1, 1, GTK_ORIENTATION_HORIZONTAL, nullptr);
4011         pref_checkbox_new_int(box, _("Flip right image"),
4012                               options->stereo.mode & PR_STEREO_FLIP_RIGHT, &c_options->stereo.tmp.flip_right);
4013         pref_checkbox_new_int(group, _("Swap left and right images"),
4014                               options->stereo.mode & PR_STEREO_SWAP, &c_options->stereo.tmp.swap);
4015         pref_checkbox_new_int(group, _("Disable stereo mode on single image source"),
4016                               options->stereo.mode & PR_STEREO_TEMP_DISABLE, &c_options->stereo.tmp.temp_disable);
4017
4018         group = pref_group_new(vbox, FALSE, _("Fullscreen stereo mode"), GTK_ORIENTATION_VERTICAL);
4019         fs_button = pref_checkbox_new_int(group, _("Use different settings for fullscreen"),
4020                               options->stereo.enable_fsmode, &c_options->stereo.enable_fsmode);
4021         box2 = pref_box_new(group, FALSE, GTK_ORIENTATION_VERTICAL, PREF_PAD_SPACE);
4022         pref_checkbox_link_sensitivity(fs_button, box2);
4023         table = pref_table_new(box2, 2, 1, FALSE, FALSE);
4024         add_stereo_mode_menu(table, 0, 0, _("Fullscreen stereo mode"), options->stereo.fsmode, &c_options->stereo.fsmode, TRUE);
4025         table = pref_table_new(box2, 2, 2, TRUE, FALSE);
4026         box = pref_table_box(table, 0, 0, GTK_ORIENTATION_HORIZONTAL, nullptr);
4027         pref_checkbox_new_int(box, _("Mirror left image"),
4028                               options->stereo.fsmode & PR_STEREO_MIRROR_LEFT, &c_options->stereo.tmp.fs_mirror_left);
4029         box = pref_table_box(table, 1, 0, GTK_ORIENTATION_HORIZONTAL, nullptr);
4030         pref_checkbox_new_int(box, _("Flip left image"),
4031                               options->stereo.fsmode & PR_STEREO_FLIP_LEFT, &c_options->stereo.tmp.fs_flip_left);
4032         box = pref_table_box(table, 0, 1, GTK_ORIENTATION_HORIZONTAL, nullptr);
4033         pref_checkbox_new_int(box, _("Mirror right image"),
4034                               options->stereo.fsmode & PR_STEREO_MIRROR_RIGHT, &c_options->stereo.tmp.fs_mirror_right);
4035         box = pref_table_box(table, 1, 1, GTK_ORIENTATION_HORIZONTAL, nullptr);
4036         pref_checkbox_new_int(box, _("Flip right image"),
4037                               options->stereo.fsmode & PR_STEREO_FLIP_RIGHT, &c_options->stereo.tmp.fs_flip_right);
4038         pref_checkbox_new_int(box2, _("Swap left and right images"),
4039                               options->stereo.fsmode & PR_STEREO_SWAP, &c_options->stereo.tmp.fs_swap);
4040         pref_checkbox_new_int(box2, _("Disable stereo mode on single image source"),
4041                               options->stereo.fsmode & PR_STEREO_TEMP_DISABLE, &c_options->stereo.tmp.fs_temp_disable);
4042
4043         group2 = pref_group_new(box2, FALSE, _("Fixed position"), GTK_ORIENTATION_VERTICAL);
4044         table = pref_table_new(group2, 5, 3, FALSE, FALSE);
4045         pref_table_spin_new_int(table, 0, 0, _("Width"), nullptr,
4046                           1, 5000, 1, options->stereo.fixed_w, &c_options->stereo.fixed_w);
4047         pref_table_spin_new_int(table, 3, 0,  _("Height"), nullptr,
4048                           1, 5000, 1, options->stereo.fixed_h, &c_options->stereo.fixed_h);
4049         pref_table_spin_new_int(table, 0, 1,  _("Left X"), nullptr,
4050                           0, 5000, 1, options->stereo.fixed_x1, &c_options->stereo.fixed_x1);
4051         pref_table_spin_new_int(table, 3, 1,  _("Left Y"), nullptr,
4052                           0, 5000, 1, options->stereo.fixed_y1, &c_options->stereo.fixed_y1);
4053         pref_table_spin_new_int(table, 0, 2,  _("Right X"), nullptr,
4054                           0, 5000, 1, options->stereo.fixed_x2, &c_options->stereo.fixed_x2);
4055         pref_table_spin_new_int(table, 3, 2,  _("Right Y"), nullptr,
4056                           0, 5000, 1, options->stereo.fixed_y2, &c_options->stereo.fixed_y2);
4057
4058 }
4059
4060 /* Main preferences window */
4061 static void config_window_create(LayoutWindow *lw)
4062 {
4063         GtkWidget *win_vbox;
4064         GtkWidget *hbox;
4065         GtkWidget *notebook;
4066         GtkWidget *button;
4067         GtkWidget *ct_button;
4068
4069         if (!c_options) c_options = init_options(nullptr);
4070
4071         configwindow = window_new("preferences", PIXBUF_INLINE_ICON_CONFIG, nullptr, _("Preferences"));
4072         DEBUG_NAME(configwindow);
4073         gtk_window_set_type_hint(GTK_WINDOW(configwindow), GDK_WINDOW_TYPE_HINT_DIALOG);
4074         g_signal_connect(G_OBJECT(configwindow), "delete_event",
4075                          G_CALLBACK(config_window_delete), NULL);
4076         if (options->save_dialog_window_positions)
4077                 {
4078                 gtk_window_resize(GTK_WINDOW(configwindow), lw->options.preferences_window.w, lw->options.preferences_window.h);
4079                 gq_gtk_window_move(GTK_WINDOW(configwindow), lw->options.preferences_window.x, lw->options.preferences_window.y);
4080                 }
4081         else
4082                 {
4083                 gtk_window_set_default_size(GTK_WINDOW(configwindow), CONFIG_WINDOW_DEF_WIDTH, CONFIG_WINDOW_DEF_HEIGHT);
4084                 }
4085         gtk_window_set_resizable(GTK_WINDOW(configwindow), TRUE);
4086         gtk_container_set_border_width(GTK_CONTAINER(configwindow), PREF_PAD_BORDER);
4087
4088         win_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_SPACE);
4089         gq_gtk_container_add(GTK_WIDGET(configwindow), win_vbox);
4090         gtk_widget_show(win_vbox);
4091
4092         notebook = gtk_notebook_new();
4093         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_LEFT);
4094         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
4095         gq_gtk_box_pack_start(GTK_BOX(win_vbox), notebook, TRUE, TRUE, 0);
4096
4097         config_tab_general(notebook);
4098         config_tab_image(notebook);
4099         config_tab_osd(notebook);
4100         config_tab_windows(notebook);
4101         config_tab_accelerators(notebook);
4102         config_tab_files(notebook);
4103         config_tab_metadata(notebook);
4104         config_tab_keywords(notebook);
4105         config_tab_color(notebook);
4106         config_tab_stereo(notebook);
4107         config_tab_behavior(notebook);
4108         config_tab_toolbar_main(notebook);
4109         config_tab_toolbar_status(notebook);
4110         config_tab_advanced(notebook);
4111
4112         gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), lw->options.preferences_window.page_number);
4113
4114         hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
4115         gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
4116         gtk_box_set_spacing(GTK_BOX(hbox), PREF_PAD_BUTTON_GAP);
4117         gq_gtk_box_pack_end(GTK_BOX(win_vbox), hbox, FALSE, FALSE, 0);
4118         gtk_widget_show(hbox);
4119
4120         button = pref_button_new(nullptr, GQ_ICON_HELP, _("Help"),
4121                                  G_CALLBACK(config_window_help_cb), notebook);
4122         gq_gtk_container_add(GTK_WIDGET(hbox), button);
4123         gtk_widget_set_can_default(button, TRUE);
4124         gtk_widget_show(button);
4125
4126         button = pref_button_new(nullptr, GQ_ICON_OK, "OK",
4127                                  G_CALLBACK(config_window_ok_cb), notebook);
4128         gq_gtk_container_add(GTK_WIDGET(hbox), button);
4129         gtk_widget_set_can_default(button, TRUE);
4130         gtk_widget_grab_default(button);
4131         gtk_widget_show(button);
4132
4133         ct_button = button;
4134
4135         button = pref_button_new(nullptr, GQ_ICON_CANCEL, _("Cancel"),
4136                                  G_CALLBACK(config_window_close_cb), nullptr);
4137         gq_gtk_container_add(GTK_WIDGET(hbox), button);
4138         gtk_widget_set_can_default(button, TRUE);
4139         gtk_widget_show(button);
4140
4141         if (!generic_dialog_get_alternative_button_order(configwindow))
4142                 {
4143                 gtk_box_reorder_child(GTK_BOX(hbox), ct_button, -1);
4144                 }
4145
4146         gtk_widget_show(notebook);
4147
4148         gtk_widget_show(configwindow);
4149 }
4150
4151 /*
4152  *-----------------------------------------------------------------------------
4153  * config window show (public)
4154  *-----------------------------------------------------------------------------
4155  */
4156
4157 void show_config_window(LayoutWindow *lw)
4158 {
4159         if (configwindow)
4160                 {
4161                 gtk_window_present(GTK_WINDOW(configwindow));
4162                 return;
4163                 }
4164
4165         config_window_create(lw);
4166 }
4167
4168 /*
4169  *-----------------
4170  * about window
4171  *-----------------
4172  */
4173
4174 void show_about_window(LayoutWindow *lw)
4175 {
4176         GDataInputStream *data_stream;
4177         GInputStream *in_stream_authors;
4178         GInputStream *in_stream_translators;
4179         GString *copyright;
4180         GdkPixbuf *pixbuf_icon;
4181         GdkPixbuf *pixbuf_logo;
4182         ZoneDetect *cd;
4183         gchar *artists[2];
4184         gchar *author_line;
4185         gchar *authors[1000];
4186         gchar *comment;
4187         gchar *timezone_path;
4188         gchar *translators;
4189         gint i_authors = 0;
4190         gint n = 0;
4191         gsize bytes_read;
4192         gsize length;
4193         gsize size;
4194         guint32 flags;
4195
4196         copyright = g_string_new("This program comes with absolutely no warranty.\nGNU General Public License, version 2 or later.\nSee https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n\n");
4197
4198         timezone_path = g_build_filename(get_rc_dir(), TIMEZONE_DATABASE_FILE, NULL);
4199         if (g_file_test(timezone_path, G_FILE_TEST_EXISTS))
4200                 {
4201                 cd = ZDOpenDatabase(timezone_path);
4202                 if (cd)
4203                         {
4204                         copyright = g_string_append(copyright, ZDGetNotice(cd));
4205                         }
4206                 else
4207                         {
4208                         log_printf("Error: Init of timezone database %s failed\n", timezone_path);
4209                         }
4210                 ZDCloseDatabase(cd);
4211                 }
4212         g_free(timezone_path);
4213
4214         copyright = g_string_append(copyright, "\n\nSome icons by https://www.flaticon.com");
4215
4216         in_stream_authors = g_resources_open_stream(GQ_RESOURCE_PATH_CREDITS "/authors", G_RESOURCE_LOOKUP_FLAGS_NONE, nullptr);
4217
4218         data_stream = g_data_input_stream_new(in_stream_authors);
4219
4220         authors[0] = nullptr;
4221         while ((author_line = g_data_input_stream_read_line(G_DATA_INPUT_STREAM(data_stream), &length, nullptr, nullptr)))
4222                 {
4223                 authors[i_authors] = g_strdup(author_line);
4224                 i_authors++;
4225                 g_free(author_line);
4226                 }
4227         authors[i_authors] = nullptr;
4228
4229         g_input_stream_close(in_stream_authors, nullptr, nullptr);
4230
4231         constexpr auto translators_path = GQ_RESOURCE_PATH_CREDITS "/translators";
4232
4233         g_resources_get_info(translators_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, nullptr);
4234
4235         in_stream_translators = g_resources_open_stream(translators_path, G_RESOURCE_LOOKUP_FLAGS_NONE, nullptr);
4236         translators = static_cast<gchar *>(g_malloc0(size));
4237         g_input_stream_read_all(in_stream_translators, translators, size, &bytes_read, nullptr, nullptr);
4238         g_input_stream_close(in_stream_translators, nullptr, nullptr);
4239
4240         comment = g_strconcat("Project created by John Ellis\nGQview 1998\nGeeqie 2007\n\n\nDevelopment and bug reports:\n", GQ_EMAIL_ADDRESS, "\nhttps://github.com/BestImageViewer/geeqie/issues",NULL);
4241
4242         artists[0] = g_strdup("Néstor Díaz Valencia <nestor@estudionexos.com>");
4243         artists[1] = nullptr;
4244
4245         pixbuf_logo = pixbuf_inline(PIXBUF_INLINE_LOGO);
4246         pixbuf_icon = pixbuf_inline(PIXBUF_INLINE_ICON);
4247         gtk_show_about_dialog(GTK_WINDOW(lw->window),
4248                 "title", _("About Geeqie"),
4249                 "resizable", TRUE,
4250                 "program-name", GQ_APPNAME,
4251                 "version", VERSION,
4252                 "logo", pixbuf_logo,
4253                 "icon", pixbuf_icon,
4254                 "website", GQ_WEBSITE,
4255                 "website-label", "Website",
4256                 "comments", comment,
4257                 "artists", artists,
4258                 "authors", authors,
4259                 "translator-credits", translators,
4260                 "wrap-license", TRUE,
4261                 "license", copyright->str,
4262                 NULL);
4263
4264         g_string_free(copyright, TRUE);
4265
4266         while(n < i_authors)
4267                 {
4268                 g_free(authors[n]);
4269                 n++;
4270                 }
4271
4272         g_free(artists[0]);
4273         g_free(comment);
4274         g_free(translators);
4275         g_object_unref(data_stream);
4276         g_object_unref(in_stream_authors);
4277         g_object_unref(in_stream_translators);
4278 }
4279
4280 static void image_overlay_set_text_colors()
4281 {
4282         c_options->image_overlay.text_red = options->image_overlay.text_red;
4283         c_options->image_overlay.text_green = options->image_overlay.text_green;
4284         c_options->image_overlay.text_blue = options->image_overlay.text_blue;
4285         c_options->image_overlay.text_alpha = options->image_overlay.text_alpha;
4286         c_options->image_overlay.background_red = options->image_overlay.background_red;
4287         c_options->image_overlay.background_green = options->image_overlay.background_green;
4288         c_options->image_overlay.background_blue = options->image_overlay.background_blue;
4289         c_options->image_overlay.background_alpha = options->image_overlay.background_alpha;
4290 }
4291
4292 /*
4293  *-----------------------------------------------------------------------------
4294  * timezone database routines
4295  *-----------------------------------------------------------------------------
4296  */
4297
4298 static void timezone_async_ready_cb(GObject *source_object, GAsyncResult *res, gpointer data)
4299 {
4300         GError *error = nullptr;
4301         auto tz = static_cast<TZData *>(data);
4302         gchar *tmp_filename;
4303         gchar *timezone_bin;
4304         gchar *tmp_dir = nullptr;
4305         FileData *fd;
4306
4307         if (!g_cancellable_is_cancelled(tz->cancellable))
4308                 {
4309                 generic_dialog_close(tz->gd);
4310                 }
4311
4312
4313         if (g_file_copy_finish(G_FILE(source_object), res, &error))
4314                 {
4315                 tmp_filename = g_file_get_path(tz->tmp_g_file);
4316                 fd = file_data_new_simple(tmp_filename);
4317                 tmp_dir = open_archive(fd);
4318
4319                 if (tmp_dir)
4320                         {
4321                         timezone_bin = g_build_filename(tmp_dir, TIMEZONE_DATABASE_VERSION, TIMEZONE_DATABASE_FILE, NULL);
4322                         if (isfile(timezone_bin))
4323                                 {
4324                                 move_file(timezone_bin, tz->timezone_database_user);
4325                                 }
4326                         else
4327                                 {
4328                                 warning_dialog(_("Warning: Cannot open timezone database file"), _("See the Log Window"), GQ_ICON_DIALOG_WARNING, nullptr);
4329                                 }
4330
4331                         g_free(timezone_bin);
4332                         g_free(tmp_dir); // The folder in /tmp is deleted in exit_program_final()
4333                         }
4334                 else
4335                         {
4336                         warning_dialog(_("Warning: Cannot open timezone database file"), _("See the Log Window"), GQ_ICON_DIALOG_WARNING, nullptr);
4337                         }
4338                 g_free(tmp_filename);
4339                 file_data_unref(fd);
4340                 }
4341         else
4342                 {
4343                 file_util_warning_dialog(_("Error: Timezone database download failed"), error->message, GQ_ICON_DIALOG_ERROR, nullptr);
4344                 }
4345
4346         g_file_delete(tz->tmp_g_file, nullptr, &error);
4347         g_object_unref(tz->tmp_g_file);
4348         tz->tmp_g_file = nullptr;
4349         g_object_unref(tz->cancellable);
4350         g_object_unref(tz->timezone_database_gq);
4351 }
4352
4353 static void timezone_progress_cb(goffset current_num_bytes, goffset total_num_bytes, gpointer data)
4354 {
4355         auto tz = static_cast<TZData *>(data);
4356
4357         if (!g_cancellable_is_cancelled(tz->cancellable))
4358                 {
4359                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(tz->progress), static_cast<gdouble>(current_num_bytes) / total_num_bytes);
4360                 }
4361 }
4362
4363 static void timezone_cancel_button_cb(GenericDialog *, gpointer data)
4364 {
4365         auto tz = static_cast<TZData *>(data);
4366
4367         g_cancellable_cancel(tz->cancellable);
4368 }
4369
4370 static void timezone_database_install_cb(GtkWidget *widget, gpointer data)
4371 {
4372         auto tz = static_cast<TZData *>(data);
4373         GError *error = nullptr;
4374         GFileIOStream *io_stream;
4375
4376         if (tz->tmp_g_file)
4377                 {
4378                 return;
4379                 }
4380
4381         tz->tmp_g_file = g_file_new_tmp("geeqie_timezone_XXXXXX", &io_stream, &error);
4382
4383         if (error)
4384                 {
4385                 file_util_warning_dialog(_("Timezone database download failed"), error->message, GQ_ICON_DIALOG_ERROR, nullptr);
4386                 log_printf("Error: Download timezone database failed:\n%s", error->message);
4387                 g_error_free(error);
4388                 g_object_unref(tz->tmp_g_file);
4389                 }
4390         else
4391                 {
4392                 tz->timezone_database_gq = g_file_new_for_uri(TIMEZONE_DATABASE_WEB);
4393
4394                 tz->gd = generic_dialog_new(_("Timezone database"), "download_timezone_database", nullptr, TRUE, timezone_cancel_button_cb, tz);
4395
4396                 generic_dialog_add_message(tz->gd, GQ_ICON_DIALOG_INFO, _("Downloading timezone database"), nullptr, FALSE);
4397
4398                 tz->progress = gtk_progress_bar_new();
4399                 gq_gtk_box_pack_start(GTK_BOX(tz->gd->vbox), tz->progress, FALSE, FALSE, 0);
4400                 gtk_widget_show(tz->progress);
4401
4402                 gtk_widget_show(tz->gd->dialog);
4403                 tz->cancellable = g_cancellable_new();
4404                 g_file_copy_async(tz->timezone_database_gq, tz->tmp_g_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_LOW, tz->cancellable, timezone_progress_cb, tz, timezone_async_ready_cb, tz);
4405
4406                 gtk_button_set_label(GTK_BUTTON(widget), _("Update"));
4407                 }
4408 }
4409 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */