[gnome-software/wip/hughsie/categories] Use the new-style category classification



commit 8b15b860190d17d05c82071316ecd45f4fdd30b4
Author: Richard Hughes <richard hughsie com>
Date:   Wed Jun 1 17:18:37 2016 +0100

    Use the new-style category classification

 src/gs-category.c                            |   62 ++++
 src/gs-category.h                            |    6 +
 src/gs-cmd.c                                 |   19 +-
 src/gs-plugin-loader.c                       |   25 +--
 src/gs-shell-overview.c                      |   12 +-
 src/plugins/Makefile.am                      |   22 +-
 src/plugins/gs-plugin-appstream.c            |  140 ++++----
 src/plugins/gs-plugin-hardcoded-categories.c |  469 ++++++++++++++++++++++++++
 src/plugins/gs-plugin-menu-spec-categories.c |   69 ----
 src/plugins/gs-plugin-menu-spec-refine.c     |  114 -------
 src/plugins/menu-spec-common.c               |  217 ------------
 src/plugins/menu-spec-common.h               |   42 ---
 12 files changed, 636 insertions(+), 561 deletions(-)
---
diff --git a/src/gs-category.c b/src/gs-category.c
index 984c92c..18c5a8a 100644
--- a/src/gs-category.c
+++ b/src/gs-category.c
@@ -43,6 +43,7 @@ struct _GsCategory
        gchar           *icon;
        gboolean         important;
        GPtrArray       *key_colors;
+       GPtrArray       *tags;
        GsCategory      *parent;
        guint            size;
        GPtrArray       *children;
