changeset 2500:eb2ce489ceea

Fix #323: Rating system https://github.com/BestImageViewer/geeqie/issues/323 Initial implementation. Set values either by Edit menu, or Alt+Keypad+n: n is 0 to 5 Alt+keypad+minus sets the value to -1.
author Colin Clark <colin.clark@cclark.uk>
date Thu, 08 Jun 2017 20:46:52 +0100
parents 92345664aa61
children b5cec98159e7
files doc/docbook/GuideMainWindowMenus.xml doc/docbook/GuideReferenceTags.xml src/bar.c src/bar_comment.c src/filedata.c src/layout_image.c src/layout_image.h src/layout_util.c src/menu.c src/metadata.h src/options.h src/preferences.c src/search.c src/typedefs.h
diffstat 14 files changed, 330 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/doc/docbook/GuideMainWindowMenus.xml	Thu Jun 08 11:46:20 2017 +0100
+++ b/doc/docbook/GuideMainWindowMenus.xml	Thu Jun 08 20:46:52 2017 +0100
@@ -711,6 +711,81 @@
           <menuchoice>
             <shortcut>
               <keycombo>
+                <keycap>[</keycap>
+              </keycombo>
+            </shortcut>
+            <guimenu>Orientation</guimenu>
+            <guimenuitem>Rotate counterclockwise</guimenuitem>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>Rotates the current image counterclockwise 90 degrees, does not modify the file on disk.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
+            <shortcut>
+              <keycombo>
+                <keycap>Shift</keycap>
+                <keycap>R</keycap>
+              </keycombo>
+            </shortcut>
+            <guimenu>Orientation</guimenu>
+            <guimenuitem>Rotate 180</guimenuitem>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>Rotates the current image 180 degrees, does not modify the file on disk.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
+            <guimenu>Rating</guimenu>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>Set a Rating value for each image.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
+            <shortcut>
+              <keycombo>
+                <keycap>Alt+Keypad+n</keycap>
+              </keycombo>
+            </shortcut>
+            <guimenu>Rating</guimenu>
+            <guimenuitem>n</guimenuitem>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>"n" is in the range 0 to 5. Sets the Rating value for the image.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
+            <shortcut>
+              <keycombo>
+                <keycap>Alt+Keypad+Minus</keycap>
+              </keycombo>
+            </shortcut>
+            <guimenu>Rating</guimenu>
+            <guimenuitem>-1</guimenuitem>
+          </menuchoice>
+        </term>
+        <listitem>
+          <para>Sets the Rating value to -1 for the image.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <menuchoice>
+            <shortcut>
+              <keycombo>
                 <keycap>Ctrl</keycap>
                 <keycap>S</keycap>
               </keycombo>
--- a/doc/docbook/GuideReferenceTags.xml	Thu Jun 08 11:46:20 2017 +0100
+++ b/doc/docbook/GuideReferenceTags.xml	Thu Jun 08 20:46:52 2017 +0100
@@ -283,6 +283,14 @@
               <para>Title</para>
             </entry>
           </row>
+          <row>
+            <entry>
+              <para>Xmp.xmp.Rating</para>
+            </entry>
+            <entry>
+              <para>Rating</para>
+            </entry>
+          </row>
         </tbody>
       </tgroup>
     </table>
--- a/src/bar.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/bar.c	Thu Jun 08 20:46:52 2017 +0100
@@ -83,6 +83,14 @@
 "        </bar>"
 "    </layout>"
 "</gq>";
+static const gchar default_config_rating[] =
+"<gq>"
+"    <layout id = '_current_'>"
+"        <bar>"
+"            <pane_comment id = 'rating' expanded = 'true' key = '" RATING_KEY "' height = '10' />"
+"        </bar>"
+"    </layout>"
+"</gq>";
 
 static const gchar default_config_exif[] =
 "<gq>"
@@ -176,6 +184,7 @@
 	{PANE_COMMENT,		"title",	N_("Title"),		default_config_title},
 	{PANE_KEYWORDS,		"keywords",	N_("Keywords"),		default_config_keywords},
 	{PANE_COMMENT,		"comment",	N_("Comment"),		default_config_comment},
+	{PANE_COMMENT,		"rating",	N_("Rating"),		default_config_rating},
 	{PANE_EXIF,		"exif",		N_("Exif"),		default_config_exif},
 /* other pre-configured panes */
 	{PANE_EXIF,		"file_info",	N_("File info"),	default_config_file_info},
@@ -567,7 +576,7 @@
 
 void bar_populate_default(GtkWidget *bar)
 {
-	const gchar *populate_id[] = {"histogram", "title", "keywords", "comment", "exif", NULL};
+	const gchar *populate_id[] = {"histogram", "title", "keywords", "comment", "rating", "exif", NULL};
 	const gchar **id = populate_id;
 
 	while (*id)
--- a/src/bar_comment.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/bar_comment.c	Thu Jun 08 20:46:52 2017 +0100
@@ -175,6 +175,10 @@
 		{
 		pcd->height = options->info_comment.height;
 		}
+	if (!g_strcmp0(pcd->pane.id, "rating"))
+		{
+		pcd->height = options->info_rating.height;
+		}
 
 	WRITE_NL(); WRITE_STRING("<pane_comment ");
 	write_char_option(outstr, indent, "id", pcd->pane.id);
@@ -311,6 +315,10 @@
 		{
 		options->info_comment.height = height;
 		}
+	if (!g_strcmp0(id, "rating"))
+		{
+		options->info_rating.height = height;
+		}
 
 	bar_pane_translate_title(PANE_COMMENT, id, &title);
 	ret = bar_pane_comment_new(id, title, key, expanded, height);
--- a/src/filedata.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/filedata.c	Thu Jun 08 20:46:52 2017 +0100
@@ -428,6 +428,7 @@
 	fd->ref = 1;
 	fd->magick = FD_MAGICK;
 	fd->exifdate = 0;
+	fd->rating = 0;
 
 	if (disable_sidecars) fd->disable_grouping = TRUE;
 
@@ -514,6 +515,24 @@
 		}
 }
 
