Fix #220, 269: marks do not persist
authorColin Clark <colin.clark@cclark.uk>
Fri, 4 May 2018 15:16:37 +0000 (16:16 +0100)
committerColin Clark <colin.clark@cclark.uk>
Fri, 4 May 2018 15:16:37 +0000 (16:16 +0100)
https://github.com/BestImageViewer/geeqie/issues/220
https://github.com/BestImageViewer/geeqie/issues/269

Marks/image connections can optionally be saved in a text file in the
same folder as History etc.
The option is in Preferences/Behavior - set to save by default.
Also a menu item to clear all marks.

doc/docbook/GuideImageMarks.xml
doc/docbook/GuideMainWindowMenus.xml
doc/docbook/GuideOptionsBehavior.xml
src/filedata.c
src/filedata.h
src/layout_util.c
src/main.c
src/options.c
src/options.h
src/preferences.c
src/rcfile.c

index 45e4219..fc2f6ee 100644 (file)
     <guimenu>Select</guimenu>\r
     menu gives access to the marks operations of setting, filtering and intersection.\r
   </para>\r
-  <para>There are 6 individual marks, any of which can be associated with an image simply by pressing the 1 to 6 keys on the keyboard.</para>\r
+  <para>There are 10 individual marks, any of which can be associated with an image simply by pressing the 0 to 9 keys on the keyboard, where key 0 represents mark 10.</para>\r
   <para>\r
     If the\r
     <guimenu>Show Marks</guimenu>\r
-    menu has been selected, each image will have a set of 6 check-boxes displayed adjacent to it in the file pane in both icon and list mode. In addition a set of 6 check-boxes will be shown at the top of the files pane. Clicking any of these will filter the displayed list.\r
+    menu has been selected, each image will have a set of 10 check-boxes displayed adjacent to it in the file pane in both icon and list mode. In addition a set of 10 check-boxes will be shown at the top of the files pane. Clicking any of these will filter the displayed list.\r
   </para>\r
   <para>\r
     If the\r
     is being displayed, the currently set marks for the image are shown. It is not necessary to include an entry into the overlay template for this to happen.\r
   </para>\r
   <para>\r
-    A keyword can be associated with a single mark by right-clicking on the keyword in the sidebar panel. When a meta-data write operation for a file is triggered either <link linkend="Buttons">manually</link> or as defined in\r
+    A keyword can be associated with a single mark by right-clicking on the keyword in the sidebar panel. When a meta-data write operation for a file is triggered either\r
+    <link linkend="Buttons">manually</link>\r
+    or as defined in\r
     <link linkend="GuideOptionsMetadata" endterm="titleGuideOptionsMetadata" />\r
     , the keyword data indicated by the current set of mark-to-keyword links will be written.\r
   </para>\r
-  <para>Neither marks, nor the associations between keywords and marks, are preserved when Geeqie is shut down.</para>\r
+  <para>\r
+    The associations between keywords and marks is preserved when Geeqie is shut down. The current setting of marks can also be optionally saved - the setting is in the\r
+    <link linkend="Behaviour">Behavior tab of Preferences</link>\r
+    .\r
+  </para>\r
   <para />\r
   <para />\r
 </section>\r
index e3cb747..c60a81b 100644 (file)
           <para>Displays marks in the file list</para>\r
         </listitem>\r
       </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>Clear marks</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Clear all marks for all images</para>\r
+          <warning>\r
+            <para>Marks that are linked to keywords will also be cleared. This may result in a metadata write operation being triggered.</para>\r
+          </warning>\r
+        </listitem>\r
+      </varlistentry>\r
       <varlistentry>\r
         <term>\r
           <menuchoice>\r
index df2584c..a5c6102 100644 (file)
           <para>If selected, a single click will enter a directory, rather than the GTK+ default of a double click.</para>\r
         </listitem>\r
       </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <guilabel>Save marks on exit</guilabel>\r
