The first stage of this is done.  Patch attached.  More comments inline and 
below.

Omari Stephens wrote:
Noah Slater wrote:
Hello,

I use Geeqie to select photographs from very large collections, such as the
thousands I might take on a shoot. As it stands, run through the photographs in
a directory, deleting the ones that I don't think should make it to the next
stage of the selection process. I usually do this a number of times, using
different directories, until I have a small handful that I like.

Because I the selection process is subjective, I add safety measures such as
copying the photographs to a new directory for each run, or running through a
copy of the same directory twice so that I can merge the selection pools of two
identical runs. This is a tedious process, and prone to errors.

I would like to suggest the following features:

  * A scorecard for each photo that can be incremented or decremented using
    keyboard shortcuts. Two obvious shortcuts would be "-" and "=". After
    pressing a key, a notification area could display the score.

I am working on this. My plan for right now is to implement it as a simple hack on top of the keywords system. I could use a keyword "score:6" for storage, and then it'd just need buttons with keyboard mappings for incrementing or decrementing the score.
I ended up doing this the Right Way and using the XMP xap:Rating tag. If you've got photos that you added ratings to with other programs (Lightroom, Bridge, etc.) you should see them. Changes you make should show up in those programs (for now; more below).

  * A way of sorting on the list of photographs by score.

As Vlad suggested, this will be more difficult to do properly. It might be possible to come up with a nice hack that works well; we'll see. I haven't yet poked at the sorting code.

I still haven't looked at this yet. Worst comes to worst, I'm just going to write a cache of some sort.


Next steps, in approximate order:
1) Add buttons and keyboard shortcuts
2) Probably get rid of the limited [0,5] range. Note that I'm not sure what other apps will do if they see data outside of that range. If you've got Lightroom or Bridge, it would be nice if you could try out the next version of the patch (or just manually edit the XMP) and see what happens.
3) Figure out whole the sorting situation.

--xsdg

PS: patch is also on the net at
http://ocaml.xvm.mit.edu/~xsdg/stuff/geeqie/ratings.v1.patch.txt
commit 9c3b2fabe4e990c4d95d03eb77f6eb285376b7a6
Author: Omari Stephens <x...@xsdg.org>
Date:   Sun Nov 15 00:57:03 2009 +0000

    Add support for reading and writing Rating xmp metadata
    
    - Add a constant to metadata.h to reference the Xmp.xmp.Rating tag
    - Add a UI in the metadata bar
    - Add associated configuration stuff

diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am
index 144b090..9528d30 100644
--- a/trunk/src/Makefile.am
+++ b/trunk/src/Makefile.am
@@ -94,6 +94,8 @@ geeqie_SOURCES = \
 	bar_keywords.h	\
 	bar_exif.c	\
 	bar_exif.h	\
+	bar_rating.c	\
+	bar_rating.h	\
 	bar_sort.c	\
 	bar_sort.h	\
 	cache.c		\
diff --git a/trunk/src/bar.c b/trunk/src/bar.c
index 52d6a54..4d64b71 100644
--- a/trunk/src/bar.c
+++ b/trunk/src/bar.c
@@ -25,6 +25,7 @@
 #include "ui_menu.h"
 #include "bar_comment.h"
 #include "bar_keywords.h"
+#include "bar_rating.h"
 #include "bar_exif.h"
 #include "bar_histogram.h"
 #include "histogram.h"
@@ -76,6 +77,15 @@ static const gchar default_config_comment[] =
 "    </layout>"
 "</gq>";
 
+static const gchar default_config_rating[] =
+"<gq>"
+"    <layout id = '_current_'>"
+"        <bar>"
+"            <pane_rating id = 'rating' expanded = 'true' key = '" RATING_KEY "' />"
+"        </bar>"
+"    </layout>"
+"</gq>";
+
 static const gchar default_config_exif[] = 
 "<gq>"
 "    <layout id = '_current_'>"
@@ -178,6 +188,7 @@ static const KnownPanes known_panes[] = {
 	{PANE_GPS,		"gps",	N_("GPS Map"),	default_config_gps},
 #endif
 #endif
+	{PANE_RATING,		"rating",       N_("Rating"),   default_config_rating},
 	{PANE_UNDEF,		NULL,		NULL,			NULL}
 };
 
diff --git a/trunk/src/bar.h b/trunk/src/bar.h
index 61dc4be..d24acf3 100644
--- a/trunk/src/bar.h
+++ b/trunk/src/bar.h
@@ -20,7 +20,8 @@ typedef enum {
 	PANE_EXIF,
 	PANE_HISTOGRAM,
 	PANE_KEYWORDS,
-	PANE_GPS
+	PANE_GPS,
+	PANE_RATING
 } PaneType;
 
 typedef struct _PaneData PaneData;
