gs-category.c 13.6 KB
Newer Older
1 2
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
3
 * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
4
 * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
5
 * Copyright (C) 2015 Kalev Lember <klember@redhat.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * 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.
 */

24 25 26 27 28 29 30 31
/**
 * 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.
 */

32 33
#include "config.h"

34 35
#include <glib/gi18n.h>

36
#include "gs-category-private.h"
37

38
struct _GsCategory
39
{
40 41
	GObject		 parent_instance;

42 43
	gchar		*id;
	gchar		*name;
44
	gchar		*icon;
45
	gint		 score;
46
	GPtrArray	*key_colors;
47
	GPtrArray	*desktop_groups;
48
	GsCategory	*parent;
49
	guint		 size;
50
	GPtrArray	*children;
51 52
};

53
G_DEFINE_TYPE (GsCategory, gs_category, G_TYPE_OBJECT)
54

55 56 57 58 59 60 61
/**
 * gs_category_to_string:
 * @category: a #GsCategory
 *
 * Returns a string representation of the category
 *
 * Returns: a string
62 63
 *
 * Since: 3.22
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
 **/
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);
	}
81
	g_string_append_printf (str, "  size: %u\n",
82
				category->size);
83
	g_string_append_printf (str, "  key-colors: %u\n",
84
				category->key_colors->len);
85
	g_string_append_printf (str, "  desktop-groups: %u\n",
86 87 88 89 90
				category->desktop_groups->len);
	if (category->parent != NULL) {
		g_string_append_printf (str, "  parent: %s\n",
					gs_category_get_id (category->parent));
	}
91
	g_string_append_printf (str, "  score: %i\n", category->score);
92
	if (category->children->len == 0) {
93
		g_string_append_printf (str, "  children: %u\n",
94 95 96 97 98 99 100 101 102 103 104 105
					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);
}

106 107
/**
 * gs_category_get_size:
108
 * @category: a #GsCategory
109 110 111 112 113
 *
 * 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.
114 115
 *
 * Returns: the number of apps in the category
116 117
 *
 * Since: 3.22
118 119 120 121 122
 **/
guint
gs_category_get_size (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), 0);
123
	return category->size;
124 125 126 127
}

/**
 * gs_category_set_size:
128 129 130 131 132
 * @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.
133 134
 *
 * Since: 3.22
135 136 137 138
 **/
void
gs_category_set_size (GsCategory *category, guint size)
{
139
	g_return_if_fail (GS_IS_CATEGORY (category));
140
	category->size = size;
141 142 143 144
}

/**
 * gs_category_increment_size:
145
 * @category: a #GsCategory
146 147
 *
 * Adds one to the size count if an application is available
148 149
 *
 * Since: 3.22
150 151 152 153 154
 **/
void
gs_category_increment_size (GsCategory *category)
{
	g_return_if_fail (GS_IS_CATEGORY (category));
155
	category->size++;
156 157
}

158 159 160 161 162 163 164
/**
 * gs_category_get_id:
 * @category: a #GsCategory
 *
 * Gets the category ID.
 *
 * Returns: the string, e.g. "other"
165 166
 *
 * Since: 3.22
167
 **/
168 169 170 171
const gchar *
gs_category_get_id (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
172
	return category->id;
173 174
}

175 176 177 178 179 180 181
/**
 * gs_category_get_name:
 * @category: a #GsCategory
 *
 * Gets the category name.
 *
 * Returns: the string, or %NULL
182 183
 *
 * Since: 3.22
184
 **/
185 186 187 188
const gchar *
gs_category_get_name (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

	/* 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");
	}

206
	return category->name;
207 208
}

209 210 211 212 213 214
/**
 * gs_category_set_name:
 * @category: a #GsCategory
 * @name: a category name, or %NULL
 *
 * Sets the category name.
215 216
 *
 * Since: 3.22
217 218 219 220 221 222 223 224 225
 **/
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);
}

226 227 228 229 230 231 232
/**
 * gs_category_get_icon:
 * @category: a #GsCategory
 *
 * Gets the category icon.
 *
 * Returns: the string, or %NULL
233 234
 *
 * Since: 3.22
235 236 237 238 239 240
 **/
const gchar *
gs_category_get_icon (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);

241 242 243 244 245 246 247
	/* 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";
248 249 250 251 252 253 254 255 256 257

	return category->icon;
}

/**
 * gs_category_set_icon:
 * @category: a #GsCategory
 * @icon: a category icon, or %NULL
 *
 * Sets the category icon.
258 259
 *
 * Since: 3.22
260 261 262 263 264 265 266 267 268
 **/
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);
}

269
/**
270
 * gs_category_get_score:
271 272
 * @category: a #GsCategory
 *
273
 * Gets if the category score.
274 275 276 277
 * 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
278 279
 *
 * Since: 3.22
280
 **/
281 282
gint
gs_category_get_score (GsCategory *category)
283 284
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), FALSE);
285
	return category->score;
286 287 288
}

/**
289
 * gs_category_set_score:
290
 * @category: a #GsCategory
291
 * @score: a category score, or %NULL
292
 *
293 294
 * Sets the category score, where larger numbers get sorted before lower
 * numbers.
295 296
 *
 * Since: 3.22
297 298
 **/
void
299
gs_category_set_score (GsCategory *category, gint score)
300 301
{
	g_return_if_fail (GS_IS_CATEGORY (category));
302
	category->score = score;
303 304
}

305 306 307 308 309 310 311
/**
 * gs_category_get_key_colors:
 * @category: a #GsCategory
 *
 * Gets the list of key colors for the category.
 *
 * Returns: (element-type GdkRGBA) (transfer none): An array
312 313
 *
 * Since: 3.22
314 315 316 317 318 319 320 321 322 323 324 325 326
 **/
