From cb2fa6ae065ec32015e46be3bcf5e7024918fe49 Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Thu, 8 Jun 2017 20:46:52 +0100 Subject: [PATCH] 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. --- doc/docbook/GuideMainWindowMenus.xml | 75 ++++++++++++++++++++++++++++ doc/docbook/GuideReferenceTags.xml | 8 +++ src/bar.c | 11 +++- src/bar_comment.c | 8 +++ src/filedata.c | 28 +++++++++++ src/layout_image.c | 50 +++++++++++++++++++ src/layout_image.h | 2 + src/layout_util.c | 67 +++++++++++++++++++++++++ src/menu.c | 4 ++ src/metadata.h | 1 + src/options.h | 4 ++ src/preferences.c | 4 ++ src/search.c | 66 ++++++++++++++++++++++++ src/typedefs.h | 4 +- 14 files changed, 330 insertions(+), 2 deletions(-) diff --git a/doc/docbook/GuideMainWindowMenus.xml b/doc/docbook/GuideMainWindowMenus.xml index fd4df7bf..754c5bdf 100644 --- a/doc/docbook/GuideMainWindowMenus.xml +++ b/doc/docbook/GuideMainWindowMenus.xml @@ -706,6 +706,81 @@ + + + + + + [ + + + Orientation + Rotate counterclockwise + + + + Rotates the current image counterclockwise 90 degrees, does not modify the file on disk. + + + + + + + + Shift + R + + + Orientation + Rotate 180 + + + + Rotates the current image 180 degrees, does not modify the file on disk. + + + + + + Rating + + + + Set a Rating value for each image. + + + + + + + + Alt+Keypad+n + + + Rating + n + + + + "n" is in the range 0 to 5. Sets the Rating value for the image. + + + + + + + + Alt+Keypad+Minus + + + Rating + -1 + + + + Sets the Rating value to -1 for the image. + + diff --git a/doc/docbook/GuideReferenceTags.xml b/doc/docbook/GuideReferenceTags.xml index 1174926f..c2b01677 100644 --- a/doc/docbook/GuideReferenceTags.xml +++ b/doc/docbook/GuideReferenceTags.xml @@ -283,6 +283,14 @@ Title + + + Xmp.xmp.Rating + + + Rating + + diff --git a/src/bar.c b/src/bar.c index 4fda1269..8088071f 100644 --- a/src/bar.c +++ b/src/bar.c @@ -83,6 +83,14 @@ static const gchar default_config_comment[] = " " " " ""; +static const gchar default_config_rating[] = +"" +" " +" " +" " +" " +" " +""; static const gchar default_config_exif[] = "" @@ -176,6 +184,7 @@ static const KnownPanes known_panes[] = { {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_add(GtkWidget *bar, GtkWidget *pane) 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) diff --git a/src/bar_comment.c b/src/bar_comment.c index 4377b603..b5eb290f 100644 --- a/src/bar_comment.c +++ b/src/bar_comment.c @@ -175,6 +175,10 @@ static void bar_pane_comment_write_config(GtkWidget *pane, GString *outstr, gint { pcd->height = options->info_comment.height; } + if (!g_strcmp0(pcd->pane.id, "rating")) + { + pcd->height = options->info_rating.height; + } WRITE_NL(); WRITE_STRING("pane.id); @@ -311,6 +315,10 @@ GtkWidget *bar_pane_comment_new_from_config(const gchar **attribute_names, const { 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); diff --git a/src/filedata.c b/src/filedata.c index ccabe3f4..b690e812 100644 --- a/src/filedata.c +++ b/src/filedata.c @@ -428,6 +428,7 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean 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_exif_time_data(GList *files) } } +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 @@ gint filelist_sort_compare_filedata(FileData *fa, FileData *fb) 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 @@ GList *filelist_sort(GList *list, SortType method, gboolean ascend) { 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); } diff --git a/src/layout_image.c b/src/layout_image.c index 2e401747..f1a3cb9e 100644 --- a/src/layout_image.c +++ b/src/layout_image.c @@ -1105,6 +1105,56 @@ void layout_image_alter_orientation(LayoutWindow *lw, AlterType type) } } +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; diff --git a/src/layout_image.h b/src/layout_image.h index 54c28226..347c547e 100644 --- a/src/layout_image.h +++ b/src/layout_image.h @@ -64,6 +64,8 @@ void layout_image_alter_orientation(LayoutWindow *lw, AlterType type); 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); diff --git a/src/layout_util.c b/src/layout_util.c index 4a82bfea..735a9e95 100644 --- a/src/layout_util.c +++ b/src/layout_util.c @@ -352,6 +352,55 @@ static void layout_menu_alter_90_cb(GtkAction *action, gpointer data) 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 @@ static GtkActionEntry menu_entries[] = { { "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 @@ static GtkActionEntry menu_entries[] = { { "CloseWindow", GTK_STOCK_CLOSE, N_("C_lose window"), "W", N_("Close window"), CB(layout_menu_close_cb) }, { "Quit", GTK_STOCK_QUIT, N_("_Quit"), "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"), "KP_0", N_("Rating 0"), CB(layout_menu_rating_0_cb) }, + { "Rating1", NULL, N_("_Rating 1"), "KP_1", N_("Rating 1"), CB(layout_menu_rating_1_cb) }, + { "Rating2", NULL, N_("_Rating 2"), "KP_2", N_("Rating 2"), CB(layout_menu_rating_2_cb) }, + { "Rating3", NULL, N_("_Rating 3"), "KP_3", N_("Rating 3"), CB(layout_menu_rating_3_cb) }, + { "Rating4", NULL, N_("_Rating 4"), "KP_4", N_("Rating 4"), CB(layout_menu_rating_4_cb) }, + { "Rating5", NULL, N_("_Rating 5"), "KP_5", N_("Rating 5"), CB(layout_menu_rating_5_cb) }, + { "RatingM1", NULL, N_("_Rating -1"), "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"), "R", N_("Rotate 180"), CB(layout_menu_alter_180_cb) }, { "Mirror", NULL, N_("_Mirror"), "M", N_("Mirror"), CB(layout_menu_alter_mirror_cb) }, @@ -1777,6 +1834,16 @@ static const gchar *menu_ui_description = " " " " " " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " " " " " " " diff --git a/src/menu.c b/src/menu.c index e5bde775..0e221985 100644 --- a/src/menu.c +++ b/src/menu.c @@ -158,6 +158,9 @@ gchar *sort_type_get_text(SortType method) 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 @@ GtkWidget *submenu_add_sort(GtkWidget *menu, GCallback func, gpointer data, 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); diff --git a/src/metadata.h b/src/metadata.h index f92e4473..ec565793 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -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); diff --git a/src/options.h b/src/options.h index f538dedc..4a6c7762 100644 --- a/src/options.h +++ b/src/options.h @@ -67,6 +67,10 @@ struct _ConfOptions gint height; } info_title; + struct { + gint height; + } info_rating; + /* file ops */ struct { gboolean enable_in_place_rename; diff --git a/src/preferences.c b/src/preferences.c index 5b6eab53..6347b7d5 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -370,6 +370,7 @@ static void config_window_apply(void) 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 @@ static void config_tab_general(GtkWidget *notebook) 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 */ diff --git a/src/search.c b/src/search.c index f48fef7c..a2f53b3f 100644 --- a/src/search.c +++ b/src/search.c @@ -138,6 +138,11 @@ struct _SearchData 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 @@ struct _SearchData 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 @@ struct _SearchData MatchType match_dimensions; MatchType match_keywords; MatchType match_comment; + MatchType match_rating; MatchType match_gps; gboolean match_name_enable; @@ -179,6 +187,7 @@ struct _SearchData 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 @@ static const MatchList text_search_menu_comment[] = { { 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 @@ static gboolean search_file_next(SearchData *sd) } } + 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 @@ static void menu_choice_size_cb(GtkWidget *combo, gpointer data) (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 @@ void search_new(FileData *dir_fd, FileData *example_file) 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 @@ void search_new(FileData *dir_fd, FileData *example_file) 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, diff --git a/src/typedefs.h b/src/typedefs.h index 26f594f8..61b2bc2c 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -66,7 +66,8 @@ typedef enum { SORT_CTIME, SORT_PATH, SORT_NUMBER, - SORT_EXIFTIME + SORT_EXIFTIME, + SORT_RATING } SortType; typedef enum { @@ -573,6 +574,7 @@ struct _FileData { 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 -- 2.20.1