diff --git a/trunk/src/bar_rating.c b/trunk/src/bar_rating.c
new file mode 100644
index 0000000..684e713
--- /dev/null
+++ b/trunk/src/bar_rating.c
@@ -0,0 +1,344 @@
+/*
+ * Geeqie
+ * (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2009 The Geeqie Team
+ *
+ * Author: Omari Stephens
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+
+#include "main.h"
+#include "bar_rating.h"
+
+#include "bar.h"
+#include "metadata.h"
+#include "filedata.h"
+#include "ui_menu.h"
+#include "ui_misc.h"
+#include "rcfile.h"
+#include "layout.h"
+
+static void bar_pane_rating_changed(GtkSpinButton *button, gpointer data);
+
+/*
+ *-------------------------------------------------------------------
+ * rating utils
+ *-------------------------------------------------------------------
+ */
+
+
+
+typedef struct _PaneRatingData PaneRatingData;
+struct _PaneRatingData
+{
+	PaneData pane;
+	GtkWidget *widget;
+	GtkWidget *spinbutton;
+	FileData *fd;
+	gchar *key;
+	gint height;
+};
+
+
+static void bar_pane_rating_write(PaneRatingData *prd)
+{
+	guint value;
+
+	if (!prd->fd) return;
+
+	value = gtk_spin_button_get_value_as_int(prd->spinbutton);
+
+	metadata_write_int(prd->fd, prd->key, value);
+}
+
+
+static void bar_pane_rating_update(PaneRatingData *prd)
+{
+	guint value;
+
+	g_signal_handlers_block_by_func(prd->spinbutton, bar_pane_rating_changed, prd);
+
+	value = metadata_read_int(prd->fd, prd->key, (guint64)-1);
+
+	if(value != (guint64)-1)
+		{
+		gtk_spin_button_set_value(prd->spinbutton, value);
+		}
+
+	g_signal_handlers_unblock_by_func(prd->spinbutton, bar_pane_rating_changed, prd);
+
+	gtk_widget_set_sensitive(prd->spinbutton, (prd->fd != NULL));
+}
+
+static void bar_pane_rating_set_selection(PaneRatingData *prd, gboolean overwrite)
+{
+	GList *list = NULL;
+	GList *work;
+	gint value = -1;
+
+	value = gtk_spin_button_get_value_as_int(prd->spinbutton);
+
+	list = layout_selection_list(prd->pane.lw);
+	list = file_data_process_groups_in_selection(list, FALSE, NULL);
+
+	work = list;
+	while (work)
+		{
+		FileData *fd = work->data;
+		work = work->next;
+		if (fd == prd->fd) continue;
+
+		if (overwrite || (metadata_read_int(fd, prd->key, -1) == (guint64)-1))
+			{
+			metadata_write_int(fd, prd->key, value);
+			}
+		}
+
+	filelist_free(list);
+}
+
+/*
+static void bar_pane_comment_sel_add_cb(GtkWidget *button, gpointer data)
+{
+	PaneCommentData *pcd = data;
+
+	bar_pane_comment_set_selection(pcd, TRUE);
+}
+
+
+static void bar_pane_comment_sel_replace_cb(GtkWidget *button, gpointer data)
+{
+	PaneCommentData *pcd = data;
+
+	bar_pane_comment_set_selection(pcd, FALSE);
+}
+*/
+
+static void bar_pane_rating_set_fd(GtkWidget *bar, FileData *fd)
+{
+	PaneRatingData *prd;
+
+	prd = g_object_get_data(G_OBJECT(bar), "pane_data");
+	if (!prd) return;
+
+	file_data_unref(prd->fd);
+	prd->fd = file_data_ref(fd);
+
+	bar_pane_rating_update(prd);
+}
+
+static gint bar_pane_rating_event(GtkWidget *bar, GdkEvent *event)
+{
+	PaneRatingData *prd;
+
+	prd = g_object_get_data(G_OBJECT(bar), "pane_data");
+	if (!prd) return FALSE;
+
+	if (GTK_WIDGET_HAS_FOCUS(prd->spinbutton))
+		{
+		return gtk_widget_event(prd->spinbutton, event);
+		}
+
+	return FALSE;
+}
+
+static void bar_pane_rating_write_config(GtkWidget *pane, GString *outstr, gint indent)
+{
+	PaneRatingData *prd;
+
+	prd = g_object_get_data(G_OBJECT(pane), "pane_data");
+	if (!prd) return;
+
+	WRITE_NL(); WRITE_STRING("<pane_rating ");
+	write_char_option(outstr, indent, "id", prd->pane.id);
+	write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(prd->pane.title)));
+	WRITE_BOOL(prd->pane, expanded);
+	WRITE_CHAR(*prd, key);
+	WRITE_INT(*prd, height);
+	WRITE_STRING("/>");
+}
+
+static void bar_pane_rating_notify_cb(FileData *fd, NotifyType type, gpointer data)
+{
+	PaneRatingData *prd = data;
+	if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == prd->fd)
+		{
+		DEBUG_1("Notify pane_rating: %s %04x", fd->path, type);
+
+		bar_pane_rating_update(prd);
+		}
+}
+
+static void bar_pane_rating_changed(GtkSpinButton *button, gpointer data)
+{
+	PaneRatingData *prd = data;
+
+	file_data_unregister_notify_func(bar_pane_rating_notify_cb, prd);
+	bar_pane_rating_write(prd);
+	file_data_register_notify_func(bar_pane_rating_notify_cb, prd, NOTIFY_PRIORITY_LOW);
+}
+
+
+/*
+static void bar_pane_comment_populate_popup(GtkTextView *textview, GtkMenu *menu, gpointer data)
+{
+	PaneCommentData *pcd = data;
+
+	menu_item_add_divider(GTK_WIDGET(menu));
+	menu_item_add_stock(GTK_WIDGET(menu), _("Add text to selected files"), GTK_STOCK_ADD, G_CALLBACK(bar_pane_comment_sel_add_cb), pcd);
+	menu_item_add_stock(GTK_WIDGET(menu), _("Replace existing text in selected files"), GTK_STOCK_CONVERT, G_CALLBACK(bar_pane_comment_sel_replace_cb), data);
+}
+*/
+
+#if 0
+static void bar_pane_comment_close(GtkWidget *bar)
+{
+	PaneCommentData *pcd;
+
+	pcd = g_object_get_data(G_OBJECT(bar), "pane_data");
+	if (!pcd) return;
+
+	gtk_widget_destroy(pcd->comment_view);
+}
+#endif
+
+static void bar_pane_rating_destroy(GtkWidget *widget, gpointer data)
+{
+	// FIXME: make sure we free the spinbutton
+	PaneRatingData *prd = data;
+
+	file_data_unregister_notify_func(bar_pane_rating_notify_cb, prd);
+
+	file_data_unref(prd->fd);
+	g_free(prd->key);
+
+	g_free(prd->pane.id);
+
+	g_free(prd);
+}
+
+
+static GtkWidget *bar_pane_rating_new(const gchar *id, const gchar *title, const gchar *key,
+	gboolean expanded, gint height)
+{
+	PaneRatingData *prd;
+	GtkWidget *hbox;
+
+	prd = g_new0(PaneRatingData, 1);
+
+	prd->pane.pane_set_fd = bar_pane_rating_set_fd;
+	prd->pane.pane_event = bar_pane_rating_event;
+	prd->pane.pane_write_config = bar_pane_rating_write_config;
+	prd->pane.title = bar_pane_expander_title(title);
+	prd->pane.id = g_strdup(id);
+	prd->pane.type = PANE_RATING;
+
+	prd->pane.expanded = expanded;
+
+	prd->key = g_strdup(key);
+	prd->height = height;
+
+	// HBOX
+	hbox = gtk_hbox_new(FALSE, 0);
+
+	prd->widget = hbox;
+	g_object_set_data(G_OBJECT(prd->widget), "pane_data", prd);
+	g_signal_connect(G_OBJECT(prd->widget), "destroy",
+			 G_CALLBACK(bar_pane_rating_destroy), prd);
+
+	gtk_widget_set_size_request(prd->widget, -1, height);
+	gtk_widget_show(hbox);
+
+	// SPIN BUTTON
+	prd->spinbutton = gtk_spin_button_new_with_range(0.0, 5.0, 1.0);
+	gtk_spin_button_set_numeric(prd->spinbutton, TRUE);
+	gtk_spin_button_set_snap_to_ticks(prd->spinbutton, TRUE);
+
+	gtk_box_pack_start(hbox, prd->spinbutton, TRUE, TRUE, 0);
+//	g_signal_connect(G_OBJECT(pcd->comment_view), "populate-popup",
+//			 G_CALLBACK(bar_pane_comment_populate_popup), pcd);
+	gtk_widget_show(prd->spinbutton);
+
+	g_signal_connect(G_OBJECT(prd->spinbutton), "value-changed",
+			 G_CALLBACK(bar_pane_rating_changed), prd);
+
+
+	file_data_register_notify_func(bar_pane_rating_notify_cb, prd, NOTIFY_PRIORITY_LOW);
+
+	return prd->widget;
+}
+
+GtkWidget *bar_pane_rating_new_from_config(const gchar **attribute_names,
+	const gchar **attribute_values)
+{
+	gchar *title = NULL;
+	gchar *key = g_strdup(RATING_KEY);
+	gboolean expanded = TRUE;
+	gint height = 50;
+	gchar *id = g_strdup("rating");
+	GtkWidget *ret;
+
+	while (*attribute_names)
+		{
+		const gchar *option = *attribute_names++;
+		const gchar *value = *attribute_values++;
+
+		if (READ_CHAR_FULL("title", title)) continue;
+		if (READ_CHAR_FULL("key", key)) continue;
+		if (READ_BOOL_FULL("expanded", expanded)) continue;
+		if (READ_INT_FULL("height", height)) continue;
+		if (READ_CHAR_FULL("id", id)) continue;
+
+
+		log_printf("unknown attribute %s = %s\n", option, value);
+		}
+
+	bar_pane_translate_title(PANE_RATING, id, &title);
+	ret = bar_pane_rating_new(id, title, key, expanded, height);
+	g_free(title);
+	g_free(key);
+	g_free(id);
+	return ret;
+}
+
+void bar_pane_rating_update_from_config(GtkWidget *pane, const gchar **attribute_names,
+	const gchar **attribute_values)
+{
+	PaneRatingData *prd;
+
+	prd = g_object_get_data(G_OBJECT(pane), "pane_data");
+	if (!prd) return;
+
+	gchar *title = NULL;
+
+	while (*attribute_names)
+		{
+		const gchar *option = *attribute_names++;
+		const gchar *value = *attribute_values++;
+
+		if (READ_CHAR_FULL("title", title)) continue;
+		if (READ_CHAR_FULL("key", prd->key)) continue;
+		if (READ_BOOL_FULL("expanded", prd->pane.expanded)) continue;
+		if (READ_INT_FULL("height", prd->height)) continue;
+		if (READ_CHAR_FULL("id", prd->pane.id)) continue;
+
+
+		log_printf("unknown attribute %s = %s\n", option, value);
+		}
+
+	if (title)
+		{
+		bar_pane_translate_title(PANE_RATING, prd->pane.id, &title);
+		gtk_label_set_text(GTK_LABEL(prd->pane.title), title);
+		g_free(title);
+		}
+	gtk_widget_set_size_request(prd->widget, -1, prd->height);
+	bar_update_expander(pane);
+	bar_pane_rating_update(prd);
+}
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/trunk/src/bar_rating.h b/trunk/src/bar_rating.h
new file mode 100644
index 0000000..45302c5
--- /dev/null
+++ b/trunk/src/bar_rating.h
@@ -0,0 +1,23 @@
+/*
+ * Geeqie
+ * (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2009 The Geeqie Team
+ *
+ * Author: Omari Stephens
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+
+#ifndef BAR_RATING_H
+#define BAR_RATING_H
+
+GtkWidget *bar_pane_rating_new_from_config(const gchar **attribute_names,
+	const gchar **attribute_values);
+void bar_pane_rating_update_from_config(GtkWidget *pane, const gchar **attribute_names,
+	const gchar **attribute_values);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/trunk/src/metadata.h b/trunk/src/metadata.h
index 639da6d..be265cc 100644
--- a/trunk/src/metadata.h
+++ b/trunk/src/metadata.h
@@ -17,6 +17,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"
 
 gboolean metadata_write_queue_remove(FileData *fd);
 gboolean metadata_write_queue_remove_list(GList *list);
diff --git a/trunk/src/rcfile.c b/trunk/src/rcfile.c
index 4053b58..babd0a4 100644
--- a/trunk/src/rcfile.c
+++ b/trunk/src/rcfile.c
@@ -22,6 +22,7 @@
 #include "bar_histogram.h"
 #include "bar_keywords.h"
 #include "bar_sort.h"
+#include "bar_rating.h"
 #include "editors.h"
 #include "filefilter.h"
 #include "misc.h"
@@ -918,6 +919,22 @@ static void options_parse_bar(GQParserData *parser_data, GMarkupParseContext *co
 			}
 		options_parse_func_push(parser_data, options_parse_leaf, NULL, NULL);
 		}
+	else if (g_ascii_strcasecmp(element_name, "pane_rating") == 0)
+		{
+		GtkWidget *pane = bar_find_pane_by_id(bar, PANE_RATING,
+			options_get_id(attribute_names, attribute_values));
+		if (pane)
+			{
+			bar_pane_rating_update_from_config(pane, attribute_names,
+				attribute_values);
+			}
+		else
+			{
+			pane = bar_pane_rating_new_from_config(attribute_names, attribute_values);
+			bar_add(bar, pane);
+			}
+		options_parse_func_push(parser_data, options_parse_leaf, NULL, NULL);
+		}
 #ifdef HAVE_LIBCHAMPLAIN
 #ifdef HAVE_LIBCHAMPLAIN_GTK
 	else if (g_ascii_strcasecmp(element_name, "pane_gps") == 0)
------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Geeqie-devel mailing list
Geeqie-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/geeqie-devel

Reply via email to