@@ -267,6 +268,65 @@ gs_category_add_key_color (GsCategory *category, const GdkRGBA *key_color)
 }
 
 /**
+ * gs_category_get_tags:
+ * @category: a #GsCategory
+ *
+ * Gets the list of AppStream tags for the category.
+ *
+ * Returns: (element-type utf8) (transfer none): An array
+ **/
+GPtrArray *
+gs_category_get_tags (GsCategory *category)
+{
+       g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+       return category->tags;
+}
+
+/**
+ * gs_category_has_tag:
+ * @category: a #GsCategory
+ * @tag: a tag found in AppStream, e.g. "AudioVisual::Player"
+ *
+ * Finds out if the category has the specific AppStream tag pair.
+ *
+ * Returns: %TRUE if found, %FALSE otherwise
+ **/
+gboolean
+gs_category_has_tag (GsCategory *category, const gchar *tag)
+{
+       guint i;
+
+       g_return_val_if_fail (GS_IS_CATEGORY (category), FALSE);
+       g_return_val_if_fail (tag != NULL, FALSE);
+
+       for (i = 0; i < category->tags->len; i++) {
+               const gchar *tag_tmp = g_ptr_array_index (category->tags, i);
+               if (g_strcmp0 (tag_tmp, tag) == 0)
+                       return TRUE;
+       }
+       return FALSE;
+}
+
+/**
+ * gs_category_add_tag:
+ * @category: a #GsCategory
+ * @tag: a tag found in AppStream, e.g. "AudioVisual::Player"
+ *
+ * Adds a tag pair to the category.
+ **/
+void
+gs_category_add_tag (GsCategory *category, const gchar *tag)
+{
+       g_return_if_fail (GS_IS_CATEGORY (category));
+       g_return_if_fail (tag != NULL);
+
+       /* add if not already found */
+       if (gs_category_has_tag (category, tag))
+               return;
+       g_ptr_array_add (category->tags, g_strdup (tag));
+}
+
+/**
  * gs_category_find_child:
  * @category: a #GsCategory
  * @id: a category ID, e.g. "other"
@@ -396,6 +456,7 @@ gs_category_finalize (GObject *object)
                                              (gpointer *) &category->parent);
        g_ptr_array_unref (category->children);
        g_ptr_array_unref (category->key_colors);
+       g_ptr_array_unref (category->tags);
        g_free (category->id);
        g_free (category->name);
        g_free (category->icon);
@@ -415,6 +476,7 @@ gs_category_init (GsCategory *category)
 {
        category->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        category->key_colors = g_ptr_array_new_with_free_func ((GDestroyNotify) gdk_rgba_free);
+       category->tags = g_ptr_array_new_with_free_func (g_free);
 }
 
 /**
diff --git a/src/gs-category.h b/src/gs-category.h
index bd41e69..7cc97e4 100644
--- a/src/gs-category.h
+++ b/src/gs-category.h
@@ -49,6 +49,12 @@ GPtrArray    *gs_category_get_key_colors     (GsCategory     *category);
 void            gs_category_add_key_color      (GsCategory     *category,
                                                 const GdkRGBA  *key_color);
 
+GPtrArray      *gs_category_get_tags           (GsCategory     *category);
+gboolean        gs_category_has_tag            (GsCategory     *category,
+                                                const gchar    *tag);
+void            gs_category_add_tag            (GsCategory     *category,
+                                                const gchar    *tag);
+
 GsCategory     *gs_category_find_child         (GsCategory     *category,
                                                 const gchar    *id);
 GPtrArray      *gs_category_get_children       (GsCategory     *category);
diff --git a/src/gs-cmd.c b/src/gs-cmd.c
index 776ea7f..60244a8 100644
--- a/src/gs-cmd.c
+++ b/src/gs-cmd.c
@@ -89,9 +89,10 @@ gs_cmd_show_results_categories (GPtrArray *list)
                parent = gs_category_get_parent (cat);
                if (parent != NULL){
                        g_autofree gchar *id = NULL;
-                       id = g_strdup_printf ("%s/%s",
+                       id = g_strdup_printf ("%s/%s [%i]",
                                              gs_category_get_id (parent),
-                                             gs_category_get_id (cat));
+                                             gs_category_get_id (cat),
+                                             gs_category_get_size (cat));
                        tmp = gs_cmd_pad_spaces (id, 32);
                        g_print ("%s : %s\n",
                                 tmp, gs_category_get_name (cat));
@@ -147,6 +148,8 @@ gs_cmd_refine_flag_from_string (const gchar *flag, GError **error)
                return GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS;
        if (g_strcmp0 (flag, "key-colors") == 0)
                return GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS;
+       if (g_strcmp0 (flag, "icon") == 0)
+               return GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON;
        g_set_error (error,
                     GS_PLUGIN_ERROR,
                     GS_PLUGIN_ERROR_NOT_SUPPORTED,
@@ -430,13 +433,15 @@ main (int argc, char **argv)
                }
        } else if (argc == 3 && g_strcmp0 (argv[1], "get-category-apps") == 0) {
                g_autoptr(GsCategory) category = NULL;
+               g_autoptr(GsCategory) parent = NULL;
                g_auto(GStrv) split = NULL;
                split = g_strsplit (argv[2], "/", 2);
-               category = gs_category_new (split[0]);
-               if (g_strv_length (split) == 2) {
-                       g_autoptr(GsCategory) child = NULL;
-                       child = gs_category_new (split[1]);
-                       gs_category_add_child (category, child);
+               if (g_strv_length (split) == 1) {
+                       category = gs_category_new (split[0]);
+               } else {
+                       parent = gs_category_new (split[0]);
+                       category = gs_category_new (split[1]);
+                       gs_category_add_child (parent, category);
                }
                for (i = 0; i < repeat; i++) {
                        if (list != NULL)
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index b932893..0156656 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -2141,9 +2141,9 @@ gs_plugin_loader_category_sort_cb (gconstpointer a, gconstpointer b)
        GsCategory *catb = GS_CATEGORY (*(GsCategory **) b);
 
        /* addons always go last */
-       if (g_strcmp0 (gs_category_get_id (cata), "Addons") == 0)
+       if (g_strcmp0 (gs_category_get_id (cata), "addons") == 0)
                return 1;
