Spelling checks for keywords auto-completion list
[geeqie.git] / src / preferences.c
index d0d34a2..dce9e05 100644 (file)
 #include "layout_config.h"
 #include "layout_util.h"
 #include "metadata.h"
+#include "misc.h"
 #include "osd.h"
 #include "pixbuf_util.h"
+#include "rcfile.h"
 #include "slideshow.h"
 #include "toolbar.h"
 #include "trash.h"
 #endif
 #endif
 
+#ifdef HAVE_SPELL
+#include <gspell/gspell.h>
+#endif
+
 #define EDITOR_NAME_MAX_LENGTH 32
 #define EDITOR_COMMAND_MAX_LENGTH 1024
 
@@ -110,6 +116,15 @@ enum {
        AE_ACCEL
 };
 
+enum {
+       FILETYPES_COLUMN_FILTER = 0,
+       FILETYPES_COLUMN_DESCRIPTION,
+       FILETYPES_COLUMN_CLASS,
+       FILETYPES_COLUMN_WRITABLE,
+       FILETYPES_COLUMN_SIDECAR,
+       FILETYPES_COLUMN_COUNT
+};
+
 gchar *format_class_list[] = {
        N_("Unknown"),
        N_("Image"),
@@ -117,7 +132,8 @@ gchar *format_class_list[] = {
        N_("Metadata"),
        N_("Video"),
        N_("Collection"),
-       N_("Document")
+       N_("Document"),
+       N_("Archive")
        };
 
 /* config memory values */
@@ -284,6 +300,7 @@ static void config_window_apply(void)
        options->image.max_autofit_size = c_options->image.max_autofit_size;
        options->image.max_enlargement_size = c_options->image.max_enlargement_size;
        options->image.use_clutter_renderer = c_options->image.use_clutter_renderer;
+       options->image.tile_size = c_options->image.tile_size;
        options->progressive_key_scrolling = c_options->progressive_key_scrolling;
        options->keyboard_scroll_step = c_options->keyboard_scroll_step;
 
@@ -324,6 +341,7 @@ static void config_window_apply(void)
 
        options->mousewheel_scrolls = c_options->mousewheel_scrolls;
        options->image_lm_click_nav = c_options->image_lm_click_nav;
+       options->image_l_click_archive = c_options->image_l_click_archive;
        options->image_l_click_video = c_options->image_l_click_video;
        options->image_l_click_video_editor = c_options->image_l_click_video_editor;
 
@@ -336,6 +354,8 @@ static void config_window_apply(void)
 
        options->image.zoom_increment = c_options->image.zoom_increment;
 
+       options->image.zoom_style = c_options->image.zoom_style;
+
        options->image.enable_read_ahead = c_options->image.enable_read_ahead;
 
 
@@ -372,7 +392,6 @@ static void config_window_apply(void)
        options->image_overlay.background_blue = c_options->image_overlay.background_blue;
        options->image_overlay.background_alpha = c_options->image_overlay.background_alpha;
        options->update_on_time_change = c_options->update_on_time_change;
-       options->image.exif_proof_rotate_enable = c_options->image.exif_proof_rotate_enable;
 
        options->duplicates_similarity_threshold = c_options->duplicates_similarity_threshold;
        options->rot_invariant_sim = c_options->rot_invariant_sim;
@@ -400,6 +419,7 @@ static void config_window_apply(void)
        options->metadata.confirm_on_dir_change = c_options->metadata.confirm_on_dir_change;
        options->metadata.keywords_case_sensitive = c_options->metadata.keywords_case_sensitive;
        options->metadata.write_orientation = c_options->metadata.write_orientation;
+       options->metadata.check_spelling = c_options->metadata.check_spelling;
        options->stereo.mode = (c_options->stereo.mode & (PR_STEREO_HORIZ | PR_STEREO_VERT | PR_STEREO_FIXED | PR_STEREO_ANAGLYPH | PR_STEREO_HALF)) |
                               (c_options->stereo.tmp.mirror_right ? PR_STEREO_MIRROR_RIGHT : 0) |
                               (c_options->stereo.tmp.flip_right   ? PR_STEREO_FLIP_RIGHT : 0) |
@@ -444,6 +464,9 @@ static void config_window_apply(void)
 
        options->star_rating.star = c_options->star_rating.star;
        options->star_rating.rejected = c_options->star_rating.rejected;
+
+       options->threads.duplicates = c_options->threads.duplicates > 0 ? c_options->threads.duplicates : -1;
+
 #ifdef DEBUG
        set_debug_level(debug_c);
 #endif
@@ -707,6 +730,47 @@ static void add_clipboard_selection_menu(GtkWidget *table, gint column, gint row
        gtk_widget_show(combo);
 }
 
+static void zoom_style_selection_menu_cb(GtkWidget *combo, gpointer data)
+{
+       gint *option = data;
+
+       switch (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)))
+               {
+               case 0:
+                       *option = ZOOM_GEOMETRIC;
+                       break;
+               case 1:
+                       *option = ZOOM_ARITHMETIC;
+                       break;
+               default:
+                       *option = ZOOM_GEOMETRIC;
+               }
+}
+
+static void add_zoom_style_selection_menu(GtkWidget *table, gint column, gint row, const gchar *text, ZoomStyle option, ZoomStyle *option_c)
+{
+       GtkWidget *combo;
+       gint current = 0;
+
+       *option_c = option;
+
+       pref_table_label(table, column, row, text, 0.0);
+
+       combo = gtk_combo_box_text_new();
+
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Geometric"));
+       if (option == ZOOM_GEOMETRIC) current = 0;
+       gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Arithmetic"));
+       if (option == ZOOM_ARITHMETIC) current = 1;
+
+       gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
+
+       g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(zoom_style_selection_menu_cb), option_c);
+
+       gtk_table_attach(GTK_TABLE(table), combo, column + 1, column + 2, row, row + 1, GTK_SHRINK, 0, 0, 0);
+       gtk_widget_show(combo);
+}
+
 typedef struct _UseableMouseItems UseableMouseItems;
 struct _UseableMouseItems
 {
@@ -781,7 +845,6 @@ static const UseableMouseItems useable_mouse_items[] = {
        {"ExifWin",     N_("Exif window"), PIXBUF_INLINE_ICON_EXIF},
        {"Thumbnails",  N_("Show thumbnails"), PIXBUF_INLINE_ICON_THUMB},
        {"ShowMarks",   N_("Show marks"), PIXBUF_INLINE_ICON_MARKS},
-       {"ImageGuidelines",     N_("Show guidelines"), PIXBUF_INLINE_ICON_GUIDELINES},
        {"DrawRectangle",       N_("Draw Rectangle"), PIXBUF_INLINE_ICON_DRAW_RECTANGLE},
        {"FloatTools",  N_("Float file list"), PIXBUF_INLINE_ICON_FLOAT},
        {"SBar",        N_("Info sidebar"), PIXBUF_INLINE_ICON_INFO},
@@ -1934,9 +1997,10 @@ static void config_tab_general(GtkWidget *notebook)
        gchar *rating_symbol;
        gchar *path;
        gchar *basename;
+       gchar *download_locn;
        GNetworkMonitor *net_mon;
-       GSocketConnectable *geeqie_org;
-       gboolean internet_available;
+       GSocketConnectable *tz_org;
+       gboolean internet_available = FALSE;
        TZData *tz;
 
        vbox = scrolled_notebook_page(notebook, _("General"));
@@ -2125,35 +2189,13 @@ static void config_tab_general(GtkWidget *notebook)
 
        pref_spacer(group, PREF_PAD_GROUP);
 
-       group = pref_group_new(vbox, FALSE, _("Info sidebar heights"), GTK_ORIENTATION_VERTICAL);
-       pref_label_new(group, _("NOTE! Geeqie must be restarted for changes to take effect"));
-       hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
-       pref_spin_new_int(hbox, _("Keywords:"), NULL,
-                                1, 9999, 1,
-                                options->info_keywords.height, &c_options->info_keywords.height);
-       pref_spin_new_int(hbox, _("Title:"), NULL,
-                                1, 9999, 1,
-                                options->info_title.height, &c_options->info_title.height);
-       pref_spin_new_int(hbox, _("Comment:"), NULL,
-                                1, 9999, 1,
-                                options->info_comment.height, &c_options->info_comment.height);
-       pref_spin_new_int(hbox, _("Rating:"), NULL,
-                                1, 9999, 1,
-                                options->info_rating.height, &c_options->info_rating.height);
-
-       pref_spacer(group, PREF_PAD_GROUP);
-
-       group = pref_group_new(vbox, FALSE, _("Show predefined keyword tree"), GTK_ORIENTATION_VERTICAL);
-
-       pref_checkbox_new_int(group, _("Show predefined keyword tree (NOTE! Geeqie must be restarted for change to take effect)"),
-                               options->show_predefined_keyword_tree, &c_options->show_predefined_keyword_tree);
-
-       pref_spacer(group, PREF_PAD_GROUP);
-
        net_mon = g_network_monitor_get_default();
-       geeqie_org = g_network_address_parse_uri(GQ_WEBSITE, 80, NULL);
-       internet_available = g_network_monitor_can_reach(net_mon, geeqie_org, NULL, NULL);
-       g_object_unref(geeqie_org);
+       tz_org = g_network_address_parse_uri(TIMEZONE_DATABASE_WEB, 80, NULL);
+       if (tz_org)
+               {
+               internet_available = g_network_monitor_can_reach(net_mon, tz_org, NULL, NULL);
+               g_object_unref(tz_org);
+               }
 
        group = pref_group_new(vbox, FALSE, _("Timezone database"), GTK_ORIENTATION_VERTICAL);
        hbox = pref_box_new(group, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
@@ -2165,9 +2207,9 @@ static void config_tab_general(GtkWidget *notebook)
 
        tz = g_new0(TZData, 1);
 
-       path = path_from_utf8(TIMEZONE_DATABASE);
+       path = path_from_utf8(TIMEZONE_DATABASE_WEB);
        basename = g_path_get_basename(path);
-       tz->timezone_database_user = g_build_filename(get_rc_dir(), basename, NULL);
+       tz->timezone_database_user = g_build_filename(get_rc_dir(), TIMEZONE_DATABASE_FILE, NULL);
        g_free(path);
        g_free(basename);
 
@@ -2180,6 +2222,10 @@ static void config_tab_general(GtkWidget *notebook)
                button = pref_button_new(GTK_WIDGET(hbox), NULL, _("Install"), FALSE, G_CALLBACK(timezone_database_install_cb), tz);
                }
 
+       download_locn = g_strconcat(_("Download database from: "), TIMEZONE_DATABASE_WEB, NULL);
+       pref_label_new(GTK_WIDGET(hbox), download_locn);
+       g_free(download_locn);
+
        if (!internet_available)
                {
                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"));
@@ -2262,6 +2308,10 @@ static void config_tab_image(GtkWidget *notebook)
                             G_CALLBACK(zoom_increment_cb), NULL);
        gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_ALWAYS);
 
+       c_options->image.zoom_style = options->image.zoom_style;
+       table = pref_table_new(group, 2, 1, FALSE, FALSE);
+       add_zoom_style_selection_menu(table, 0, 0, _("Zoom style:"), options->image.zoom_style, &c_options->image.zoom_style);
+
        group = pref_group_new(vbox, FALSE, _("Fit image to window"), GTK_ORIENTATION_VERTICAL);
 
        hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
@@ -2282,6 +2332,15 @@ static void config_tab_image(GtkWidget *notebook)
        pref_checkbox_link_sensitivity(ct_button, spin);
        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."));
 
+       group = pref_group_new(vbox, FALSE, _("Tile size"), GTK_ORIENTATION_VERTICAL);
+       gtk_widget_set_sensitive(group, !options->image.use_clutter_renderer);
+
+       hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+       spin = pref_spin_new_int(hbox, _("Pixels"), _("(Requires restart)"),
+                                128, 4096, 128,
+                                options->image.tile_size, &c_options->image.tile_size);
+       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."));
+
        group = pref_group_new(vbox, FALSE, _("Appearance"), GTK_ORIENTATION_VERTICAL);
 
        pref_checkbox_new_int(group, _("Use custom border color in window mode"),
@@ -2303,19 +2362,66 @@ static void config_tab_image(GtkWidget *notebook)
 
        c_options->image.alpha_color_1 = options->image.alpha_color_1;
        c_options->image.alpha_color_2 = options->image.alpha_color_2;
+}
+
+/* windows tab */
 
-       group = pref_group_new(vbox, FALSE, _("Convenience"), GTK_ORIENTATION_VERTICAL);
+static void save_default_window_layout_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = NULL;
+       gchar *default_path;
+       gchar *tmp_id;
+
+       /* Get current lw */
+       layout_valid(&lw);
+
+       tmp_id = lw->options.id;
+       lw->options.id = g_strdup("lw_default");
 
-       pref_checkbox_new_int(group, _("Auto rotate proofs using Exif information"),
-                             options->image.exif_proof_rotate_enable, &c_options->image.exif_proof_rotate_enable);
+       default_path = g_build_filename(get_rc_dir(), DEFAULT_WINDOW_LAYOUT, NULL);
+       save_default_layout_options_to_file(default_path, options, lw);
+       g_free(lw->options.id);
+       lw->options.id = tmp_id;
+       g_free(default_path);
 }
 
