Spelling checks for keywords auto-completion list
[geeqie.git] / src / preferences.c
index 47ebd29..dce9e05 100644 (file)
 #endif
 #endif
 
+#ifdef HAVE_SPELL
+#include <gspell/gspell.h>
+#endif
+
 #define EDITOR_NAME_MAX_LENGTH 32
 #define EDITOR_COMMAND_MAX_LENGTH 1024
 
@@ -112,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"),
@@ -328,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;
 
@@ -340,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;
 
 
@@ -376,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;
@@ -404,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) |
@@ -714,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
 {
@@ -1940,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"));
@@ -2132,9 +2190,12 @@ static void config_tab_general(GtkWidget *notebook)
        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);
@@ -2146,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);
 
@@ -2161,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"));
@@ -2243,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);
@@ -2293,11 +2362,6 @@ 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;
-
-       group = pref_group_new(vbox, FALSE, _("Convenience"), GTK_ORIENTATION_VERTICAL);
-
-       pref_checkbox_new_int(group, _("Auto rotate proofs using Exif information"),
-                             options->image.exif_proof_rotate_enable, &c_options->image.exif_proof_rotate_enable);
 }
 
 /* windows tab */
@@ -2577,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;
@@ -2658,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);
@@ -2672,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"));
@@ -2689,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"));
@@ -2700,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"));
@@ -2711,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);
@@ -2839,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"),
@@ -3139,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"));
 
@@ -3157,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);
 
@@ -3411,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);
 
@@ -3477,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);
@@ -3950,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
@@ -3959,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);
@@ -4049,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))
                {
@@ -4058,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);
@@ -4113,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);