GPtrArray *
gs_category_get_key_colors (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
	return category->key_colors;
}

/**
 * gs_category_add_key_color:
 * @category: a #GsCategory
 * @key_color: a #GdkRGBA
 *
327
 * Adds a key color to the category.
328 329
 *
 * Since: 3.22
330 331 332 333 334 335 336 337 338
 **/
void
gs_category_add_key_color (GsCategory *category, const GdkRGBA *key_color)
{
	g_return_if_fail (GS_IS_CATEGORY (category));
	g_return_if_fail (key_color != NULL);
	g_ptr_array_add (category->key_colors, gdk_rgba_copy (key_color));
}

339 340 341 342 343 344 345
/**
 * 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
346 347
 *
 * Since: 3.22
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
 **/
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
364 365
 *
 * Since: 3.22
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
 **/
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.
390 391
 *
 * Since: 3.22
392 393 394 395 396 397 398 399 400 401 402 403 404
 **/
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));
}

405 406 407 408 409 410 411
/**
 * gs_category_find_child:
 * @category: a #GsCategory
 * @id: a category ID, e.g. "other"
 *
 * Find a child category with a specific ID.
 *
412
 * Returns: (transfer none): the #GsCategory, or %NULL
413 414
 *
 * Since: 3.22
415
 **/
416 417 418 419
GsCategory *
gs_category_find_child (GsCategory *category, const gchar *id)
{
	GsCategory *tmp;
420
	guint i;
421 422

	/* find the subcategory */
423 424
	for (i = 0; i < category->children->len; i++) {
		tmp = GS_CATEGORY (g_ptr_array_index (category->children, i));
425 426 427 428 429 430
		if (g_strcmp0 (id, gs_category_get_id (tmp)) == 0)
			return tmp;
	}
	return NULL;
}

431 432 433 434 435 436 437
/**
 * gs_category_get_parent:
 * @category: a #GsCategory
 *
 * Gets the parent category.
 *
 * Returns: the #GsCategory or %NULL
438 439
 *
 * Since: 3.22
440
 **/
441 442 443 444
GsCategory *
gs_category_get_parent (GsCategory *category)
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
445
	return category->parent;
446 447
}

448
/**
449
 * gs_category_get_children:
450 451
 * @category: a #GsCategory
 *
452
 * Gets the list if children for a category.
453
 *
454
 * Return value: (element-type GsApp) (transfer none): A list of children
455 456
 *
 * Since: 3.22
457
 **/
458 459
GPtrArray *
gs_category_get_children (GsCategory *category)
460 461
{
	g_return_val_if_fail (GS_IS_CATEGORY (category), NULL);
462
	return category->children;
463 464
}

465
/**
466
 * gs_category_add_child:
467 468 469 470
 * @category: a #GsCategory
 * @subcategory: a #GsCategory
 *
 * Adds a child category to a parent category.
471 472
 *
 * Since: 3.22
473
 **/
474
void
475
gs_category_add_child (GsCategory *category, GsCategory *subcategory)
476 477
{
	g_return_if_fail (GS_IS_CATEGORY (category));
478 479 480 481 482 483 484 485 486
	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));
487 488
}

489 490 491 492 493 494 495 496 497 498
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;
499
	return g_strdup_printf ("%u:%s",
500 501 502 503
				sort_order,
				gs_category_get_name (category));
}

504
static gint
505
gs_category_sort_children_cb (gconstpointer a, gconstpointer b)
506
{
507 508
	GsCategory *ca = GS_CATEGORY (*(GsCategory **) a);
	GsCategory *cb = GS_CATEGORY (*(GsCategory **) b);
509 510 511
	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);
512 513 514
}

/**
515
 * gs_category_sort_children:
516 517
 * @category: a #GsCategory
 *
518
 * Sorts the list of children.
519 520
 *
 * Since: 3.22
521 522
 **/
void
523
gs_category_sort_children (GsCategory *category)
524
{
525 526
	g_ptr_array_sort (category->children,
			  gs_category_sort_children_cb);
527 528
}

529 530 531 532 533
static void
gs_category_finalize (GObject *object)
{
	GsCategory *category = GS_CATEGORY (object);

534 535 536
	if (category->parent != NULL)
		g_object_remove_weak_pointer (G_OBJECT (category->parent),
		                              (gpointer *) &category->parent);
537
	g_ptr_array_unref (category->children);
538
	g_ptr_array_unref (category->key_colors);
539
	g_ptr_array_unref (category->desktop_groups);
540 541
	g_free (category->id);
	g_free (category->name);
542
	g_free (category->icon);
543 544 545 546

	G_OBJECT_CLASS (gs_category_parent_class)->finalize (object);
}

547 548 549 550 551 552 553 554 555 556
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)
{
557
	category->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
558
	category->key_colors = g_ptr_array_new_with_free_func ((GDestroyNotify) gdk_rgba_free);
559
	category->desktop_groups = g_ptr_array_new_with_free_func (g_free);
560 561
}

562 563 564 565 566 567 568 569
/**
 * gs_category_new:
 * @id: an ID, e.g. "all"
 * @name: a localised name
 *
 * Creates a new category object.
 *
 * Returns: the new #GsCategory
570 571
 *
 * Since: 3.22
572
 **/
573
GsCategory *
574
gs_category_new (const gchar *id)
575 576 577
{
	GsCategory *category;
	category = g_object_new (GS_TYPE_CATEGORY, NULL);
578
	category->id = g_strdup (id);
579 580
	return GS_CATEGORY (category);
}
581 582

/* vim: set noexpandtab: */