-/* windows tab */
+#if GTK_CHECK_VERSION(3,22,0)
+static gboolean popover_cb(gpointer data)
+{
+       GtkPopover *popover = data;
+
+       gtk_popover_popdown(popover);
+
+       return FALSE;
+}
+
+static void default_layout_changed_cb(GtkWidget *button, GtkPopover *popover)
+{
+       gtk_popover_popup(popover);
+
+       g_timeout_add(2000, popover_cb, popover);
+}
+
+static GtkWidget *create_popover(GtkWidget *parent, GtkWidget *child, GtkPositionType pos)
+{
+       GtkWidget *popover;
+
+       popover = gtk_popover_new(parent);
+       gtk_popover_set_position(GTK_POPOVER (popover), pos);
+       gtk_container_add (GTK_CONTAINER(popover), child);
+       gtk_container_set_border_width(GTK_CONTAINER (popover), 6);
+       gtk_widget_show (child);
+
+       return popover;
+}
+#endif
+
 static void config_tab_windows(GtkWidget *notebook)
 {
        GtkWidget *hbox;
        GtkWidget *vbox;
        GtkWidget *group;
+       GtkWidget *subgroup;
        GtkWidget *button;
        GtkWidget *ct_button;
        GtkWidget *spin;
@@ -2324,7 +2430,7 @@ static void config_tab_windows(GtkWidget *notebook)
 
        group = pref_group_new(vbox, FALSE, _("State"), GTK_ORIENTATION_VERTICAL);
 
-       ct_button = pref_checkbox_new_int(group, _("Remember window positions"),
+       ct_button = pref_checkbox_new_int(group, _("Remember session"),
                                          options->save_window_positions, &c_options->save_window_positions);
 
        button = pref_checkbox_new_int(group, _("Use saved window positions also for new windows"),
@@ -2344,6 +2450,18 @@ static void config_tab_windows(GtkWidget *notebook)
        pref_checkbox_new_int(group, _("Show window IDs"),
                              options->show_window_ids, &c_options->show_window_ids);
 
+       subgroup = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+       pref_label_new(subgroup, _("Use current layout for default: "));
+       button = pref_button_new(subgroup, NULL, _("Set"), FALSE, G_CALLBACK(save_default_window_layout_cb), NULL);
+
+#if GTK_CHECK_VERSION(3,22,0)
+       GtkWidget *popover;
+
+       popover = create_popover(button, gtk_label_new(_("Current window layout\nhas been set as default")), GTK_POS_TOP);
+       gtk_popover_set_modal(GTK_POPOVER (popover), FALSE);
+       g_signal_connect(button, "clicked", G_CALLBACK(default_layout_changed_cb), popover);
+#endif
+
        group = pref_group_new(vbox, FALSE, _("Size"), GTK_ORIENTATION_VERTICAL);
 
        pref_checkbox_new_int(group, _("Fit window to image when tools are hidden/floating"),
@@ -2523,6 +2641,60 @@ static GtkTreeModel *create_class_model(void)
 
 
 /* filtering tab */
+static gint filter_table_sort_cb(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data)
+{
+       gint n = GPOINTER_TO_INT(data);
+       gint ret = 0;
+       FilterEntry *filter_a;
+       FilterEntry *filter_b;
+
+       gtk_tree_model_get(model, a, 0, &filter_a, -1);
+       gtk_tree_model_get(model, b, 0, &filter_b, -1);
+
+       switch (n)
+               {
+               case FILETYPES_COLUMN_DESCRIPTION:
+                       {
+                       ret = g_utf8_collate(filter_a->description, filter_b->description);
+                       break;
+                       }
+               case FILETYPES_COLUMN_CLASS:
+                       {
+                       ret = g_strcmp0(format_class_list[filter_a->file_class], format_class_list[filter_b->file_class]);
+                       break;
+                       }
+               case FILETYPES_COLUMN_WRITABLE:
+                       {
+                       ret = filter_a->writable - filter_b->writable;
+                       break;
+                       }
+               case FILETYPES_COLUMN_SIDECAR:
+                       {
+                       ret = filter_a->allow_sidecar - filter_b->allow_sidecar;
+                       break;
+                       }
+               default:
+                       g_return_val_if_reached(0);
+               }
+
+       return ret;
+}
+
+static gboolean search_function_cb(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
+{
+       FilterEntry *fe;
+       gboolean ret = TRUE;
+
+       gtk_tree_model_get(model, iter, 0, &fe, -1);
+
+       if (g_strstr_len(fe->extensions, -1, key))
+               {
+               ret = FALSE;
+               }
+
+       return ret;
+}
+
 static void config_tab_files(GtkWidget *notebook)
 {
        GtkWidget *hbox;
@@ -2604,6 +2776,10 @@ static void config_tab_files(GtkWidget *notebook)
                                                GINT_TO_POINTER(FE_EXTENSION), NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
 
+       gtk_tree_view_set_enable_search(GTK_TREE_VIEW(filter_view), TRUE);
+       gtk_tree_view_set_search_column(GTK_TREE_VIEW(filter_view), FILETYPES_COLUMN_FILTER);
+       gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(filter_view), search_function_cb, NULL, NULL);
+
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_title(column, _("Description"));
        gtk_tree_view_column_set_resizable(column, TRUE);
@@ -2618,6 +2794,8 @@ static void config_tab_files(GtkWidget *notebook)
        gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
                                                GINT_TO_POINTER(FE_DESCRIPTION), NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
+       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_DESCRIPTION, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_DESCRIPTION), NULL);
+       gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_DESCRIPTION);
 
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_title(column, _("Class"));
@@ -2635,6 +2813,8 @@ static void config_tab_files(GtkWidget *notebook)
        gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
                                                GINT_TO_POINTER(FE_CLASS), NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
+       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_CLASS, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_CLASS), NULL);
+       gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_CLASS);
 
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_title(column, _("Writable"));
@@ -2646,6 +2826,8 @@ static void config_tab_files(GtkWidget *notebook)
        gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
                                                GINT_TO_POINTER(FE_WRITABLE), NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