+void set_rating_data(GList *files)
+{
+	gchar *rating_str;
+	DEBUG_1("%s set_rating_data: ...", get_exec_time());
+
+	while (files)
+		{
+		FileData *file = files->data;
+		rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
+		if (rating_str )
+			{
+			file->rating = atoi(rating_str);
+			g_free(rating_str);
+			}
+		files = files->next;
+		}
+}
+
 FileData *file_data_new_no_grouping(const gchar *path_utf8)
 {
 	struct stat st;
@@ -1026,6 +1045,11 @@
 			if (fa->exifdate > fb->exifdate) return 1;
 			/* fall back to name */
 			break;
+		case SORT_RATING:
+			if (fa->rating < fb->rating) return -1;
+			if (fa->rating > fb->rating) return 1;
+			/* fall back to name */
+			break;
 #ifdef HAVE_STRVERSCMP
 		case SORT_NUMBER:
 			ret = strverscmp(fa->name, fb->name);
@@ -1081,6 +1105,10 @@
 		{
 		set_exif_time_data(list);
 		}
+	if (method == SORT_RATING)
+		{
+		set_rating_data(list);
+		}
 	return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
 }
 
--- a/src/layout_image.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/layout_image.c	Thu Jun 08 20:46:52 2017 +0100
@@ -1105,6 +1105,56 @@
 		}
 }
 
