summaryrefslogtreecommitdiffstats
path: root/lib/gs-category.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/gs-category.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/lib/gs-category.c b/lib/gs-category.c
new file mode 100644
index 0000000..9da2c73
--- /dev/null
+++ b/lib/gs-category.c
@@ -0,0 +1,527 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
+ * Copyright (C) 2015 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-category
+ * @short_description: An category that contains applications
+ *
+ * This object provides functionality that allows a plugin to create
+ * a tree structure of categories that each contain #GsApp's.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gs-category-private.h"
+
+struct _GsCategory
+{
+ GObject parent_instance;
+
+ gchar *id;
+ gchar *name;
+ gchar *icon;
+ gint score;
+ GPtrArray *desktop_groups;
+ GsCategory *parent;
+ guint size;
+ GPtrArray *children;
+};
+
+G_DEFINE_TYPE (GsCategory, gs_category, G_TYPE_OBJECT)
+
+/**
+ * gs_category_to_string:
+ * @category: a #GsCategory
+ *
+ * Returns a string representation of the category
+ *
+ * Returns: a string
+ *
+ * Since: 3.22
+ **/
+gchar *
+gs_category_to_string (GsCategory *category)
+{
+ guint i;
+ GString *str = g_string_new (NULL);
+ g_string_append_printf (str, "GsCategory[%p]:\n", category);
+ g_string_append_printf (str, " id: %s\n",
+ category->id);
+ if (category->name != NULL) {
+ g_string_append_printf (str, " name: %s\n",
+ category->name);
+ }
+ if (category->icon != NULL) {
+ g_string_append_printf (str, " icon: %s\n",
+ category->icon);
+ }
+ g_string_append_printf (str, " size: %u\n",
+ category->size);
+ g_string_append_printf (str, " desktop-groups: %u\n",
+ category->desktop_groups->len);
+ if (category->parent != NULL) {
+ g_string_append_printf (str, " parent: %s\n",
+ gs_category_get_id (category->parent));
+ }
+ g_string_append_printf (str, " score: %i\n", category->score);
+ if (category->children->len == 0) {
+ g_string_append_printf (str, " children: %u\n",
+ category->children->len);
+ } else {
+ g_string_append (str, " children:\n");
+ for (i = 0; i < category->children->len; i++) {
+ GsCategory *child = g_ptr_array_index (category->children, i);
+ g_string_append_printf (str, " - %s\n",
+ gs_category_get_id (child));
+ }
+ }
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * gs_category_get_size:
+ * @category: a #GsCategory
+ *
+ * Returns how many applications the category could contain.
+ *
+ * NOTE: This may over-estimate the number if duplicate applications are
+ * filtered or core applications are not shown.
+ *
+ * Returns: the number of apps in the category
+ *
+ * Since: 3.22
+ **/
+guint
+gs_category_get_size (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), 0);
+ return category->size;
+}
+
+/**
+ * gs_category_set_size:
+ * @category: a #GsCategory
+ * @size: the number of applications
+ *
+ * Sets the number of applications in the category.
+ * Most plugins do not need to call this function.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_set_size (GsCategory *category, guint size)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ category->size = size;
+}
+
+/**
+ * gs_category_increment_size:
+ * @category: a #GsCategory
+ *
+ * Adds one to the size count if an application is available
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_increment_size (GsCategory *category)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ category->size++;
+}
+
+/**
+ * gs_category_get_id:
+ * @category: a #GsCategory
+ *
+ * Gets the category ID.
+ *
+ * Returns: the string, e.g. "other"
+ *
+ * Since: 3.22
+ **/
+const gchar *
+gs_category_get_id (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+ return category->id;
+}
+
+/**
+ * gs_category_get_name:
+ * @category: a #GsCategory
+ *
+ * Gets the category name.
+ *
+ * Returns: the string, or %NULL
+ *
+ * Since: 3.22
+ **/
+const gchar *
+gs_category_get_name (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+
+ /* special case, we don't want translations in the plugins */
+ if (g_strcmp0 (category->id, "other") == 0) {
+ /* TRANSLATORS: this is where all applications that don't
+ * fit in other groups are put */
+ return _("Other");
+ }
+ if (g_strcmp0 (category->id, "all") == 0) {
+ /* TRANSLATORS: this is a subcategory matching all the
+ * different apps in the parent category, e.g. "Games" */
+ return _("All");
+ }
+ if (g_strcmp0 (category->id, "featured") == 0) {
+ /* TRANSLATORS: this is a subcategory of featured apps */
+ return _("Featured");
+ }
+
+ return category->name;
+}
+
+/**
+ * gs_category_set_name:
+ * @category: a #GsCategory
+ * @name: a category name, or %NULL
+ *
+ * Sets the category name.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_set_name (GsCategory *category, const gchar *name)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ g_free (category->name);
+ category->name = g_strdup (name);
+}
+
+/**
+ * gs_category_get_icon:
+ * @category: a #GsCategory
+ *
+ * Gets the category icon.
+ *
+ * Returns: the string, or %NULL
+ *
+ * Since: 3.22
+ **/
+const gchar *
+gs_category_get_icon (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+
+ /* special case */
+ if (g_strcmp0 (category->id, "other") == 0)
+ return "emblem-system-symbolic";
+ if (g_strcmp0 (category->id, "all") == 0)
+ return "emblem-default-symbolic";
+ if (g_strcmp0 (category->id, "featured") == 0)
+ return "emblem-favorite-symbolic";
+
+ return category->icon;
+}
+
+/**
+ * gs_category_set_icon:
+ * @category: a #GsCategory
+ * @icon: a category icon, or %NULL
+ *
+ * Sets the category icon.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_set_icon (GsCategory *category, const gchar *icon)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ g_free (category->icon);
+ category->icon = g_strdup (icon);
+}
+
+/**
+ * gs_category_get_score:
+ * @category: a #GsCategory
+ *
+ * Gets if the category score.
+ * Important categories may be shown before other categories, or tagged in a
+ * different way, for example with color or in a different section.
+ *
+ * Returns: the string, or %NULL
+ *
+ * Since: 3.22
+ **/
+gint
+gs_category_get_score (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), FALSE);
+ return category->score;
+}
+
+/**
+ * gs_category_set_score:
+ * @category: a #GsCategory
+ * @score: a category score, or %NULL
+ *
+ * Sets the category score, where larger numbers get sorted before lower
+ * numbers.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_set_score (GsCategory *category, gint score)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ category->score = score;
+}
+
+/**
+ * gs_category_get_desktop_groups:
+ * @category: a #GsCategory
+ *
+ * Gets the list of AppStream groups for the category.
+ *
+ * Returns: (element-type utf8) (transfer none): An array
+ *
+ * Since: 3.22
+ **/
+GPtrArray *
+gs_category_get_desktop_groups (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+ return category->desktop_groups;
+}
+
+/**
+ * gs_category_has_desktop_group:
+ * @category: a #GsCategory
+ * @desktop_group: a group of categories found in AppStream, e.g. "AudioVisual::Player"
+ *
+ * Finds out if the category has the specific AppStream desktop group.
+ *
+ * Returns: %TRUE if found, %FALSE otherwise
+ *
+ * Since: 3.22
+ **/
+gboolean
+gs_category_has_desktop_group (GsCategory *category, const gchar *desktop_group)
+{
+ guint i;
+
+ g_return_val_if_fail (GS_IS_CATEGORY (category), FALSE);
+ g_return_val_if_fail (desktop_group != NULL, FALSE);
+
+ for (i = 0; i < category->desktop_groups->len; i++) {
+ const gchar *tmp = g_ptr_array_index (category->desktop_groups, i);
+ if (g_strcmp0 (tmp, desktop_group) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * gs_category_add_desktop_group:
+ * @category: a #GsCategory
+ * @desktop_group: a group of categories found in AppStream, e.g. "AudioVisual::Player"
+ *
+ * Adds a desktop group to the category.
+ * A desktop group is a set of category strings that all must exist.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_add_desktop_group (GsCategory *category, const gchar *desktop_group)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ g_return_if_fail (desktop_group != NULL);
+
+ /* add if not already found */
+ if (gs_category_has_desktop_group (category, desktop_group))
+ return;
+ g_ptr_array_add (category->desktop_groups, g_strdup (desktop_group));
+}
+
+/**
+ * gs_category_find_child:
+ * @category: a #GsCategory
+ * @id: a category ID, e.g. "other"
+ *
+ * Find a child category with a specific ID.
+ *
+ * Returns: (transfer none): the #GsCategory, or %NULL
+ *
+ * Since: 3.22
+ **/
+GsCategory *
+gs_category_find_child (GsCategory *category, const gchar *id)
+{
+ GsCategory *tmp;
+ guint i;
+
+ /* find the subcategory */
+ for (i = 0; i < category->children->len; i++) {
+ tmp = GS_CATEGORY (g_ptr_array_index (category->children, i));
+ if (g_strcmp0 (id, gs_category_get_id (tmp)) == 0)
+ return tmp;
+ }
+ return NULL;
+}
+
+/**
+ * gs_category_get_parent:
+ * @category: a #GsCategory
+ *
+ * Gets the parent category.
+ *
+ * Returns: the #GsCategory or %NULL
+ *
+ * Since: 3.22
+ **/
+GsCategory *
+gs_category_get_parent (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+ return category->parent;
+}
+
+/**
+ * gs_category_get_children:
+ * @category: a #GsCategory
+ *
+ * Gets the list if children for a category.
+ *
+ * Return value: (element-type GsApp) (transfer none): A list of children
+ *
+ * Since: 3.22
+ **/
+GPtrArray *
+gs_category_get_children (GsCategory *category)
+{
+ g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
+ return category->children;
+}
+
+/**
+ * gs_category_add_child:
+ * @category: a #GsCategory
+ * @subcategory: a #GsCategory
+ *
+ * Adds a child category to a parent category.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_add_child (GsCategory *category, GsCategory *subcategory)
+{
+ g_return_if_fail (GS_IS_CATEGORY (category));
+ g_return_if_fail (GS_IS_CATEGORY (subcategory));
+
+ /* FIXME: do we need this? */
+ subcategory->parent = category;
+ g_object_add_weak_pointer (G_OBJECT (subcategory->parent),
+ (gpointer *) &subcategory->parent);
+
+ g_ptr_array_add (category->children,
+ g_object_ref (subcategory));
+}
+
+static gchar *
+gs_category_get_sort_key (GsCategory *category)
+{
+ guint sort_order = 5;
+ if (g_strcmp0 (gs_category_get_id (category), "featured") == 0)
+ sort_order = 0;
+ else if (g_strcmp0 (gs_category_get_id (category), "all") == 0)
+ sort_order = 2;
+ else if (g_strcmp0 (gs_category_get_id (category), "other") == 0)
+ sort_order = 9;
+ return g_strdup_printf ("%u:%s",
+ sort_order,
+ gs_category_get_name (category));
+}
+
+static gint
+gs_category_sort_children_cb (gconstpointer a, gconstpointer b)
+{
+ GsCategory *ca = GS_CATEGORY (*(GsCategory **) a);
+ GsCategory *cb = GS_CATEGORY (*(GsCategory **) b);
+ g_autofree gchar *id_a = gs_category_get_sort_key (ca);
+ g_autofree gchar *id_b = gs_category_get_sort_key (cb);
+ return g_strcmp0 (id_a, id_b);
+}
+
+/**
+ * gs_category_sort_children:
+ * @category: a #GsCategory
+ *
+ * Sorts the list of children.
+ *
+ * Since: 3.22
+ **/
+void
+gs_category_sort_children (GsCategory *category)
+{
+ g_ptr_array_sort (category->children,
+ gs_category_sort_children_cb);
+}
+
+static void
+gs_category_finalize (GObject *object)
+{
+ GsCategory *category = GS_CATEGORY (object);
+
+ if (category->parent != NULL)
+ g_object_remove_weak_pointer (G_OBJECT (category->parent),
+ (gpointer *) &category->parent);
+ g_ptr_array_unref (category->children);
+ g_ptr_array_unref (category->desktop_groups);
+ g_free (category->id);
+ g_free (category->name);
+ g_free (category->icon);
+
+ G_OBJECT_CLASS (gs_category_parent_class)->finalize (object);
+}
+
+static void
+gs_category_class_init (GsCategoryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gs_category_finalize;
+}
+
+static void
+gs_category_init (GsCategory *category)
+{
+ category->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ category->desktop_groups = g_ptr_array_new_with_free_func (g_free);
+}
+
+/**
+ * gs_category_new:
+ * @id: an ID, e.g. "all"
+ *
+ * Creates a new category object.
+ *
+ * Returns: the new #GsCategory
+ *
+ * Since: 3.22
+ **/
+GsCategory *
+gs_category_new (const gchar *id)
+{
+ GsCategory *category;
+ category = g_object_new (GS_TYPE_CATEGORY, NULL);
+ category->id = g_strdup (id);
+ return GS_CATEGORY (category);
+}