+       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_WRITABLE, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_WRITABLE), NULL);
+       gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_WRITABLE);
 
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_title(column, _("Sidecar is allowed"));
@@ -2657,7 +2839,8 @@ static void config_tab_files(GtkWidget *notebook)
        gtk_tree_view_column_set_cell_data_func(column, renderer, filter_set_func,
                                                GINT_TO_POINTER(FE_ALLOW_SIDECAR), NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(filter_view), column);
-
+       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(filter_store), FILETYPES_COLUMN_SIDECAR, filter_table_sort_cb, GINT_TO_POINTER(FILETYPES_COLUMN_SIDECAR), NULL);
+       gtk_tree_view_column_set_sort_column_id(column, FILETYPES_COLUMN_SIDECAR);
 
        filter_store_populate();
        gtk_container_add(GTK_CONTAINER(scrolled), filter_view);
@@ -2785,6 +2968,14 @@ static void config_tab_metadata(GtkWidget *notebook)
        pref_checkbox_new_int(group, _("Write metadata on directory change"),
                              options->metadata.confirm_on_dir_change, &c_options->metadata.confirm_on_dir_change);
 
+#ifdef HAVE_SPELL
+#if GTK_CHECK_VERSION(3,20,0)
+       group = pref_group_new(vbox, FALSE, _("Spelling checks"), GTK_ORIENTATION_VERTICAL);
+
+       ct_button = pref_checkbox_new_int(group, _("Check spelling -Requires restart"), options->metadata.check_spelling, &c_options->metadata.check_spelling);
+#endif
+#endif
+
        group = pref_group_new(vbox, FALSE, _("Pre-load metadata"), GTK_ORIENTATION_VERTICAL);
 
        ct_button = pref_checkbox_new_int(group, _("Read metadata in background"),
@@ -3085,6 +3276,9 @@ static void config_tab_keywords(GtkWidget *notebook)
        GtkTextIter iter;
        GtkTextBuffer *buffer;
        gchar *tmp;
+#ifdef HAVE_SPELL
+       GspellTextView *gspell_view;
+#endif
 
        vbox = scrolled_notebook_page(notebook, _("Keywords"));
 
@@ -3103,6 +3297,16 @@ static void config_tab_keywords(GtkWidget *notebook)
        gtk_box_pack_start(GTK_BOX(group), scrolled, TRUE, TRUE, 0);
        gtk_widget_show(scrolled);
 
+#ifdef HAVE_SPELL
+#if GTK_CHECK_VERSION(3,20,0)
+       if (options->metadata.check_spelling)
+               {
+               gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(keyword_text));
+               gspell_text_view_basic_setup(gspell_view);
+               }
+#endif
+#endif
+
        gtk_container_add(GTK_CONTAINER(scrolled), keyword_text);
        gtk_widget_show(keyword_text);
 