+static void image_alter_rating(FileData *fd_n, const gchar *rating)
+{
+	metadata_write_string(fd_n, RATING_KEY, rating);
+}
+
+void layout_image_rating(LayoutWindow *lw, const gchar *rating)
+{
+	if (!layout_valid(&lw)) return;
+
+	GtkTreeModel *store;
+	GList *work;
+	GtkTreeSelection *selection;
+	GtkTreePath *tpath;
+	FileData *fd_n;
+	GtkTreeIter iter;
+	IconData *id;
+
+	if (!lw || !lw->vf) return;
+
+	if (lw->vf->type == FILEVIEW_ICON)
+		{
+		if (!VFICON(lw->vf)->selection) return;
+		work = VFICON(lw->vf)->selection;
+		}
+	else
+		{
+		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(lw->vf->listview));
+		work = gtk_tree_selection_get_selected_rows(selection, &store);
+		}
+
+	while (work)
+		{
+		if (lw->vf->type == FILEVIEW_ICON)
+			{
+			id = work->data;
+			fd_n = id->fd;
+			work = work->next;
+			}
+		else
+			{
+			tpath = work->data;
+			gtk_tree_model_get_iter(store, &iter, tpath);
+			gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
+			work = work->next;
+			}
+
+		image_alter_rating(fd_n, rating);
+		}
+}
+
 void layout_image_reset_orientation(LayoutWindow *lw)
 {
 	ImageWindow *imd= lw->image;
--- a/src/layout_image.h	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/layout_image.h	Thu Jun 08 20:46:52 2017 +0100
@@ -64,6 +64,8 @@
 void layout_image_set_desaturate(LayoutWindow *lw, gboolean desaturate);
 gboolean layout_image_get_desaturate(LayoutWindow *lw);
 
+void layout_image_rating(LayoutWindow *lw, const gchar *rating);
+
 /*
 gint layout_image_stereo_get(LayoutWindow *lw);
 void layout_image_stereo_set(LayoutWindow *lw, gint stereo_mode);
--- a/src/layout_util.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/layout_util.c	Thu Jun 08 20:46:52 2017 +0100
@@ -352,6 +352,55 @@
 	layout_image_alter_orientation(lw, ALTER_ROTATE_90);
 }
 
+static void layout_menu_rating_0_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "0");
+}
+
+static void layout_menu_rating_1_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "1");
+}
+
+static void layout_menu_rating_2_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "2");
+}
+
+static void layout_menu_rating_3_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "3");
+}
+
+static void layout_menu_rating_4_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "4");
+}
+
+static void layout_menu_rating_5_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "5");
+}
+
+static void layout_menu_rating_m1_cb(GtkAction *action, gpointer data)
+{
+	LayoutWindow *lw = data;
+
+	layout_image_rating(lw, "-1");
+}
+
 static void layout_menu_alter_90cc_cb(GtkAction *action, gpointer data)
 {
 	LayoutWindow *lw = data;
@@ -1524,6 +1573,7 @@
   { "EditMenu",		NULL,			N_("_Edit"),				NULL,			NULL,					NULL },
   { "SelectMenu",	NULL,			N_("_Select"),				NULL,			NULL,					NULL },
   { "OrientationMenu",	NULL,			N_("_Orientation"),			NULL,			NULL,					NULL },
+  { "RatingMenu",	NULL,			N_("_Rating"),					NULL,			NULL,					NULL },
   { "ExternalMenu",	NULL,			N_("E_xternal Editors"),		NULL,			NULL,					NULL },
   { "PreferencesMenu",	NULL,			N_("P_references"),			NULL,			NULL,					NULL },
   { "ViewMenu",		NULL,			N_("_View"),				NULL,			NULL,					NULL },
@@ -1569,6 +1619,13 @@
   { "CloseWindow",	GTK_STOCK_CLOSE,	N_("C_lose window"),			"<control>W",		N_("Close window"),			CB(layout_menu_close_cb) },
   { "Quit",		GTK_STOCK_QUIT, 	N_("_Quit"),				"<control>Q",		N_("Quit"),				CB(layout_menu_exit_cb) },
   { "RotateCW",		NULL,			N_("_Rotate clockwise"),		"bracketright",		N_("Rotate clockwise"),			CB(layout_menu_alter_90_cb) },
+  { "Rating0",		NULL,			N_("_Rating 0"),	"<alt>KP_0",	N_("Rating 0"),			CB(layout_menu_rating_0_cb) },
+  { "Rating1",		NULL,			N_("_Rating 1"),	"<alt>KP_1",	N_("Rating 1"),			CB(layout_menu_rating_1_cb) },
+  { "Rating2",		NULL,			N_("_Rating 2"),	"<alt>KP_2",	N_("Rating 2"),			CB(layout_menu_rating_2_cb) },
+  { "Rating3",		NULL,			N_("_Rating 3"),	"<alt>KP_3",	N_("Rating 3"),			CB(layout_menu_rating_3_cb) },
+  { "Rating4",		NULL,			N_("_Rating 4"),	"<alt>KP_4",	N_("Rating 4"),			CB(layout_menu_rating_4_cb) },
+  { "Rating5",		NULL,			N_("_Rating 5"),	"<alt>KP_5",	N_("Rating 5"),			CB(layout_menu_rating_5_cb) },
+  { "RatingM1",		NULL,			N_("_Rating -1"),	"<alt>KP_Subtract",	N_("Rating -1"),	CB(layout_menu_rating_m1_cb) },
   { "RotateCCW",	NULL,			N_("Rotate _counterclockwise"),		"bracketleft",		N_("Rotate counterclockwise"),		CB(layout_menu_alter_90cc_cb) },
   { "Rotate180",	NULL,			N_("Rotate 1_80"),			"<shift>R",		N_("Rotate 180"),			CB(layout_menu_alter_180_cb) },
   { "Mirror",		NULL,			N_("_Mirror"),				"<shift>M",		N_("Mirror"),				CB(layout_menu_alter_mirror_cb) },
@@ -1777,6 +1834,16 @@
 "        <menuitem action='ExifRotate'/>"
 "        <separator/>"
 "      </menu>"
+"      <menu action='RatingMenu'>"
+"        <menuitem action='Rating0'/>"
+"        <menuitem action='Rating1'/>"
+"        <menuitem action='Rating2'/>"
+"        <menuitem action='Rating3'/>"
+"        <menuitem action='Rating4'/>"
+"        <menuitem action='Rating5'/>"
+"        <menuitem action='RatingM1'/>"
+"        <separator/>"
+"      </menu>"
 "      <menuitem action='SaveMetadata'/>"
 "      <placeholder name='PropertiesSection'/>"
 "      <separator/>"
--- a/src/menu.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/menu.c	Thu Jun 08 20:46:52 2017 +0100
@@ -158,6 +158,9 @@
 		case SORT_NUMBER:
 			return _("Sort by number");
 			break;
+		case SORT_RATING:
+			return _("Sort by rating");
+			break;
 		case SORT_NAME:
 		default:
 			return _("Sort by name");
@@ -205,6 +208,7 @@
 	submenu_add_sort_item(submenu, func, SORT_CTIME, show_current, type);
 	submenu_add_sort_item(submenu, func, SORT_EXIFTIME, show_current, type);
 	submenu_add_sort_item(submenu, func, SORT_SIZE, show_current, type);
+	submenu_add_sort_item(submenu, func, SORT_RATING, show_current, type);
 	if (include_path) submenu_add_sort_item(submenu, func, SORT_PATH, show_current, type);
 	if (include_none) submenu_add_sort_item(submenu, func, SORT_NONE, show_current, type);
 
--- a/src/metadata.h	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/metadata.h	Thu Jun 08 20:46:52 2017 +0100
@@ -25,6 +25,7 @@
 #define COMMENT_KEY "Xmp.dc.description"
 #define KEYWORD_KEY "Xmp.dc.subject"
 #define ORIENTATION_KEY "Xmp.tiff.Orientation"
+#define RATING_KEY "Xmp.xmp.Rating"
 
 void metadata_cache_free(FileData *fd);
 
--- a/src/options.h	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/options.h	Thu Jun 08 20:46:52 2017 +0100
@@ -67,6 +67,10 @@
 		gint height;
 	} info_title;
 
+	struct {
+		gint height;
+	} info_rating;
+
 	/* file ops */
 	struct {
 		gboolean enable_in_place_rename;
--- a/src/preferences.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/preferences.c	Thu Jun 08 20:46:52 2017 +0100
@@ -370,6 +370,7 @@
 	options->info_keywords.height = c_options->info_keywords.height;
 	options->info_title.height = c_options->info_title.height;
 	options->info_comment.height = c_options->info_comment.height;
+	options->info_rating.height = c_options->info_rating.height;
 
 #ifdef DEBUG
 	set_debug_level(debug_c);
@@ -1486,6 +1487,9 @@
 	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);
 }
 
 /* image tab */
