/* -*- 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 * Copyright (C) 2013 Matthias Clasen * Copyright (C) 2015 Kalev Lember * * 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 #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); }