@@ -3357,6 +3561,10 @@ static void config_tab_behavior(GtkWidget *notebook)
        button = pref_button_new(NULL, GTK_STOCK_CLEAR, NULL, FALSE,
                                 G_CALLBACK(safe_delete_clear_cb), NULL);
        gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+
+       c_options->file_ops.no_trash = options->file_ops.no_trash;
+       c_options->file_ops.use_system_trash = options->file_ops.use_system_trash;
+
        pref_radiobutton_new(group, ct_button, _("Use system Trash bin"),
                                        options->file_ops.use_system_trash && !options->file_ops.no_trash, G_CALLBACK(use_system_trash_cb), NULL);
 
@@ -3423,6 +3631,8 @@ static void config_tab_behavior(GtkWidget *notebook)
                              options->mousewheel_scrolls, &c_options->mousewheel_scrolls);
        pref_checkbox_new_int(group, _("Navigation by left or middle click on image"),
                              options->image_lm_click_nav, &c_options->image_lm_click_nav);
+       pref_checkbox_new_int(group, _("Open archive by left click on image"),
+                             options->image_l_click_archive, &c_options->image_l_click_archive);
        pref_checkbox_new_int(group, _("Play video by left click on image"),
                              options->image_l_click_video, &c_options->image_l_click_video);
        table = pref_table_new(group, 2, 1, FALSE, FALSE);