-       if (g_strcmp0 (gs_category_get_id (catb), "Addons") == 0)
+       if (g_strcmp0 (gs_category_get_id (catb), "addons") == 0)
                return -1;
 
        return g_strcmp0 (gs_category_get_name (cata),
@@ -2208,27 +2208,6 @@ gs_plugin_loader_get_categories_thread_cb (GTask *task,
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
        }
 
-       /* ensure they all have an 'All' category */
-       for (i = 0; i < state->catlist->len; i++) {
-               GsCategory *parent = GS_CATEGORY (g_ptr_array_index (state->catlist, i));
-               if (g_strcmp0 (gs_category_get_id (parent), "Addons") == 0)
-                       continue;
-               if (gs_category_find_child (parent, "all") == NULL) {
-                       g_autoptr(GsCategory) child = NULL;
-                       child = gs_category_new ("all");
-                       gs_category_add_child (parent, child);
-                       /* this is probably valid... */
-                       gs_category_set_size (child, gs_category_get_size (parent));
-               }
-               if (gs_category_find_child (parent, "featured") == NULL) {
-                       g_autoptr(GsCategory) child = NULL;
-                       child = gs_category_new ("featured");
-                       gs_category_add_child (parent, child);
-                       /* this is probably valid... */
-                       gs_category_set_size (child, 5);
-               }
-       }
-
        /* sort by name */
        g_ptr_array_sort (state->catlist, gs_plugin_loader_category_sort_cb);
        for (i = 0; i < state->catlist->len; i++) {
diff --git a/src/gs-shell-overview.c b/src/gs-shell-overview.c
index d55a847..c2de572 100644
--- a/src/gs-shell-overview.c
+++ b/src/gs-shell-overview.c
@@ -385,24 +385,24 @@ gs_shell_overview_load (GsShellOverview *self)
        date = g_date_time_new_now_utc ();
        switch (g_date_time_get_day_of_year (date) % 4) {
        case 0:
-               category_of_day = "Audio";
+               category_of_day = "audio-video";
                /* TRANSLATORS: this is a heading for audio applications which have been featured 
('recommended') by the distribution */
-               gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Audio 
Applications"));
+               gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Audio & Video 
Applications"));
                break;
        case 1:
-               category_of_day = "Game";
+               category_of_day = "games";
                /* TRANSLATORS: this is a heading for games which have been featured ('recommended') by the 
distribution */
                gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Games"));
                break;
        case 2:
-               category_of_day = "Graphics";
+               category_of_day = "graphics";
                /* TRANSLATORS: this is a heading for graphics applications which have been featured 
('recommended') by the distribution */
                gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Graphics 
Applications"));
                break;
        case 3:
-               category_of_day = "Office";
+               category_of_day = "productivity";
                /* TRANSLATORS: this is a heading for office applications which have been featured 
('recommended') by the distribution */
-               gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Office 
Applications"));
+               gtk_label_set_label (GTK_LABEL (priv->popular_rotating_heading), _("Recommended Productivity 
Applications"));
                break;
        default:
                g_assert_not_reached ();
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 2a1630e..813a071 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -33,10 +33,9 @@ plugin_LTLIBRARIES =                                 \
        libgs_plugin_dummy.la                           \
        libgs_plugin_dpkg.la                            \
        libgs_plugin_hardcoded-blacklist.la             \
+       libgs_plugin_hardcoded-categories.la            \
        libgs_plugin_hardcoded-popular.la               \
        libgs_plugin_hardcoded-featured.la              \
-       libgs_plugin_menu-spec-categories.la            \
-       libgs_plugin_menu-spec-refine.la                \
        libgs_plugin_fedora-distro-upgrades.la          \
        libgs_plugin_provenance.la                      \
        libgs_plugin_provenance-license.la              \
@@ -210,21 +209,10 @@ libgs_plugin_steam_la_LDFLAGS = -module -avoid-version
 libgs_plugin_steam_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 endif
 
-libgs_plugin_menu_spec_categories_la_SOURCES =         \
-       gs-plugin-menu-spec-categories.c                \
-       menu-spec-common.c                              \
-       menu-spec-common.h
-libgs_plugin_menu_spec_categories_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_menu_spec_categories_la_LDFLAGS = -module -avoid-version
-libgs_plugin_menu_spec_categories_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
-
-libgs_plugin_menu_spec_refine_la_SOURCES =             \
-       gs-plugin-menu-spec-refine.c                    \
-       menu-spec-common.c                              \
-       menu-spec-common.h
-libgs_plugin_menu_spec_refine_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_menu_spec_refine_la_LDFLAGS = -module -avoid-version
-libgs_plugin_menu_spec_refine_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+libgs_plugin_hardcoded_categories_la_SOURCES = gs-plugin-hardcoded-categories.c
+libgs_plugin_hardcoded_categories_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_hardcoded_categories_la_LDFLAGS = -module -avoid-version
+libgs_plugin_hardcoded_categories_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 
 libgs_plugin_hardcoded_blacklist_la_SOURCES = gs-plugin-hardcoded-blacklist.c
 libgs_plugin_hardcoded_blacklist_la_LIBADD = $(GS_PLUGIN_LIBS)
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index 244f223..575c761 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -63,8 +63,7 @@ gs_plugin_initialize (GsPlugin *plugin)
                                  AS_STORE_WATCH_FLAG_ADDED |
                                  AS_STORE_WATCH_FLAG_REMOVED);
 
-       /* need category list and package name */
-       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "menu-spec-categories");
+       /* need package name */
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dpkg");
 }
 
@@ -390,46 +389,52 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
                             GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       const gchar *search_id1;
-       const gchar *search_id2 = NULL;
-       AsApp *item;
-       GsCategory *parent;
        GPtrArray *array;
+       GPtrArray *tags;
        guint i;
+       guint j;
        g_autoptr(AsProfileTask) ptask = NULL;
 
-       /* get the two search terms */
+       /* just look at each app in turn */
        ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
                                          "appstream::add-category-apps");
-       search_id1 = gs_category_get_id (category);
-       parent = gs_category_get_parent (category);
-       if (parent != NULL)
-               search_id2 = gs_category_get_id (parent);
-
-       /* the "General" item has no ID */
-       if (search_id1 == NULL) {
-               search_id1 = search_id2;
-               search_id2 = NULL;
-       }
-
-       /* just look at each app in turn */
        array = as_store_get_apps (priv->store);
-       for (i = 0; i < array->len; i++) {
-               g_autoptr(GsApp) app = NULL;
-               item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
-                       continue;
-               if (g_strcmp0 (search_id1, "all") != 0 &&
-                   !as_app_has_category (item, search_id1))
-                       continue;
-               if (search_id2 != NULL && !as_app_has_category (item, search_id2))
+       tags = gs_category_get_tags (category);
+       if (tags->len == 0) {
+               g_warning ("no tags for %s", gs_category_get_id (category));
+               return TRUE;
+       }
+       for (j = 0; j < tags->len; j++) {
+               const gchar *tag = g_ptr_array_index (tags, j);
+               g_auto(GStrv) split = g_strsplit (tag, "::", -1);
+               if (g_strv_length (split) != 2)
                        continue;
 
-               /* got a search match, so add all the data we can */
-               app = gs_plugin_appstream_create_app (plugin, as_app_get_id (item));
-               if (!gs_appstream_refine_app (plugin, app, item, error))
-                       return FALSE;
-               gs_app_list_add (list, app);
+               /* match the app */
+               for (i = 0; i < array->len; i++) {
+                       AsApp *item;
+                       g_autoptr(GsApp) app = NULL;
+
+                       /* no ID is invalid */
+                       item = g_ptr_array_index (array, i);
+                       if (as_app_get_id (item) == NULL)
+                               continue;
+
+                       /* match the parent */
+                       if (!as_app_has_category (item, split[0]))
+                               continue;
+
+                       /* match the child */
+                       if (!as_app_has_category (item, split[1]))
+                               continue;
+
+                       /* add all the data we can */
+                       app = gs_plugin_appstream_create_app (plugin,
+                                                             as_app_get_id (item));
+                       if (!gs_appstream_refine_app (plugin, app, item, error))
+                               return FALSE;
+                       gs_app_list_add (list, app);
+               }
        }
        return TRUE;
 }
@@ -528,44 +533,43 @@ gs_plugin_add_installed (GsPlugin *plugin,
        return TRUE;
 }
 
+static gboolean
+_as_app_matches_category_tag (AsApp *app, const gchar *tag)
+{
+       g_auto(GStrv) split = g_strsplit (tag, "::", -1);
+       if (!as_app_has_category (app, split[0]))
+               return FALSE;
+       if (split[1] != NULL && !as_app_has_category (app, split[1]))
+               return FALSE;
+       return TRUE;
+}
+
 static void
-gs_plugin_add_categories_for_app (GPtrArray *list, AsApp *app)
+gs_plugin_add_categories_for_app (GsCategory *parent, AsApp *app)
 {
-       guint i, j;
+       GPtrArray *children;
+       GPtrArray *tags;
        GsCategory *category;
-       GsCategory *parent;
-       gboolean found_subcat;
-
-       /* does it match the main category */
-       for (i = 0; i < list->len; i++) {
-               GPtrArray *children;
-               parent = GS_CATEGORY (g_ptr_array_index (list, i));
-               if (!as_app_has_category (app, gs_category_get_id (parent)))
-                       continue;
-               gs_category_increment_size (parent);
-
-               /* does it match any sub-categories */
-               found_subcat = FALSE;
-               children = gs_category_get_children (parent);
-               for (j = 0; j < children->len; j++) {
-                       category = GS_CATEGORY (g_ptr_array_index (children, j));
-                       if (!as_app_has_category (app, gs_category_get_id (category)))
-                               continue;
-                       gs_category_increment_size (category);
-                       found_subcat = TRUE;
-               }
+       guint i, j;
 
-               /* matching the main category but no subcategories means we have
-                * to create a new 'Other' subcategory manually */
-               if (!found_subcat) {
-                       category = gs_category_find_child (parent, "other");
-                       if (category == NULL) {
-                               category = gs_category_new ("other");
-                               gs_category_add_child (parent, category);
-                               g_object_unref (category);
+       /* find all the sub-categories */
+       children = gs_category_get_children (parent);
+       for (j = 0; j < children->len; j++) {
+               gboolean matched = FALSE;
+               category = GS_CATEGORY (g_ptr_array_index (children, j));
+
+               /* do any tags match this application */
+               tags = gs_category_get_tags (category);
+               for (i = 0; i < tags->len; i++) {
+                       const gchar *tag = g_ptr_array_index (tags, i);
+                       if (_as_app_matches_category_tag (app, tag)) {
+                               matched = TRUE;
+                               break;
                        }
-                       as_app_add_category (app, gs_category_get_id (category));
+               }
+               if (matched) {
                        gs_category_increment_size (category);
+                       gs_category_increment_size (parent);
                }
        }
 }
@@ -580,6 +584,7 @@ gs_plugin_add_categories (GsPlugin *plugin,
        AsApp *app;
        GPtrArray *array;
        guint i;
+       guint j;
        g_autoptr(AsProfileTask) ptask = NULL;
 
        /* find out how many packages are in each category */
@@ -592,7 +597,10 @@ gs_plugin_add_categories (GsPlugin *plugin,
                        continue;
                if (as_app_get_priority (app) < 0)
                        continue;
-               gs_plugin_add_categories_for_app (list, app);
+               for (j = 0; j < list->len; j++) {
+                       GsCategory *parent = GS_CATEGORY (g_ptr_array_index (list, j));
+                       gs_plugin_add_categories_for_app (parent, app);
+               }
        }
        return TRUE;
 }
diff --git a/src/plugins/gs-plugin-hardcoded-categories.c b/src/plugins/gs-plugin-hardcoded-categories.c
new file mode 100644
index 0000000..be66ee6
--- /dev/null
+++ b/src/plugins/gs-plugin-hardcoded-categories.c
@@ -0,0 +1,469 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011-2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <gnome-software.h>
+#include <glib/gi18n.h>
+
+/*
+ * SECTION:
+ * Adds categories from a hardcoded list based on the the desktop menu
+ * specification.
+ */
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+       /* need categories */
+       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "appstream");
+
+       /* the old name for these plugins */
+       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "menu-spec-categories");
+       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "menu-spec-refine");
+}
+
+typedef struct {
+       const gchar     *id;
+       const gchar     *name;
+       const gchar     *fdo_cats[16];
+} GsCategoryMap;
+
+typedef struct {
+       const gchar     *id;
+       GsCategoryMap   *mapping;
+       const gchar     *name;
+       const gchar     *icon;
+       const gchar     *key_colors;
+       gboolean         important;
+} GsCategoryData;
+
+/* AudioVideo */
+static const GsCategoryMap map_audiovisual[] = {
+//     { "all",                NC_("Menu of AudioVideo", "All"),
+//                                     { NULL } },
+       { "featured",           NC_("Menu of AudioVideo", "Featured"),
+                                       { "AudioVideo::featured",
+                                         NULL} },
+       { "creation-editing",   NC_("Menu of AudioVideo", "Audio Creation & Editing"),
+                                       { "AudioVideo::AudioVideoEditing",
+                                         "AudioVideo::Midi",
+                                         "AudioVideo::DiscBurning",
+                                         "AudioVideo::Sequencer",
+                                         NULL} },
+       { "music-players",      NC_("Menu of AudioVideo", "Music Players"),
+                                       { "AudioVideo::Music",
+                                         "AudioVideo::Player",
+                                         NULL} },
+       { NULL }
+};
+
+/* Development */
+static const GsCategoryMap map_developertools[] = {
+       { "featured",           NC_("Menu of Development", "Featured"),
+                                       { "Development::featured",
+                                         NULL} },
+       { "debuggers",          NC_("Menu of Development", "Debuggers"),
+                                       { "Development:Debugger",
+                                         NULL} },
+       { "ide",                NC_("Menu of Development", "IDEs"),
+                                       { "Development::IDE",
+                                         "Development::GUIDesigner",
+                                         NULL} },
+       { NULL }
+};
+
+/* Education */
+static const GsCategoryMap map_education[] = {
+       { "featured",           NC_("Menu of Education", "Featured"),
+                                       { "Education::featured",
+                                         NULL} },
+       { "astronomy",          NC_("Menu of Education", "Astronomy"),
+                                       { "Education::Astronomy",
+                                         NULL} },
+       { "chemistry",          NC_("Menu of Education", "Chemistry"),
+                                       { "Education::Chemistry",
+                                         NULL} },
+       { "languages",          NC_("Menu of Education", "Languages"),
+                                       { "Education::Languages",
+                                         "Education::Literature",
+                                         NULL} },
+       { "math",               NC_("Menu of Education", "Math"),
+                                       { "Education::Math",
+                                         "Education::NumericalAnalysis",
+                                         NULL} },
+       { NULL }
+};
+
+/* Games */
+static const GsCategoryMap map_games[] = {
+       { "featured",           NC_("Menu of Games", "Featured"),
+                                       { "Game::featured",
+                                         NULL} },
+       { "action",             NC_("Menu of Games", "Action"),
+                                       { "Game::ActionGame",
+                                         NULL} },
+       { "adventure",          NC_("Menu of Games", "Adventure"),
+                                       { "Game::AdventureGame",
+                                         NULL} },
+       { "arcade",             NC_("Menu of Games", "Arcade"),
+                                       { "Game::ArcadeGame",
+                                         NULL} },
+       { "blocks",             NC_("Menu of Games", "Blocks"),
+                                       { "Game::BlocksGame",
+                                         NULL} },
+       { "board",              NC_("Menu of Games", "Board"),
+                                       { "Game::BoardGame",
+                                         NULL} },
+       { "card",               NC_("Menu of Games", "Card"),
+                                       { "Game::CardGame",
+                                         NULL} },
+       { "emulator",           NC_("Menu of Games", "Emulators"),
+                                       { "Game::Emulator",
+                                         NULL} },
+       { "kids",               NC_("Menu of Games", "Kids"),
+                                       { "Game::KidsGame",
+                                         NULL} },
+       { "logic",              NC_("Menu of Games", "Logic"),
+                                       { "Game::LogicGame",
+                                         NULL} },
+       { "role-playing",       NC_("Menu of Games", "Role Playing"),
+                                       { "Game::RolePlaying",
+                                         NULL} },
+       { "sports",             NC_("Menu of Games", "Sports"),
+                                       { "Game::SportsGame",
+                                         "Game::Simulation",
+                                         NULL} },
+       { "strategy",           NC_("Menu of Games", "Strategy"),
+                                       { "Game::StrategyGame",
+                                         NULL} },
+       { NULL }
+};
+
+/* Graphics */
+static const GsCategoryMap map_graphics[] = {
+       { "featured",           NC_("Menu of Graphics", "Featured"),
+                                       { "Graphics::featured",
+                                         NULL} },
+       { "3d",                 NC_("Menu of Graphics", "3D Graphics"),
+                                       { "Graphics::3DGraphics",
+                                         NULL} },
+       { "photography",        NC_("Menu of Graphics", "Photography"),
+                                       { "Graphics::Photography",
+                                         NULL} },
+       { "scanning",           NC_("Menu of Graphics", "Scanning"),
+                                       { "Graphics::Scanning",
+                                         NULL} },
+       { "vector",             NC_("Menu of Graphics", "Vector Graphics"),
+                                       { "Graphics::VectorGraphics",
+                                         NULL} },
+       { "viewers",            NC_("Menu of Graphics", "Viewers"),
+                                       { "Graphics::Viewer",
+                                         NULL} },
+       { NULL }
+};
+
+/* Office */
+static const GsCategoryMap map_productivity[] = {
+       { "featured",           NC_("Menu of Office", "Featured"),
+                                       { "Office::featured",
+                                         NULL} },
+       { "calendar",           NC_("Menu of Office", "Calendar"),
+                                       { "Office::Calendar",
+                                         "Office::ProjectManagement",
+                                         NULL} },
+       { "database",           NC_("Menu of Office", "Database"),
+                                       { "Office::Database",
+                                         NULL} },
+       { "finance",            NC_("Menu of Office", "Finance"),
+                                       { "Office::Finance",
+                                         "Office::Spreadsheet",
+                                         NULL} },
+       { "word Processor",     NC_("Menu of Office", "Word Processor"),
+                                       { "Office::WordProcessor",
+                                         "Office::Dictionary",
+                                         NULL} },
+       { NULL }
+};
+
+/* Addons */
+static const GsCategoryMap map_addons[] = {
+       { "fonts",              NC_("Menu of Addons", "Fonts"),
+                                       { "Addons::Fonts",
+                                         NULL} },
+       { "codecs",             NC_("Menu of Addons", "Codecs"),
+                                       { "Addons::Codecs",
+                                         NULL} },
+       { "input-sources",      NC_("Menu of Addons", "Input Sources"),
+                                       { "Addons::InputSources",
+                                         NULL} },
+       { "language-packs",     NC_("Menu of Addons", "Language Packs"),
+                                       { "Addons::LanguagePacks",
+                                         NULL} },
+       { "shell-sxtensions",   NC_("Menu of Addons", "Shell Extensions"),
+                                       { "Addons::ShellExtensions",
+                                         NULL} },
+       { "Localization",       NC_("Menu of Addons", "Localization"),
+                                       { "Addons::Localization",
+                                         NULL} },
+       { NULL }
+};
+
+/* Science */
+static const GsCategoryMap map_science[] = {
+       { "featured",           NC_("Menu of Science", "Featured"),
+                                       { "Science::featured",
+                                         NULL} },
+       { "artificial-intelligence", NC_("Menu of Science", "Artificial Intelligence"),
+                                       { "Science::ArtificialIntelligence",
+                                         NULL} },
+       { "astronomy",          NC_("Menu of Science", "Astronomy"),
+                                       { "Science::Astronomy",
+                                         NULL} },
+       { "chemistry",          NC_("Menu of Science", "Chemistry"),
+                                       { "Science::Chemistry",
+                                         NULL} },
+       { "math",               NC_("Menu of Science", "Math"),
+                                       { "Science::Math",
+                                         "Science::Physics",
+                                         "Science::NumericalAnalysis",
+                                         NULL} },
+       { "robotics",           NC_("Menu of Science", "Robotics"),
+                                       { "Science::Robotics",
+                                         NULL} },
+       { NULL }
+};
+
+/* Communication */
+static const GsCategoryMap map_communication[] = {
+       { "featured",           NC_("Menu of Communication", "Featured"),
+                                       { "Network::featured",
+                                         NULL} },
+       { "chat",               NC_("Menu of Communication", "Chat"),
+                                       { "Network::Chat",
+                                         "Network::IRCClient",
+                                         "Network::Telephony",
+                                         "Network::VideoConference",
+                                         "Network::Email",
+                                         NULL} },
+       { "news",               NC_("Menu of Communication", "News"),
+                                       { "Network::Feed",
+                                         "Network::News",
+                                         NULL} },
+       { "web-browsers",       NC_("Menu of Communication", "Web Browsers"),
+                                       { "Network::WebBrowser",
+                                         NULL} },
+       { NULL }
+};
+
+/* Utilities */
+static const GsCategoryMap map_utilities[] = {
+       { "featured",           NC_("Menu of Utilities", "Featured"),
+                                       { "Utilities::featured",
+                                         NULL} },
+       { "text-editors",       NC_("Menu of Utilities", "Text Editors"),
+                                       { "Utility::TextEditor",
+                                         NULL} },
+       { NULL }
+};
+
+/* main categories */
+static const GsCategoryData msdata[] = {
+       /* TRANSLATORS: this is the menu spec main category for Audio & Video */
+       { "audio-video",        map_audiovisual,        N_("Audio & Video"),
+                               "folder-music-symbolic", "#215d9c", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Development */
+       { "developer-tools",    map_developertools,     N_("Developer Tools"),
+                               "applications-engineering-symbolic", "#297bcc" },
+       /* TRANSLATORS: this is the menu spec main category for Education */
+       { "education",          map_education,          N_("Education"),
+                               "system-help-symbolic", "#29cc5d" },
+       /* TRANSLATORS: this is the menu spec main category for Game */
+       { "games",              map_games,              N_("Games"),
+                               "applications-games-symbolic", "#c4a000", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Graphics */
+       { "graphics",           map_graphics,           N_("Graphics & Photography"),
+                               "applications-graphics-symbolic", "#75507b", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Office */
+       { "productivity",       map_productivity,       N_("Productivity"),
+                               "text-editor-symbolic", "#cc0000", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Add-ons */
+       { "addons",             map_addons,             N_("Add-ons"),
+                               "application-x-addon-symbolic", "#4e9a06", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Science */
+       { "science",            map_science,            N_("Science"),
+                               "applications-science-symbolic", "#9c29ca" },
+       /* TRANSLATORS: this is the menu spec main category for Communication */
+       { "communication",      map_communication,      N_("Communication & News"),
+                               "user-available-symbolic", "#729fcf", TRUE },
+       /* TRANSLATORS: this is the menu spec main category for Utilities */
+       { "utilities",          map_utilities,          N_("Utilities"),
+                               "applications-utilities-symbolic", "#2944cc" },
+       { NULL }
+};
+
+gboolean
+gs_plugin_add_categories (GsPlugin *plugin,
+                         GPtrArray *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       gchar msgctxt[100];
+       guint i, j, k;
+
+       for (i = 0; msdata[i].id != NULL; i++) {
+               GdkRGBA key_color;
+               GsCategory *category = NULL;
+
+               /* add parent category */
+               category = gs_category_new (msdata[i].id);
+               gs_category_set_icon (category, msdata[i].icon);
+               gs_category_set_name (category, gettext (msdata[i].name));
+               gs_category_set_important (category, msdata[i].important);
+               if (gdk_rgba_parse (&key_color, msdata[i].key_colors))
+                       gs_category_add_key_color (category, &key_color);
+               g_ptr_array_add (list, category);
+               g_snprintf (msgctxt, 100, "Menu subcategory of %s", msdata[i].name);
+
+               /* add subcategories */
+               for (j = 0; msdata[i].mapping[j].id != NULL; j++) {
+                       const GsCategoryMap *map = &msdata[i].mapping[j];
+                       g_autoptr(GsCategory) sub = gs_category_new (map->id);
+                       for (k = 0; map->fdo_cats[k] != NULL; k++)
+                               gs_category_add_tag (sub, map->fdo_cats[k]);
+                       gs_category_set_name (sub, g_dpgettext2 (GETTEXT_PACKAGE,
+                                                                msgctxt,
+                                                                map->name));
+                       gs_category_add_child (category, sub);
+               }
+       }
+       return TRUE;
+}
+
+/* most of this time this won't be required, unless the user creates a
+ * GsCategory manually and uses it to get results, for instance in the
+ * overview page or with gnome-software-cmd get-category-apps games/featured */
+gboolean
+gs_plugin_add_category_apps (GsPlugin *plugin,
+                            GsCategory *category,
+                            GsAppList *list,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GPtrArray *tags;
+       GsCategory *parent;
+       guint i, j, k;
+
+       /* already set */
+       tags = gs_category_get_tags (category);
+       if (tags->len > 0)
+               return TRUE;
+
+       /* not valid */
+       parent = gs_category_get_parent (category);
+       if (parent == NULL)
+               return TRUE;
+
+       /* find tags for a parent::child category */
+       for (i = 0; msdata[i].id != NULL; i++) {
+               if (g_strcmp0 (gs_category_get_id (parent), msdata[i].id) != 0)
+                       continue;
+               for (j = 0; msdata[i].mapping[j].id != NULL; j++) {
+                       const GsCategoryMap *map = &msdata[i].mapping[j];
+                       if (g_strcmp0 (gs_category_get_id (category), map->id) != 0)
+                               continue;
+                       for (k = 0; map->fdo_cats[k] != NULL; k++)
+                               gs_category_add_tag (category, map->fdo_cats[k]);
+               }
+       }
+       return TRUE;
+}
+
+static void
+gs_plugin_refine_app_category (GsPlugin *plugin,
+                              GsApp *app,
+                              const GsCategoryData *cat)
+{
+       const gchar *menu_path[] = { NULL, NULL, NULL };
+       gboolean ret = FALSE;
+       gchar *tmp;
+       guint i;
+
+       /* find a sub-level category the app has */
+       for (i = 0; msdata[i].id != NULL; i++) {
+               tmp = g_strstr_len (msdata[i].id, -1, "::");
+               if (tmp == NULL)
+                       continue;
+
+//FIXME: this isn't going to work!
+
+               if (!g_str_has_prefix (msdata[i].id, cat->id))
+                       continue;
+               ret = gs_app_has_category (app, tmp + 2);
+               if (ret) {
+                       g_autofree gchar *msgctxt = NULL;
+                       msgctxt = g_strdup_printf ("Menu subcategory of %s", cat->name);
+                       menu_path[1] = g_dpgettext2 (GETTEXT_PACKAGE, msgctxt, msdata[i].name);
+                       break;
+               }
+       }
+
+       /* the top-level category always exists */
+       menu_path[0] = gettext (cat->name);
+       gs_app_set_menu_path (app, (gchar **) menu_path);
+}
+
+gboolean
+gs_plugin_refine_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GsPluginRefineFlags flags,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       gboolean ret = FALSE;
+       gchar *tmp;
+       guint i;
+       const gchar *EMPTY[] = { "", NULL };
+
+       /* nothing to do here */
+       if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH) == 0)
+               return TRUE;
+       if (gs_app_get_menu_path (app) != NULL)
+               return TRUE;
+
+       /* find a top level category the app has */
+       for (i = 0; msdata[i].id != NULL; i++) {
+               tmp = g_strstr_len (msdata[i].id, -1, "::");
+               if (tmp != NULL)
+                       continue;
+               ret = gs_app_has_category (app, msdata[i].id);
+               if (ret) {
+                       gs_plugin_refine_app_category (plugin, app, &msdata[i]);
+                       break;
+               }
+       }
+
+       /* don't keep searching for this */
+       if (!ret)
+               gs_app_set_menu_path (app, (gchar **) EMPTY);
+
+       return TRUE;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]