--- a/src/search.c	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/search.c	Thu Jun 08 20:46:52 2017 +0100
@@ -138,6 +138,11 @@
 	GtkWidget *menu_comment;
 	GtkWidget *entry_comment;
 
+	GtkWidget *check_rating;
+	GtkWidget *menu_rating;
+	GtkWidget *spin_rating;
+	GtkWidget *spin_rating_end;
+
 	FileData *search_dir_fd;
 	gboolean   search_path_recurse;
 	gchar *search_name;
@@ -159,6 +164,8 @@
 	CacheData *search_similarity_cd;
 	GList *search_keyword_list;
 	gchar *search_comment;
+	gint   search_rating;
+	gint   search_rating_end;
 	gboolean   search_comment_match_case;
 	gboolean   search_date_exif;
 
@@ -170,6 +177,7 @@
 	MatchType match_dimensions;
 	MatchType match_keywords;
 	MatchType match_comment;
+	MatchType match_rating;
 	MatchType match_gps;
 
 	gboolean match_name_enable;
@@ -179,6 +187,7 @@
 	gboolean match_similarity_enable;
 	gboolean match_keywords_enable;
 	gboolean match_comment_enable;
+	gboolean match_rating_enable;
 
 	GList *search_folder_list;
 	GList *search_done_list;
@@ -266,6 +275,14 @@
 	{ N_("miss"),		SEARCH_MATCH_NONE }
 };
 