+        </term>\r
+        <listitem>\r
+          <para>Save all marks that have been set. Note that marks that are linked to a keyword will always be saved irrespective of this setting.</para>\r
+        </listitem>\r
+      </varlistentry>\r
       <varlistentry>\r
         <term>\r
           <guilabel>Recent folder list maximum size</guilabel>\r
index d0c6484..aab31a8 100644 (file)
@@ -29,6 +29,7 @@
 #include "metadata.h"
 #include "trash.h"
 #include "histogram.h"
+#include "secure_save.h"
 
 #include "exif.h"
 
@@ -3159,4 +3160,133 @@ gboolean file_data_unregister_real_time_monitor(FileData *fd)
 
        return TRUE;
 }
+
+/*
+ *-----------------------------------------------------------------------------
+ * Saving marks list, clearing marks
+ * Uses file_data_pool
+ *-----------------------------------------------------------------------------
+ */
+
+static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
+{
+       gchar *file_name = key;
+       GString *result = userdata;
+       FileData *fd;
+
+       if (isfile(file_name))
+               {
+               fd = value;
+               if (fd && fd->marks > 0)
+                       {
+                       g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
+                       }
+               }
+}
+
+gboolean marks_list_load(const gchar *path)
+{
+       FILE *f;
+       gchar s_buf[1024];
+       gchar *pathl;
+       gchar *file_path;
+       gchar *marks_value;
+
+       pathl = path_from_utf8(path);
+       f = fopen(pathl, "r");
+       g_free(pathl);
+       if (!f) return FALSE;
+
+       /* first line must start with Marks comment */
+       if (!fgets(s_buf, sizeof(s_buf), f) ||
+                                       strncmp(s_buf, "#Marks", 6) != 0)
+               {
+               fclose(f);
+               return FALSE;
+               }
+
+       while (fgets(s_buf, sizeof(s_buf), f))
+               {
+               if (s_buf[0]=='#') continue;
+                       file_path = strtok(s_buf, ",");
+                       marks_value = strtok(NULL, ",");
+                       if (isfile(file_path))
+                               {
+                               FileData *fd = file_data_new_group(file_path);
+                               file_data_ref(fd);
+                               gint n = 0;
+                               while (n <= 9)
+                                       {
+                                       gint mark_no = 1 << n;
+                                       if (atoi(marks_value) & mark_no)
+                                               {
+                                               file_data_set_mark(fd, n , 1);
+                                               }
+                                       n++;
+                                       }
+                               }
+               }
+
+       fclose(f);
+       return TRUE;
+}
+
+gboolean marks_list_save(gchar *path, gboolean save)
+{
+       SecureSaveInfo *ssi;
+       gchar *pathl;
+       GString  *marks = g_string_new("");
+
+       pathl = path_from_utf8(path);
+       ssi = secure_open(pathl);
+       g_free(pathl);
+       if (!ssi)
+               {
+               log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
+               return FALSE;
+               }
+
+       secure_fprintf(ssi, "#Marks lists\n");
+
+       if (save)
+               {
+               g_hash_table_foreach(file_data_pool, marks_get_files, marks);
+               }
+       secure_fprintf(ssi, "%s", marks->str);
+       g_string_free(marks, FALSE);
+
+       secure_fprintf(ssi, "#end\n");
+       return (secure_close(ssi) == 0);
+}
+
+static void marks_clear(gpointer key, gpointer value, gpointer userdata)
+{
+       gchar *file_name = key;
+       gint mark_no;
+       gint n;
+       FileData *fd;
+
+       if (isfile(file_name))
+               {
+               fd = value;
+               if (fd && fd->marks > 0)
+                       {
+                       n = 0;
+                       while (n <= 9)
+                               {
+                               mark_no = 1 << n;
+                               if (fd->marks & mark_no)
+                                       {
+                                       file_data_set_mark(fd, n , 0);
+                                       }
+                               n++;
+                               }
+                       }
+               }
+}
+
+void marks_clear_all()
+{
+       g_hash_table_foreach(file_data_pool, marks_clear, NULL);
+}
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 367996d..6aa159f 100644 (file)
@@ -163,5 +163,9 @@ gboolean file_data_unregister_real_time_monitor(FileData *fd);
 
 void read_exif_time_data(FileData *file);
 void read_exif_time_digitized_data(FileData *file);
