Fix #323: Rating system
authorColin Clark <colin.clark@cclark.uk>
Thu, 8 Jun 2017 19:46:52 +0000 (20:46 +0100)
committerColin Clark <colin.clark@cclark.uk>
Thu, 8 Jun 2017 19:46:52 +0000 (20:46 +0100)
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.

14 files changed:
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

index fd4df7b..754c5bd 100644 (file)
           </warning>\r
         </listitem>\r
       </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>[</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Orientation</guimenu>\r
+            <guimenuitem>Rotate counterclockwise</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Rotates the current image counterclockwise 90 degrees, does not modify the file on disk.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>Shift</keycap>\r
+                <keycap>R</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Orientation</guimenu>\r
+            <guimenuitem>Rotate 180</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Rotates the current image 180 degrees, does not modify the file on disk.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>Rating</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Set a Rating value for each image.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>Alt+Keypad+n</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Rating</guimenu>\r
+            <guimenuitem>n</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>"n" is in the range 0 to 5. Sets the Rating value for the image.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>Alt+Keypad+Minus</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Rating</guimenu>\r
+            <guimenuitem>-1</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Sets the Rating value to -1 for the image.</para>\r
+        </listitem>\r
+      </varlistentry>\r
       <varlistentry>\r
         <term>\r
           <menuchoice>\r
index 1174926..c2b0167 100644 (file)
               <para>Title</para>\r
             </entry>\r
           </row>\r
+          <row>\r
+            <entry>\r
+              <para>Xmp.xmp.Rating</para>\r
+            </entry>\r
+            <entry>\r
+              <para>Rating</para>\r
+            </entry>\r
+          </row>\r
         </tbody>\r
       </tgroup>\r
     </table>\r
index 4fda126..8088071 100644 (file)
--- a/src/bar.c
+++ b/src/bar.c
@@ -83,6 +83,14 @@ static const gchar default_config_comment[] =
 "        </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 @@ 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)
index 4377b60..b5eb290 100644 (file)
@@ -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_comment ");
        write_char_option(outstr, indent, "id", pcd->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);
index ccabe3f..b690e81 100644 (file)
@@ -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);
 }
 
index 2e40174..f1a3cb9 100644 (file)
@@ -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;
index 54c2822..347c547 100644 (file)
@@ -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);
index 4a82bfe..735a9e9 100644 (file)
@@ -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"),                    "<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 @@ static const gchar *menu_ui_description =
 "        <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/>"
index e5bde77..0e22198 100644 (file)
@@ -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);
 
index f92e447..ec56579 100644 (file)
@@ -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);
 
index f538ded..4a6c776 100644 (file)
@@ -67,6 +67,10 @@ struct _ConfOptions
                gint height;
        } info_title;
 
+       struct {
+               gint height;
+       } info_rating;
+
        /* file ops */
        struct {
                gboolean enable_in_place_rename;
index 5b6eab5..6347b7d 100644 (file)
@@ -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 */
index f48fef7..a2f53b3 100644 (file)
@@ -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,
index 26f594f..61b2bc2 100644 (file)
@@ -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