+
+static const MatchList text_search_menu_rating[] = {
+	{ N_("equal to"),	SEARCH_MATCH_EQUAL },
+	{ N_("less than"),	SEARCH_MATCH_UNDER },
+	{ N_("greater than"),	SEARCH_MATCH_OVER },
+	{ N_("between"),	SEARCH_MATCH_BETWEEN }
+};
+
 static const MatchList text_search_menu_gps[] = {
 	{ N_("not geocoded"),	SEARCH_MATCH_NONE },
 	{ N_("less than"),	SEARCH_MATCH_UNDER },
@@ -1962,6 +1979,31 @@
 			}
 		}
 
+	if (match && sd->match_rating_enable)
+		{
+		tested = TRUE;
+		match = FALSE;
+		gint rating;
+
+		rating = metadata_read_int(fd, RATING_KEY, 0);
+		if (sd->match_rating == SEARCH_MATCH_EQUAL)
+			{
+			match = (rating == sd->search_rating);
+			}
+		else if (sd->match_rating == SEARCH_MATCH_UNDER)
+			{
+			match = (rating < sd->search_rating);
+			}
+		else if (sd->match_rating == SEARCH_MATCH_OVER)
+			{
+			match = (rating > sd->search_rating);
+			}
+		else if (sd->match_rating == SEARCH_MATCH_BETWEEN)
+			{
+			match = MATCH_IS_BETWEEN(rating, sd->search_rating, sd->search_rating_end);
+			}
+		}
+
 	if (match && sd->match_gps_enable)
 		{
 		/* Calculate the distance the image is from the specified origin.
@@ -2538,6 +2580,16 @@
 				(sd->match_size == SEARCH_MATCH_BETWEEN));
 }
 
+static void menu_choice_rating_cb(GtkWidget *combo, gpointer data)
+{
+	SearchData *sd = data;
+
+	if (!menu_choice_get_match_type(combo, &sd->match_rating)) return;
+
+	menu_choice_set_visible(gtk_widget_get_parent(sd->spin_rating_end),
+				(sd->match_rating == SEARCH_MATCH_BETWEEN));
+}
+
 static void menu_choice_date_cb(GtkWidget *combo, gpointer data)
 {
 	SearchData *sd = data;
@@ -2766,6 +2818,7 @@
 	sd->match_dimensions = SEARCH_MATCH_EQUAL;
 	sd->match_keywords = SEARCH_MATCH_ALL;
 	sd->match_comment = SEARCH_MATCH_CONTAINS;
+	sd->match_rating = SEARCH_MATCH_EQUAL;
 
 	sd->match_name_enable = TRUE;
 
@@ -2934,6 +2987,19 @@
 	pref_checkbox_new_int(hbox, _("Match case"),
 			      sd->search_comment_match_case, &sd->search_comment_match_case);
 
+	/* Search for image rating */
+	hbox = menu_choice(sd->box_search, &sd->check_rating, &sd->menu_rating,
+			   _("Image rating is"), &sd->match_rating_enable,
+			   text_search_menu_rating, sizeof(text_search_menu_rating) / sizeof(MatchList),
+			   G_CALLBACK(menu_choice_rating_cb), sd);
+	sd->spin_size = menu_spin(hbox, -1, 5, sd->search_rating,
+				  G_CALLBACK(menu_choice_spin_cb), &sd->search_rating);
+	hbox2 = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
+	gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
+	pref_label_new(hbox2, _("and"));
+	sd->spin_rating_end = menu_spin(hbox2, -1, 5, sd->search_rating_end,
+				      G_CALLBACK(menu_choice_spin_cb), &sd->search_rating_end);
+
 	/* Search for images within a specified range of a lat/long coordinate
 	*/
 	hbox = menu_choice(sd->box_search, &sd->check_gps, &sd->menu_gps,
--- a/src/typedefs.h	Thu Jun 08 11:46:20 2017 +0100
+++ b/src/typedefs.h	Thu Jun 08 20:46:52 2017 +0100
@@ -66,7 +66,8 @@
 	SORT_CTIME,
 	SORT_PATH,
 	SORT_NUMBER,
-	SORT_EXIFTIME
+	SORT_EXIFTIME,
+	SORT_RATING
 } SortType;
 
 typedef enum {
@@ -573,6 +574,7 @@
 	time_t exifdate;
 	GHashTable *modified_xmp; // hash table which contains unwritten xmp metadata in format: key->list of string values
 	GList *cached_metadata;
+	gint rating;
 };
 
 struct _LayoutOptions