changeset 2750:0eac8ea9b1be

Fix #220, 269: marks do not persist 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.
author Colin Clark <colin.clark@cclark.uk>
date Fri, 04 May 2018 16:16:37 +0100
parents 48333f45a526
children 59db4be809a8
files 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
diffstat 11 files changed, 238 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/doc/docbook/GuideImageMarks.xml	Thu May 03 16:03:26 2018 +0100
+++ b/doc/docbook/GuideImageMarks.xml	Fri May 04 16:16:37 2018 +0100
@@ -11,11 +11,11 @@
     <guimenu>Select</guimenu>
     menu gives access to the marks operations of setting, filtering and intersection.
   </para>
-  <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>
+  <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>
   <para>
     If the
     <guimenu>Show Marks</guimenu>
-    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.
+    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.
   </para>
   <para>
     If the
@@ -23,11 +23,17 @@
     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.
   </para>
   <para>
-    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
+    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
     <link linkend="GuideOptionsMetadata" endterm="titleGuideOptionsMetadata" />
     , the keyword data indicated by the current set of mark-to-keyword links will be written.
   </para>
-  <para>Neither marks, nor the associations between keywords and marks, are preserved when Geeqie is shut down.</para>
+  <para>
+    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
+    <link linkend="Behaviour">Behavior tab of Preferences</link>
+    .
+  </para>
   <para />
   <para />
 </section>
--- a/doc/docbook/GuideMainWindowMenus.xml	Thu May 03 16:03:26 2018 +0100
+++ b/doc/docbook/GuideMainWindowMenus.xml	Fri May 04 16:16:37 2018 +0100
@@ -445,6 +445,19 @@
       <varlistentry>
         <term>
           <menuchoice>
+            <guimenu>Clear marks</guimenu>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>Clear all marks for all images</para>
+          <warning>
+            <para>Marks that are linked to keywords will also be cleared. This may result in a metadata write operation being triggered.</para>
+          </warning>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
             <guimenu>Mark n</guimenu>
           </menuchoice>
         </term>
--- a/doc/docbook/GuideOptionsBehavior.xml	Thu May 03 16:03:26 2018 +0100
+++ b/doc/docbook/GuideOptionsBehavior.xml	Fri May 04 16:16:37 2018 +0100
@@ -126,6 +126,14 @@
       </varlistentry>
       <varlistentry>
         <term>
+          <guilabel>Save marks on exit</guilabel>
+        </term>
+        <listitem>
+          <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>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <guilabel>Recent folder list maximum size</guilabel>
         </term>
         <listitem>
--- a/src/filedata.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/filedata.c	Fri May 04 16:16:37 2018 +0100
@@ -29,6 +29,7 @@
 #include "metadata.h"
 #include "trash.h"
 #include "histogram.h"
+#include "secure_save.h"
 
 #include "exif.h"
 
@@ -3159,4 +3160,133 @@
 
 	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: */
--- a/src/filedata.h	Thu May 03 16:03:26 2018 +0100
+++ b/src/filedata.h	Fri May 04 16:16:37 2018 +0100
@@ -163,5 +163,9 @@
 
 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: */
--- a/src/layout_util.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/layout_util.c	Fri May 04 16:16:37 2018 +0100
@@ -231,6 +231,39 @@
 	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 @@
   { "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 @@
 "      <placeholder name='ClipboardSection'/>"
 "      <separator/>"
 "      <menuitem action='ShowMarks'/>"
+"      <menuitem action='ClearMarks'/>"
 "      <placeholder name='MarksSection'/>"
 "      <separator/>"
 "    </menu>"
--- a/src/main.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/main.c	Fri May 04 16:16:37 2018 +0100
@@ -510,6 +510,7 @@
  */
 
 #define RC_HISTORY_NAME "history"
+#define RC_MARKS_NAME "marks"
 
 static void setup_env_path(void)
 {
@@ -537,6 +538,24 @@
 	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 @@
 
 	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 @@
 	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
--- a/src/options.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/options.c	Fri May 04 16:16:37 2018 +0100
@@ -79,6 +79,8 @@
 	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));
--- a/src/options.h	Thu May 03 16:03:26 2018 +0100
+++ b/src/options.h	Fri May 04 16:16:37 2018 +0100
@@ -61,6 +61,8 @@
 
 	gint log_window_lines;
 
+	gboolean marks_save;		// save marks on exit
+
 	/* info sidebar component heights */
 	struct {
 		gint height;
--- a/src/preferences.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/preferences.c	Fri May 04 16:16:37 2018 +0100
@@ -407,6 +407,8 @@
 	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 @@
 	GtkWidget *ct_button;
 	GtkWidget *spin;
 	GtkWidget *table;
+	GtkWidget *marks;
 
 	vbox = scrolled_notebook_page(notebook, _("Behavior"));
 
@@ -2386,6 +2389,10 @@
 	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);
 
--- a/src/rcfile.c	Thu May 03 16:03:26 2018 +0100
+++ b/src/rcfile.c	Fri May 04 16:16:37 2018 +0100
@@ -343,6 +343,8 @@
 	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 @@
 		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;