@@ -3611,6 +3821,9 @@ static void config_tab_advanced(GtkWidget *notebook)
        GdkPixbufFormat *fm;
        gint i;
        GString *types_string = g_string_new(NULL);
+       GtkWidget *types_string_label;
+       GtkWidget *threads_string_label;
+       GtkWidget *dupes_threads_spin;
 
        vbox = scrolled_notebook_page(notebook, _("Advanced"));
        group = pref_group_new(vbox, FALSE, _("External preview extraction"), GTK_ORIENTATION_VERTICAL);
@@ -3653,8 +3866,7 @@ static void config_tab_advanced(GtkWidget *notebook)
                }
 
        types_string = g_string_prepend(types_string, _("Usable file types:\n"));
-       pref_label_new(group, types_string->str);
-       GtkWidget *types_string_label = gtk_label_new(types_string->str);
+       types_string_label = pref_label_new(group, types_string->str);
        gtk_label_set_line_wrap(GTK_LABEL(types_string_label), TRUE);
 
        pref_spacer(group, PREF_PAD_GROUP);
@@ -3678,6 +3890,19 @@ static void config_tab_advanced(GtkWidget *notebook)
        g_slist_free(formats_list);
        string_list_free(extensions_list);
        g_string_free(types_string, TRUE);
+
+       pref_spacer(group, PREF_PAD_GROUP);
+
+       pref_line(vbox, PREF_PAD_SPACE);
+       group = pref_group_new(vbox, FALSE, _("Thread pool limits"), GTK_ORIENTATION_VERTICAL);
+
+       threads_string_label = pref_label_new(group, "This option limits the number of threads (or cpu cores)\nthat Geeqie will use when running duplicate checks. The default value is 0, which means all available cores will be used.");
+       gtk_label_set_line_wrap(GTK_LABEL(threads_string_label), TRUE);
+
+       pref_spacer(vbox, PREF_PAD_GROUP);
+
+       dupes_threads_spin = pref_spin_new_int(vbox, _("Duplicate check:"), _("max. threads"), 0, get_cpu_cores(), 1, options->threads.duplicates, &c_options->threads.duplicates);
+       gtk_widget_set_tooltip_markup(dupes_threads_spin, _("Set to 0 for unlimited"));
 }
 
 /* stereo tab */
