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