+
+gboolean marks_list_save(gchar *path, gboolean clear);
+gboolean marks_list_load(const gchar *path);
+void marks_clear_all();
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index c1804a1..ea6396a 100644 (file)
@@ -231,6 +231,39 @@ LayoutWindow *layout_menu_new_window(GtkAction *action, gpointer data)
        return nw;
 }
 
+
+static void clear_marks_cancel_cb(GenericDialog *gd, gpointer data)
+{
+       generic_dialog_close(gd);
+}
+
+static void clear_marks_help_cb(GenericDialog *gd, gpointer data)
+{
+       help_window_show("GuideMainWindowMenus.html");
+}
+
+void layout_menu_clear_marks_ok_cb(GenericDialog *gd, gpointer data)
+{
+       marks_clear_all();
+       generic_dialog_close(gd);
+}
+
+static void layout_menu_clear_marks_cb(GtkAction *action, gpointer data)
+{
+       GenericDialog *gd;
+
+       gd = generic_dialog_new(_("Clear Marks"),
+                               "marks_clear", NULL, FALSE, clear_marks_cancel_cb, NULL);
+       generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION, "Clear all marks?",
+                               "This will clear all marks for all images,\nincluding those linked to keywords",
+                               TRUE);
+       generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, layout_menu_clear_marks_ok_cb, TRUE);
+       generic_dialog_add_button(gd, GTK_STOCK_HELP, NULL,
+                               clear_marks_help_cb, FALSE);
+
+       gtk_widget_show(gd->dialog);
+}
+
 static void layout_menu_new_window_cb(GtkAction *action, gpointer data)
 {
        layout_menu_new_window(action, data);
@@ -1867,7 +1900,7 @@ static GtkActionEntry menu_entries[] = {
   { "SplitDownPane",   NULL,                   N_("_Down Pane"),       "<alt>Down",                    N_("Down Pane"),        CB(layout_menu_split_pane_updown_cb) },
   { "WriteRotation",   NULL,                   N_("_Write orientation to file"),               NULL,           N_("Write orientation to file"),                        CB(layout_menu_write_rotate_cb) },
   { "WriteRotationKeepDate",   NULL,                   N_("_Write orientation to file (preserve timestamp)"),                  NULL,           N_("Write orientation to file (preserve timestamp)"),                   CB(layout_menu_write_rotate_keep_date_cb) },
-
+  { "ClearMarks",      NULL,           N_("Clear Marks..."),                   NULL,           N_("Clear Marks"),                      CB(layout_menu_clear_marks_cb) },
 };
 
 static GtkToggleActionEntry menu_toggle_entries[] = {
@@ -1991,6 +2024,7 @@ static const gchar *menu_ui_description =
 "      <placeholder name='ClipboardSection'/>"
 "      <separator/>"
 "      <menuitem action='ShowMarks'/>"
+"      <menuitem action='ClearMarks'/>"
 "      <placeholder name='MarksSection'/>"
 "      <separator/>"
 "    </menu>"
index d6931a4..c331a07 100644 (file)
@@ -510,6 +510,7 @@ static void parse_command_line_for_debug_option(gint argc, gchar *argv[])
  */
 
 #define RC_HISTORY_NAME "history"
+#define RC_MARKS_NAME "marks"
 
 static void setup_env_path(void)
 {
@@ -537,6 +538,24 @@ static void keys_save(void)
        g_free(path);
 }
 
+static void marks_load(void)
+{
+       gchar *path;
+
+       path = g_build_filename(get_rc_dir(), RC_MARKS_NAME, NULL);
+       marks_list_load(path);
+       g_free(path);
+}
+
+static void marks_save(gboolean save)
+{
+       gchar *path;
+
+       path = g_build_filename(get_rc_dir(), RC_MARKS_NAME, NULL);
+       marks_list_save(path, save);
+       g_free(path);
+}
+
 static void mkdir_if_not_exists(const gchar *path)
 {
        if (isdir(path)) return;
@@ -753,6 +772,8 @@ void exit_program(void)
 
        if (metadata_write_queue_confirm(FALSE, exit_program_write_metadata_cb, NULL)) return;
 
+       options->marks_save ? marks_save(TRUE) : marks_save(FALSE);
+
        if (exit_confirm_dlg()) return;
 
        exit_program_final();
@@ -988,6 +1009,8 @@ gint main(gint argc, gchar *argv[])
        remote_connection = remote_server_init(buf, cd);
        g_free(buf);
 
+       marks_load();
+
        DEBUG_1("%s main: gtk_main", get_exec_time());
        gtk_main();
 #ifdef HAVE_GTHREAD
index 0740489..54843a5 100644 (file)
@@ -79,6 +79,8 @@ ConfOptions *init_options(ConfOptions *options)
        options->fullscreen.disable_saver = TRUE;
        options->fullscreen.screen = -1;
 
+       options->marks_save = TRUE;
+
        memset(&options->image.border_color, 0, sizeof(options->image.border_color));
        memset(&options->image.alpha_color_1, 0, sizeof(options->image.alpha_color_1));
        memset(&options->image.alpha_color_2, 0, sizeof(options->image.alpha_color_2));
index 6847ac7..e37c9e5 100644 (file)
@@ -61,6 +61,8 @@ struct _ConfOptions
 
        gint log_window_lines;
 
+       gboolean marks_save;            // save marks on exit
+
        /* info sidebar component heights */
        struct {
                gint height;
index d242f0f..1d908aa 100644 (file)
@@ -407,6 +407,8 @@ static void config_window_apply(void)
        options->info_comment.height = c_options->info_comment.height;
        options->info_rating.height = c_options->info_rating.height;
 
+       options->marks_save = c_options->marks_save;
+
 #ifdef DEBUG
        set_debug_level(debug_c);
 #endif
@@ -2333,6 +2335,7 @@ static void config_tab_behavior(GtkWidget *notebook)
        GtkWidget *ct_button;
        GtkWidget *spin;
        GtkWidget *table;
+       GtkWidget *marks;
 
        vbox = scrolled_notebook_page(notebook, _("Behavior"));
 
@@ -2386,6 +2389,10 @@ static void config_tab_behavior(GtkWidget *notebook)
        pref_checkbox_new_int(group, _("List directory view uses single click to enter"),
                              options->view_dir_list_single_click_enter, &c_options->view_dir_list_single_click_enter);
 
+       marks = pref_checkbox_new_int(group, _("Save marks on exit"),
+                               options->marks_save, &c_options->marks_save);
+       gtk_widget_set_tooltip_text(marks,"Note that marks linked to a keyword will be saved irrespective of this setting");
+
        pref_spin_new_int(group, _("Recent folder list maximum size"), NULL,
                          1, 50, 1, options->open_recent_list_maxsize, &c_options->open_recent_list_maxsize);
 
index e0ccfd1..5f21bd8 100644 (file)
@@ -343,6 +343,8 @@ static void write_global_attributes(GString *outstr, gint indent)
        WRITE_NL(); WRITE_UINT(*options, log_window_lines);
        WRITE_NL(); WRITE_BOOL(*options, log_window.timer_data);
 
+       WRITE_NL(); WRITE_BOOL(*options, marks_save);
+
        /* File operations Options */
        WRITE_NL(); WRITE_BOOL(*options, file_ops.enable_in_place_rename);
        WRITE_NL(); WRITE_BOOL(*options, file_ops.confirm_delete);
@@ -647,6 +649,8 @@ static gboolean load_global_params(const gchar **attribute_names, const gchar **
                if (READ_INT(*options, log_window_lines)) continue;
                if (READ_BOOL(*options, log_window.timer_data)) continue;
 
+               if (READ_BOOL(*options, marks_save)) continue;
+
                /* Properties dialog options */
                if (READ_CHAR(*options, properties.tabs_order)) continue;