@@ -3881,7 +4106,6 @@ void show_about_window(LayoutWindow *lw)
        gchar *path;
        GString *copyright;
        gchar *timezone_path;
-       gchar *basename;
        ZoneDetect *cd;
        FILE *fp = NULL;
 #define LINE_LENGTH 1000
@@ -3890,21 +4114,21 @@ void show_about_window(LayoutWindow *lw)
        copyright = g_string_new(NULL);
        copyright = g_string_append(copyright, "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");
 
-       path = path_from_utf8(TIMEZONE_DATABASE);
-       basename = g_path_get_basename(path);
-       timezone_path = g_build_filename(get_rc_dir(), basename, NULL);
+       timezone_path = g_build_filename(get_rc_dir(), TIMEZONE_DATABASE_FILE, NULL);
        if (g_file_test(timezone_path, G_FILE_TEST_EXISTS))
                {
                cd = ZDOpenDatabase(timezone_path);
                if (cd)
                        {
                        copyright = g_string_append(copyright, ZDGetNotice(cd));
-                       ZDCloseDatabase(cd);
                        }
+               else
+                       {
+                       log_printf("Error: Init of timezone database %s failed\n", timezone_path);
+                       }
+               ZDCloseDatabase(cd);
                }
-       g_free(path);
        g_free(timezone_path);
