/* context-tile-callbacks.c * * Copyright 2026 Eva M, Alexander Vanhee * * 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 3 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, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include "bz-entry.h" #include "context-tile-callbacks.h" #include "safety-calculator.h" #include "spdx.h" static char * format_with_small_suffix (char *number, const char *suffix) { char *dot = g_strrstr (number, "."); if (dot != NULL) { char *end = dot; while (*(end + 1) != '\0') end++; while (end > dot && *end == '0') *end-- = '\0'; if (end == dot) *dot = '\0'; } return g_strdup_printf ("%s\xC2\xA0%s", number, suffix); } static char * format_favorites_count (gpointer object, int favorites_count) { if (favorites_count < 0) return g_strdup (" "); return g_strdup_printf ("%d", favorites_count); } static char * format_recent_downloads (gpointer object, int value) { double result; int digits; if (value <= 0) return g_strdup (_ ("---")); if (value >= 1000000) { result = value / 1000000.0; digits = (int) log10 (result) + 1; /* Translators: M is the suffix for millions */ return g_strdup_printf (_ ("%.*fM"), 3 - digits, result); } else if (value >= 1000) { result = value / 1000.0; digits = (int) log10 (result) + 1; /* Translators: K is the suffix for thousands*/ return g_strdup_printf (_ ("%.*fK"), 3 - digits, result); } else return g_strdup_printf ("%'d", value); } static char * format_recent_downloads_tooltip (gpointer object, int value) { return g_strdup_printf (_ ("%d downloads in the last month"), value); } static char * format_size (gpointer object, guint64 value) { g_autofree char *size_str = g_format_size (value); char *space = g_strrstr (size_str, "\xC2\xA0"); char *decimal = NULL; int digits = 0; if (value == 0) return g_strdup (_ ("N/A")); if (space != NULL) { *space = '\0'; for (char *p = size_str; *p != '\0' && *p != '.'; p++) if (g_ascii_isdigit (*p)) digits++; if (digits >= 3) { decimal = g_strrstr (size_str, "."); if (decimal != NULL) *decimal = '\0'; } return format_with_small_suffix (size_str, space + 2); } return g_strdup (size_str); } static char * get_size_tile_child_name (gpointer object, gboolean removable) { return g_strdup (removable ? "installed" : "download"); } static char * get_download_size_label (gpointer object, gboolean runtime_installed, guint64 runtime_size) { if (!runtime_installed && runtime_size > 0) { g_autofree char *size_str = g_format_size (runtime_size); // TRANSLATORS: %s is a formatted file size, for example: "128 MB" return g_strdup_printf (_ ("+%s Runtime"), size_str); } return g_strdup (_ ("Download")); } static char * format_download_size_tooltip (gpointer object, guint64 value) { g_autofree char *size_str = NULL; if (value == 0) return g_strdup (_ ("Size information unavailable")); size_str = g_format_size (value); return g_strdup_printf (_ ("Download size of %s"), size_str); } static char * format_installed_size_tooltip (gpointer object, guint64 value) { g_autofree char *size_str = NULL; if (value == 0) return g_strdup (_ ("Size information unavailable")); size_str = g_format_size (value); return g_strdup_printf (_ ("Installed size of %s"), size_str); } static char * format_age_rating (gpointer object, AsContentRating *content_rating) { guint age; if (content_rating == NULL) return g_strdup ("?"); age = as_content_rating_get_minimum_age (content_rating); if (age < 3) age = 3; /* Translators: Age rating format, e.g. "12+" for ages 12 and up */ return g_strdup_printf (_ ("%d+"), age); } static char * get_age_rating_label (gpointer object, AsContentRating *content_rating) { guint age; if (content_rating == NULL) return g_strdup (_ ("Age Rating")); age = as_content_rating_get_minimum_age (content_rating); if (age == 0) return g_strdup (_ ("All Ages")); else return g_strdup (_ ("Age Rating")); } static char * get_age_rating_tooltip (gpointer object, AsContentRating *content_rating) { guint age; if (content_rating == NULL) return g_strdup (_ ("Age rating information unavailable")); age = as_content_rating_get_minimum_age (content_rating); if (age == 0) return g_strdup (_ ("Suitable for all ages")); return g_strdup_printf (_ ("Suitable for ages %d and up"), age); } static char * get_age_rating_style (gpointer object, AsContentRating *content_rating) { guint age; if (content_rating == NULL) return g_strdup ("grey"); age = as_content_rating_get_minimum_age (content_rating); if (age >= 18) return g_strdup ("error"); else if (age >= 15) return g_strdup ("orange"); else if (age >= 12) return g_strdup ("warning"); else return g_strdup ("grey"); } static char * format_license_tooltip (gpointer object, BzEntry *entry) { const char *license; gboolean is_floss = FALSE; g_autofree char *name = NULL; if (entry == NULL) return g_strdup (_ ("Unknown")); g_object_get (entry, "is-floss", &is_floss, "project-license", &license, NULL); if (license == NULL || *license == '\0') return g_strdup (_ ("Unknown")); if (is_floss && bz_spdx_is_valid (license)) { name = bz_spdx_get_name (license); return g_strdup_printf (_ ("Free software licensed under %s"), (name != NULL && *name != '\0') ? name : license); } if (is_floss) return g_strdup (_ ("Free software")); if (bz_spdx_is_proprietary (license)) return g_strdup (_ ("Proprietary Software")); name = bz_spdx_get_name (license); return g_strdup_printf (_ ("Special License: %s"), (name != NULL && *name != '\0') ? name : license); } static char * get_license_label (gpointer object, BzEntry *entry) { const char *license; gboolean is_floss = FALSE; if (entry == NULL) return g_strdup (_ ("Unknown")); g_object_get (entry, "is-floss", &is_floss, "project-license", &license, NULL); if (is_floss) /* TRANSLATORS: You can omit the "software" part if your translation makes it obvious we're talking about "libre" and not "gratis"*/ return g_strdup (_ ("Free Software")); if (license == NULL || *license == '\0') return g_strdup (_ ("Unknown")); if (bz_spdx_is_proprietary (license)) return g_strdup (_ ("Proprietary")); return g_strdup (_ ("Special License")); } static char * get_license_icon (gpointer object, gboolean is_floss, int index) { const char *icons[][2] = { { "license-symbolic", "proprietary-code-symbolic" }, { "community-symbolic", "license-symbolic" } }; return g_strdup (icons[is_floss ? 1 : 0][index]); } static char * get_formfactor_label (gpointer object, gboolean is_mobile_friendly) { return g_strdup (is_mobile_friendly ? _ ("Adaptive") : _ ("Desktop Only")); } static char * get_formfactor_tooltip (gpointer object, gboolean is_mobile_friendly) { return g_strdup (is_mobile_friendly ? _ ("Works on desktop, tablets, and phones") : _ ("May not work on mobile devices")); } static char * get_safety_rating_icon (gpointer object, BzEntry *entry, int index) { char *icon = NULL; BzImportance importance = 0; if (entry == NULL) return g_strdup ("app-safety-unknown-symbolic"); if (index < 0 || index > 2) return NULL; if (index == 0) { importance = bz_safety_calculator_calculate_rating (entry); switch (importance) { case BZ_IMPORTANCE_UNIMPORTANT: case BZ_IMPORTANCE_NEUTRAL: return g_strdup ("app-safety-ok-symbolic"); case BZ_IMPORTANCE_INFORMATION: case BZ_IMPORTANCE_WARNING: return NULL; case BZ_IMPORTANCE_IMPORTANT: return g_strdup ("dialog-warning-symbolic"); default: return NULL; } } icon = bz_safety_calculator_get_top_icon (entry, index - 1); return icon; } const char * bz_safety_style_for_importance (BzImportance importance) { switch (importance) { case BZ_IMPORTANCE_UNIMPORTANT: case BZ_IMPORTANCE_NEUTRAL: return "grey"; case BZ_IMPORTANCE_INFORMATION: return "warning"; case BZ_IMPORTANCE_WARNING: return "orange"; case BZ_IMPORTANCE_IMPORTANT: return "error"; default: return "grey"; } } static char * get_safety_rating_style (gpointer object, BzEntry *entry) { if (entry == NULL) return g_strdup ("grey"); return g_strdup (bz_safety_style_for_importance ( bz_safety_calculator_calculate_rating (entry))); } static char * get_safety_rating_label (gpointer object, BzEntry *entry) { BzImportance importance; if (entry == NULL) return g_strdup (_ ("N/A")); importance = bz_safety_calculator_calculate_rating (entry); switch (importance) { case BZ_IMPORTANCE_UNIMPORTANT: // TRANSLATORS: short app safety rating label return g_strdup (_ ("Safe")); case BZ_IMPORTANCE_NEUTRAL: // TRANSLATORS: short app safety rating label return g_strdup (_ ("Low Risk")); case BZ_IMPORTANCE_INFORMATION: // TRANSLATORS: short app safety rating label return g_strdup (_ ("Low Risk")); case BZ_IMPORTANCE_WARNING: // TRANSLATORS: short app safety rating label return g_strdup (_ ("Medium Risk")); case BZ_IMPORTANCE_IMPORTANT: // TRANSLATORS: short app safety rating label return g_strdup (_ ("High Risk")); default: return g_strdup (_ ("N/A")); } } void bz_widget_class_bind_all_context_tile_callbacks (GtkWidgetClass *widget_class) { g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); gtk_widget_class_bind_template_callback (widget_class, format_favorites_count); gtk_widget_class_bind_template_callback (widget_class, format_recent_downloads); gtk_widget_class_bind_template_callback (widget_class, format_recent_downloads_tooltip); gtk_widget_class_bind_template_callback (widget_class, format_size); gtk_widget_class_bind_template_callback (widget_class, get_size_tile_child_name); gtk_widget_class_bind_template_callback (widget_class, get_download_size_label); gtk_widget_class_bind_template_callback (widget_class, format_download_size_tooltip); gtk_widget_class_bind_template_callback (widget_class, format_installed_size_tooltip); gtk_widget_class_bind_template_callback (widget_class, format_age_rating); gtk_widget_class_bind_template_callback (widget_class, get_age_rating_label); gtk_widget_class_bind_template_callback (widget_class, get_age_rating_tooltip); gtk_widget_class_bind_template_callback (widget_class, get_age_rating_style); gtk_widget_class_bind_template_callback (widget_class, format_license_tooltip); gtk_widget_class_bind_template_callback (widget_class, get_license_label); gtk_widget_class_bind_template_callback (widget_class, get_license_icon); gtk_widget_class_bind_template_callback (widget_class, get_formfactor_label); gtk_widget_class_bind_template_callback (widget_class, get_formfactor_tooltip); gtk_widget_class_bind_template_callback (widget_class, get_safety_rating_icon); gtk_widget_class_bind_template_callback (widget_class, get_safety_rating_style); gtk_widget_class_bind_template_callback (widget_class, get_safety_rating_label); }