-       g_free(basename);
 
        authors[0] = NULL;
        path = g_build_filename(gq_helpdir, "AUTHORS", NULL);
@@ -3980,6 +4204,9 @@ static void timezone_async_ready_cb(GObject *source_object, GAsyncResult *res, g
        GError *error = NULL;
        TZData *tz = data;
        gchar *tmp_filename;
+       gchar *timezone_bin;
+       gchar *tmp_dir = NULL;
+       FileData *fd;
 
        if (!g_cancellable_is_cancelled(tz->cancellable))
                {
@@ -3989,13 +4216,35 @@ static void timezone_async_ready_cb(GObject *source_object, GAsyncResult *res, g
 
        if (g_file_copy_finish(G_FILE(source_object), res, &error))
                {
-               tmp_filename = g_file_get_parse_name(tz->tmp_g_file);
-               move_file(tmp_filename, tz->timezone_database_user);
+               tmp_filename = g_file_get_path(tz->tmp_g_file);
+               fd = file_data_new_simple(tmp_filename);
+               tmp_dir = open_archive(fd);
+
+               if (tmp_dir)
+                       {
+                       timezone_bin = g_build_filename(tmp_dir, TIMEZONE_DATABASE_VERSION, TIMEZONE_DATABASE_FILE, NULL);
+                       if (isfile(timezone_bin))
+                               {
+                               move_file(timezone_bin, tz->timezone_database_user);
+                               }
+                       else
+                               {
+                               warning_dialog(_("Warning: Cannot open timezone database file"), _("See the Log Window"), GTK_STOCK_DIALOG_WARNING, NULL);
+                               }
+
+                       g_free(timezone_bin);
+                       g_free(tmp_dir); // The folder in /tmp is deleted in exit_program_final()
+                       }
+               else
+                       {
+                       warning_dialog(_("Warning: Cannot open timezone database file"), _("See the Log Window"), GTK_STOCK_DIALOG_WARNING, NULL);
+                       }
                g_free(tmp_filename);
+               file_data_unref(fd);
                }
        else
                {
-               file_util_warning_dialog(_("Timezone database download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL);
+               file_util_warning_dialog(_("Error: Timezone database download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL);
                }
 
        g_file_delete(tz->tmp_g_file, NULL, &error);
@@ -4044,7 +4293,7 @@ static void timezone_database_install_cb(GtkWidget *widget, gpointer data)
                }
        else
                {
-               tz->timezone_database_gq = g_file_new_for_uri(TIMEZONE_DATABASE);
+               tz->timezone_database_gq = g_file_new_for_uri(TIMEZONE_DATABASE_WEB);
 
                tz->gd = generic_dialog_new(_("Timezone database"), "download_timezone_database", NULL, TRUE, timezone_cancel_button_cb, tz);