[gnome-software/1166-repository-dialog-design-updates: 8/8] gs-repos-dialog: Implement design updates
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/1166-repository-dialog-design-updates: 8/8] gs-repos-dialog: Implement design updates
- Date: Tue, 27 Jul 2021 14:55:00 +0000 (UTC)
commit 3da85cf5b072df36eff8871305a3d383881ead73
Author: Milan Crha <mcrha redhat com>
Date: Wed Jul 14 10:05:14 2021 +0200
gs-repos-dialog: Implement design updates
Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1166
plugins/flatpak/gs-flatpak-utils.c | 22 +-
plugins/flatpak/gs-flatpak-utils.h | 4 +-
plugins/flatpak/gs-flatpak.c | 2 +-
plugins/fwupd/gs-plugin-fwupd.c | 3 +
plugins/packagekit/gs-plugin-packagekit.c | 4 +
plugins/rpm-ostree/gs-plugin-rpm-ostree.c | 4 +
po/POTFILES.in | 3 +
src/gs-repo-row.c | 403 ++++++++++++++-------
src/gs-repo-row.h | 18 +-
src/gs-repo-row.ui | 188 +++++-----
src/gs-repos-dialog.c | 570 +++++++++++-------------------
src/gs-repos-dialog.ui | 49 +--
src/gs-repos-section.c | 213 +++++++++++
src/gs-repos-section.h | 31 ++
src/gtk-style.css | 28 ++
src/meson.build | 1 +
16 files changed, 870 insertions(+), 673 deletions(-)
---
diff --git a/plugins/flatpak/gs-flatpak-utils.c b/plugins/flatpak/gs-flatpak-utils.c
index 3882583c8..05ee126cd 100644
--- a/plugins/flatpak/gs-flatpak-utils.c
+++ b/plugins/flatpak/gs-flatpak-utils.c
@@ -9,6 +9,8 @@
#include <config.h>
#include <ostree.h>
+#include <glib/gi18n.h>
+
#include "gs-flatpak-app.h"
#include "gs-flatpak.h"
#include "gs-flatpak-utils.h"
@@ -62,7 +64,9 @@ gs_flatpak_error_convert (GError **perror)
}
GsApp *
-gs_flatpak_app_new_from_remote (FlatpakRemote *xremote)
+gs_flatpak_app_new_from_remote (GsPlugin *plugin,
+ FlatpakRemote *xremote,
+ gboolean is_user)
{
g_autofree gchar *title = NULL;
g_autofree gchar *url = NULL;
@@ -77,13 +81,23 @@ gs_flatpak_app_new_from_remote (FlatpakRemote *xremote)
flatpak_remote_get_name (xremote));
gs_app_set_size_download (app, GS_APP_SIZE_UNKNOWABLE);
gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_FLATPAK);
+ gs_app_set_scope (app, is_user ? AS_COMPONENT_SCOPE_USER : AS_COMPONENT_SCOPE_SYSTEM);
+
+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "100");
+ gs_app_set_metadata (app, "GnomeSoftware::InstallationKind",
+ is_user ? _("User Installation") : _("System Installation"));
+ if (!is_user)
+ gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE);
/* title */
title = flatpak_remote_get_title (xremote);
- if (title != NULL) {
+ if (title != NULL)
gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, title);
- gs_app_set_origin_ui (app, title);
- }
+
+ /* origin_ui on a remote is the repo dialogue section name,
+ * not the remote title */
+ gs_app_set_origin_ui (app, _("Applications"));
/* url */
url = flatpak_remote_get_url (xremote);
diff --git a/plugins/flatpak/gs-flatpak-utils.h b/plugins/flatpak/gs-flatpak-utils.h
index 61cd62d37..1f1fa3689 100644
--- a/plugins/flatpak/gs-flatpak-utils.h
+++ b/plugins/flatpak/gs-flatpak-utils.h
@@ -13,7 +13,9 @@ G_BEGIN_DECLS
#include <gnome-software.h>
void gs_flatpak_error_convert (GError **perror);
-GsApp *gs_flatpak_app_new_from_remote (FlatpakRemote *xremote);
+GsApp *gs_flatpak_app_new_from_remote (GsPlugin *plugin,
+ FlatpakRemote *xremote,
+ gboolean is_user);
GsApp *gs_flatpak_app_new_from_repo_file (GFile *file,
GCancellable *cancellable,
GError **error);
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index 570bdd0b6..54f7792c0 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -379,7 +379,7 @@ gs_flatpak_create_source (GsFlatpak *self, FlatpakRemote *xremote)
g_autoptr(GsApp) app = NULL;
/* create a temp GsApp */
- app = gs_flatpak_app_new_from_remote (xremote);
+ app = gs_flatpak_app_new_from_remote (self->plugin, xremote, flatpak_installation_get_is_user
(self->installation));
gs_flatpak_claim_app (self, app);
/* we already have one, returned the ref'd cached copy */
diff --git a/plugins/fwupd/gs-plugin-fwupd.c b/plugins/fwupd/gs-plugin-fwupd.c
index 1a6002732..7b2256141 100644
--- a/plugins/fwupd/gs-plugin-fwupd.c
+++ b/plugins/fwupd/gs-plugin-fwupd.c
@@ -1175,6 +1175,9 @@ gs_plugin_add_sources (GsPlugin *plugin,
gs_app_set_metadata (app, "fwupd::remote-id",
fwupd_remote_get_id (remote));
gs_app_set_management_plugin (app, "fwupd");
+ gs_app_set_metadata (app, "GnomeSoftware::PackagingFormat", "fwupd");
+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "800");
+ gs_app_set_origin_ui (app, _("Firmware"));
gs_app_list_add (list, app);
}
return TRUE;
diff --git a/plugins/packagekit/gs-plugin-packagekit.c b/plugins/packagekit/gs-plugin-packagekit.c
index 068bb306c..785d03066 100644
--- a/plugins/packagekit/gs-plugin-packagekit.c
+++ b/plugins/packagekit/gs-plugin-packagekit.c
@@ -11,6 +11,7 @@
#include <config.h>
+#include <glib/gi18n-lib.h>
#include <gnome-software.h>
#include <gsettings-desktop-schemas/gdesktop-enums.h>
#include <packagekit-glib2/packagekit.h>
@@ -294,6 +295,9 @@ gs_plugin_add_sources (GsPlugin *plugin,
gs_app_set_summary (app,
GS_APP_QUALITY_LOWEST,
pk_repo_detail_get_description (rd));
+ gs_plugin_packagekit_set_packaging_format (plugin, app);
+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "300");
+ gs_app_set_origin_ui (app, _("Packages"));
gs_app_list_add (list, app);
g_hash_table_insert (hash,
g_strdup (id),
diff --git a/plugins/rpm-ostree/gs-plugin-rpm-ostree.c b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
index ac26448b7..84eadc9ca 100644
--- a/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
+++ b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c
@@ -14,6 +14,7 @@
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
#include <libdnf/libdnf.h>
#include <ostree.h>
#include <rpm/rpmdb.h>
@@ -2146,6 +2147,9 @@ gs_plugin_add_sources (GsPlugin *plugin,
gs_app_set_name (app, GS_APP_QUALITY_LOWEST, description);
gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, description);
+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "200");
+ gs_app_set_origin_ui (app, _("Operating System (RPM-OStree)"));
+
gs_app_list_add (list, app);
}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9fe738f85..3e89dc119 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -90,9 +90,12 @@ plugins/eos-updater/gs-plugin-eos-updater.c
plugins/fedora-pkgdb-collections/gs-plugin-fedora-pkgdb-collections.c
plugins/flatpak/org.gnome.Software.Plugin.Flatpak.metainfo.xml.in
plugins/flatpak/gs-flatpak.c
+plugins/flatpak/gs-flatpak-utils.c
plugins/flatpak/gs-plugin-flatpak.c
plugins/fwupd/gs-fwupd-app.c
plugins/fwupd/gs-plugin-fwupd.c
plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in
+plugins/packagekit/gs-plugin-packagekit.c
+plugins/rpm-ostree/gs-plugin-rpm-ostree.c
plugins/snap/gs-plugin-snap.c
plugins/snap/org.gnome.Software.Plugin.Snap.metainfo.xml.in
diff --git a/src/gs-repo-row.c b/src/gs-repo-row.c
index e9529e9e3..94ff13b08 100644
--- a/src/gs-repo-row.c
+++ b/src/gs-repo-row.c
@@ -8,153 +8,94 @@
#include "config.h"
-#include "gs-repo-row.h"
-
#include <glib/gi18n.h>
+#include <libsoup/soup.h>
+
+#include "gs-repo-row.h"
typedef struct
{
+ GsPluginLoader *plugin_loader; /* owned */
GsApp *repo;
- GtkWidget *button;
GtkWidget *name_label;
+ GtkWidget *hostname_label;
GtkWidget *comment_label;
- GtkWidget *details_revealer;
- GtkWidget *status_label;
- GtkWidget *url_box;
- GtkWidget *url_label;
+ GtkWidget *remove_button;
+ GtkWidget *disable_switch;
+ gulong switch_handler_id;
guint refresh_idle_id;
+ guint busy_counter;
+ gboolean supports_remove;
+ gboolean supports_enable_disable;
} GsRepoRowPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GsRepoRow, gs_repo_row, GTK_TYPE_LIST_BOX_ROW)
enum {
- SIGNAL_BUTTON_CLICKED,
+ SIGNAL_REMOVE_CLICKED,
+ SIGNAL_SWITCH_CLICKED,
SIGNAL_LAST
};
static guint signals [SIGNAL_LAST] = { 0 };
-void
-gs_repo_row_set_name (GsRepoRow *row, const gchar *name)
-{
- GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
-
- gtk_label_set_text (GTK_LABEL (priv->name_label), name);
- gtk_widget_set_visible (priv->name_label, name != NULL);
-}
-
-void
-gs_repo_row_set_comment (GsRepoRow *row, const gchar *comment)
-{
- GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
-
- gtk_label_set_markup (GTK_LABEL (priv->comment_label), comment);
- gtk_widget_set_visible (priv->comment_label, comment != NULL);
-}
-
-void
-gs_repo_row_set_url (GsRepoRow *row, const gchar *url)
-{
- GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
-
- gtk_label_set_text (GTK_LABEL (priv->url_label), url);
- gtk_widget_set_visible (priv->url_box, url != NULL);
-}
-
-static gboolean
-repo_supports_removal (GsApp *repo)
-{
- const gchar *management_plugin = gs_app_get_management_plugin (repo);
-
- /* can't remove a repo, only enable/disable existing ones */
- if (g_strcmp0 (management_plugin, "fwupd") == 0 ||
- g_strcmp0 (management_plugin, "packagekit") == 0 ||
- g_strcmp0 (management_plugin, "rpm-ostree") == 0)
- return FALSE;
-
- return TRUE;
-}
-
static void
refresh_ui (GsRepoRow *row)
{
GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+ gboolean active = FALSE;
+ gboolean state_sensitive = FALSE;
+ gboolean busy = priv->busy_counter> 0;
+ gboolean is_system_repo;
if (priv->repo == NULL) {
- gtk_widget_set_visible (priv->button, FALSE);
+ gtk_widget_set_sensitive (priv->disable_switch, FALSE);
+ gtk_switch_set_active (GTK_SWITCH (priv->disable_switch), FALSE);
return;
}
- gtk_widget_set_visible (priv->button, TRUE);
+ g_signal_handler_block (priv->disable_switch, priv->switch_handler_id);
+ gtk_widget_set_sensitive (priv->disable_switch, TRUE);
- /* set button text */
switch (gs_app_get_state (priv->repo)) {
case GS_APP_STATE_AVAILABLE:
case GS_APP_STATE_AVAILABLE_LOCAL:
- /* TRANSLATORS: this is a button in the software repositories
- dialog for enabling a repo */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("_Enable"));
- /* enable button */
- gtk_widget_set_sensitive (priv->button, TRUE);
+ active = FALSE;
+ state_sensitive = TRUE;
break;
case GS_APP_STATE_INSTALLED:
- if (repo_supports_removal (priv->repo)) {
- /* TRANSLATORS: this is a button in the software repositories dialog
- for removing a repo. The ellipsis indicates that further
- steps are required */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("_Remove…"));
- } else {
- /* TRANSLATORS: this is a button in the software repositories dialog
- for disabling a repo. The ellipsis indicates that further
- steps are required */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("_Disable…"));
- }
- /* enable button */
- gtk_widget_set_sensitive (priv->button, TRUE);
+ active = TRUE;
break;
case GS_APP_STATE_INSTALLING:
- /* TRANSLATORS: this is a button in the software repositories dialog
- that shows the status of a repo being enabled */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("Enabling"));
- /* disable button */
- gtk_widget_set_sensitive (priv->button, FALSE);
+ active = TRUE;
+ busy = TRUE;
break;
case GS_APP_STATE_REMOVING:
- if (repo_supports_removal (priv->repo)) {
- /* TRANSLATORS: this is a button in the software repositories dialog
- that shows the status of a repo being removed */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("Removing"));
- } else {
- /* TRANSLATORS: this is a button in the software repositories dialog
- that shows the status of a repo being disabled */
- gtk_button_set_label (GTK_BUTTON (priv->button), _("Disabling"));
- }
- /* disable button */
- gtk_widget_set_sensitive (priv->button, FALSE);
+ active = FALSE;
+ busy = TRUE;
break;
case GS_APP_STATE_UNAVAILABLE:
+ g_signal_handler_unblock (priv->disable_switch, priv->switch_handler_id);
gtk_widget_destroy (GTK_WIDGET (row));
return;
default:
+ state_sensitive = TRUE;
break;
}
- /* set enabled/disabled label */
- switch (gs_app_get_state (priv->repo)) {
- case GS_APP_STATE_INSTALLED:
- /* TRANSLATORS: this is a label in the software repositories
- dialog that indicates that a repo is enabled. */
- gtk_label_set_text (GTK_LABEL (priv->status_label), _("Enabled"));
- break;
- case GS_APP_STATE_AVAILABLE:
- case GS_APP_STATE_AVAILABLE_LOCAL:
- /* TRANSLATORS: this is a label in the software repositories
- dialog that indicates that a repo is disabled. */
- gtk_label_set_text (GTK_LABEL (priv->status_label), _("Disabled"));
- break;
- default:
- break;
- }
+ is_system_repo = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE);
+
+ /* Disable for the system repos, if installed */
+ gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive ||
!is_system_repo));
+ gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_system_repo);
+
+ /* Set only the 'state' to visually indicate the state is not saved yet */
+ if (busy)
+ gtk_switch_set_state (GTK_SWITCH (priv->disable_switch), active);
+ else
+ gtk_switch_set_active (GTK_SWITCH (priv->disable_switch), active);
+
+ g_signal_handler_unblock (priv->disable_switch, priv->switch_handler_id);
}
static gboolean
@@ -180,10 +121,78 @@ repo_state_changed_cb (GsApp *repo, GParamSpec *pspec, GsRepoRow *row)
priv->refresh_idle_id = g_idle_add (refresh_idle, g_object_ref (row));
}
-void
+static gchar *
+get_repo_installed_text (GsApp *repo)
+{
+ GsAppList *related;
+ guint cnt_addon = 0;
+ guint cnt_apps = 0;
+ g_autofree gchar *addons_text = NULL;
+ g_autofree gchar *apps_text = NULL;
+
+ related = gs_app_get_related (repo);
+ for (guint i = 0; i < gs_app_list_length (related); i++) {
+ GsApp *app_tmp = gs_app_list_index (related, i);
+ switch (gs_app_get_kind (app_tmp)) {
+ case AS_COMPONENT_KIND_WEB_APP:
+ case AS_COMPONENT_KIND_DESKTOP_APP:
+ cnt_apps++;
+ break;
+ case AS_COMPONENT_KIND_FONT:
+ case AS_COMPONENT_KIND_CODEC:
+ case AS_COMPONENT_KIND_INPUT_METHOD:
+ case AS_COMPONENT_KIND_ADDON:
+ cnt_addon++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (cnt_addon == 0) {
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ installed' sentence, describing a software repository. */
+ return g_strdup_printf (ngettext ("%u application installed",
+ "%u applications installed",
+ cnt_apps), cnt_apps);
+ }
+ if (cnt_apps == 0) {
+ /* TRANSLATORS: This string is used to construct the 'X add-ons
+ installed' sentence, describing a software repository. */
+ return g_strdup_printf (ngettext ("%u add-on installed",
+ "%u add-ons installed",
+ cnt_addon), cnt_addon);
+ }
+
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the number of applications. */
+ apps_text = g_strdup_printf (ngettext ("%u application",
+ "%u applications",
+ cnt_apps), cnt_apps);
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the number of add-ons. */
+ addons_text = g_strdup_printf (ngettext ("%u add-on",
+ "%u add-ons",
+ cnt_addon), cnt_addon);
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the total number of
+ applications and add-ons. */
+ return g_strdup_printf (ngettext ("%s and %s installed",
+ "%s and %s installed",
+ cnt_apps + cnt_addon),
+ apps_text, addons_text);
+}
+
+static void
gs_repo_row_set_repo (GsRepoRow *row, GsApp *repo)
{
GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+ GsPlugin *plugin;
+ g_autofree gchar *comment = NULL;
+ const gchar *tmp;
g_assert (priv->repo == NULL);
@@ -191,6 +200,42 @@ gs_repo_row_set_repo (GsRepoRow *row, GsApp *repo)
g_signal_connect_object (priv->repo, "notify::state",
G_CALLBACK (repo_state_changed_cb),
row, 0);
+
+ plugin = gs_plugin_loader_find_plugin (priv->plugin_loader, gs_app_get_management_plugin (repo));
+ priv->supports_remove = plugin != NULL && gs_plugin_get_action_supported (plugin,
GS_PLUGIN_ACTION_REMOVE_REPO);
+ priv->supports_enable_disable = plugin != NULL &&
+ gs_plugin_get_action_supported (plugin, GS_PLUGIN_ACTION_ENABLE_REPO) &&
+ gs_plugin_get_action_supported (plugin, GS_PLUGIN_ACTION_DISABLE_REPO);
+
+ gtk_label_set_label (GTK_LABEL (priv->name_label), gs_app_get_name (repo));
+
+ gtk_widget_set_visible (priv->hostname_label, FALSE);
+
+ tmp = gs_app_get_url (repo, AS_URL_KIND_HOMEPAGE);
+ if (tmp != NULL && *tmp != '\0') {
+ g_autoptr(SoupURI) uri = NULL;
+
+ uri = soup_uri_new (tmp);
+ if (uri && soup_uri_get_host (uri) != NULL && *soup_uri_get_host (uri) != '\0') {
+ gtk_label_set_label (GTK_LABEL (priv->hostname_label), soup_uri_get_host (uri));
+ gtk_widget_set_visible (priv->hostname_label, TRUE);
+ }
+ }
+
+ comment = get_repo_installed_text (repo);
+ tmp = gs_app_get_metadata_item (priv->repo, "GnomeSoftware::InstallationKind");
+ if (tmp != NULL && *tmp != '\0') {
+ gchar *cnt;
+
+ /* Translators: The first '%s' is replaced with a text like '10 applications installed',
+ the second '%s' is replaced with installation kind, like in case of Flatpak 'User
Installation'. */
+ cnt = g_strdup_printf (C_("repo-row", "%s • %s"), comment, tmp);
+ g_clear_pointer (&comment, g_free);
+ comment = cnt;
+ }
+
+ gtk_label_set_label (GTK_LABEL (priv->comment_label), comment);
+
refresh_ui (row);
}
@@ -201,35 +246,33 @@ gs_repo_row_get_repo (GsRepoRow *row)
return priv->repo;
}
-void
-gs_repo_row_show_details (GsRepoRow *row)
+static void
+disable_switch_clicked_cb (GtkWidget *widget,
+ GParamSpec *param,
+ GsRepoRow *row)
{
GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
- gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
- gtk_revealer_set_reveal_child (GTK_REVEALER (priv->details_revealer), TRUE);
-}
+ g_return_if_fail (GS_IS_REPO_ROW (row));
-void
-gs_repo_row_hide_details (GsRepoRow *row)
-{
- GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+ if (priv->repo == NULL || priv->busy_counter > 0)
+ return;
- gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
- gtk_revealer_set_reveal_child (GTK_REVEALER (priv->details_revealer), FALSE);
+ g_signal_emit (row, signals[SIGNAL_SWITCH_CLICKED], 0);
}
-void
-gs_repo_row_show_status (GsRepoRow *row)
+static void
+gs_repo_row_remove_button_clicked_cb (GtkWidget *button,
+ GsRepoRow *row)
{
GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
- gtk_widget_set_visible (priv->status_label, TRUE);
-}
-static void
-button_clicked_cb (GtkWidget *widget, GsRepoRow *row)
-{
- g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0);
+ g_return_if_fail (GS_IS_REPO_ROW (row));
+
+ if (priv->repo == NULL || priv->busy_counter)
+ return;
+
+ g_signal_emit (row, signals[SIGNAL_REMOVE_CLICKED], 0);
}
static void
@@ -248,6 +291,8 @@ gs_repo_row_destroy (GtkWidget *object)
priv->refresh_idle_id = 0;
}
+ g_clear_object (&priv->plugin_loader);
+
GTK_WIDGET_CLASS (gs_repo_row_parent_class)->destroy (object);
}
@@ -255,10 +300,15 @@ static void
gs_repo_row_init (GsRepoRow *row)
{
GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+ GtkWidget *image;
gtk_widget_init_template (GTK_WIDGET (row));
- g_signal_connect (priv->button, "clicked",
- G_CALLBACK (button_clicked_cb), row);
+ priv->switch_handler_id = g_signal_connect (priv->disable_switch, "notify::active",
+ G_CALLBACK (disable_switch_clicked_cb), row);
+ image = gtk_image_new_from_icon_name ("user-trash-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (priv->remove_button), image);
+ g_signal_connect (priv->remove_button, "clicked",
+ G_CALLBACK (gs_repo_row_remove_button_clicked_cb), row);
}
static void
@@ -269,26 +319,109 @@ gs_repo_row_class_init (GsRepoRowClass *klass)
widget_class->destroy = gs_repo_row_destroy;
- signals [SIGNAL_BUTTON_CLICKED] =
- g_signal_new ("button-clicked",
+ signals [SIGNAL_REMOVE_CLICKED] =
+ g_signal_new ("remove-clicked",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsRepoRowClass, remove_clicked),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ signals [SIGNAL_SWITCH_CLICKED] =
+ g_signal_new ("switch-clicked",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (GsRepoRowClass, button_clicked),
+ G_STRUCT_OFFSET (GsRepoRowClass, switch_clicked),
NULL, NULL, g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ gtk_widget_class_set_css_name (widget_class, "reporow");
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-repo-row.ui");
- gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, button);
gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, name_label);
+ gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, hostname_label);
gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, comment_label);
- gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, details_revealer);
- gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, status_label);
- gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, url_box);
- gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, url_label);
+ gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, remove_button);
+ gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, disable_switch);
}
GtkWidget *
-gs_repo_row_new (void)
+gs_repo_row_new (GsPluginLoader *plugin_loader,
+ GsApp *repo)
{
- return g_object_new (GS_TYPE_REPO_ROW, NULL);
+ GsRepoRow *row = g_object_new (GS_TYPE_REPO_ROW, NULL);
+ GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+ priv->plugin_loader = g_object_ref (plugin_loader);
+ gs_repo_row_set_repo (row, repo);
+ return GTK_WIDGET (row);
+}
+
+static void
+gs_repo_row_change_busy (GsRepoRow *row,
+ gboolean value)
+{
+ GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+
+ g_return_if_fail (GS_IS_REPO_ROW (row));
+
+ if (value)
+ g_return_if_fail (priv->busy_counter + 1 > priv->busy_counter);
+ else
+ g_return_if_fail (priv->busy_counter > 0);
+
+ priv->busy_counter += (value ? 1 : -1);
+
+ if (value && priv->busy_counter == 1)
+ gtk_widget_set_sensitive (priv->disable_switch, FALSE);
+ else if (!value && !priv->busy_counter)
+ refresh_ui (row);
+}
+
+/**
+ * gs_repo_row_mark_busy:
+ * @row: a #GsRepoRow
+ *
+ * Mark the @row as busy, that is the @row has pending operation(s).
+ * Unmark the @row as busy with gs_repo_row_unmark_busy() once
+ * the operation is done. This can be called mutliple times, only call
+ * the gs_repo_row_unmark_busy() as many times as this function had
+ * been called.
+ *
+ * Since: 41
+ **/
+void
+gs_repo_row_mark_busy (GsRepoRow *row)
+{
+ gs_repo_row_change_busy (row, TRUE);
+}
+
+/**
+ * gs_repo_row_unmark_busy:
+ * @row: a #GsRepoRow
+ *
+ * A pair function for gs_repo_row_mark_busy().
+ *
+ * Since: 41
+ **/
+void
+gs_repo_row_unmark_busy (GsRepoRow *row)
+{
+ gs_repo_row_change_busy (row, FALSE);
+}
+
+/**
+ * gs_repo_row_get_is_busy:
+ * @row: a #GsRepoRow
+ *
+ * Returns: %TRUE, when there is any pending operation for the @row
+ *
+ * Since: 41
+ **/
+gboolean
+gs_repo_row_get_is_busy (GsRepoRow *row)
+{
+ GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row);
+
+ g_return_val_if_fail (GS_IS_REPO_ROW (row), FALSE);
+
+ return priv->busy_counter > 0;
}
diff --git a/src/gs-repo-row.h b/src/gs-repo-row.h
index ff2564ea5..03ae94f0f 100644
--- a/src/gs-repo-row.h
+++ b/src/gs-repo-row.h
@@ -20,21 +20,15 @@ G_DECLARE_DERIVABLE_TYPE (GsRepoRow, gs_repo_row, GS, REPO_ROW, GtkListBoxRow)
struct _GsRepoRowClass
{
GtkListBoxRowClass parent_class;
- void (*button_clicked) (GsRepoRow *row);
+ void (*remove_clicked) (GsRepoRow *row);
+ void (*switch_clicked) (GsRepoRow *row);
};
-GtkWidget *gs_repo_row_new (void);
-void gs_repo_row_set_name (GsRepoRow *row,
- const gchar *name);
-void gs_repo_row_set_comment (GsRepoRow *row,
- const gchar *comment);
-void gs_repo_row_set_url (GsRepoRow *row,
- const gchar *url);
-void gs_repo_row_set_repo (GsRepoRow *row,
+GtkWidget *gs_repo_row_new (GsPluginLoader *plugin_loader,
GsApp *repo);
GsApp *gs_repo_row_get_repo (GsRepoRow *row);
-void gs_repo_row_show_details (GsRepoRow *row);
-void gs_repo_row_hide_details (GsRepoRow *row);
-void gs_repo_row_show_status (GsRepoRow *row);
+void gs_repo_row_mark_busy (GsRepoRow *row);
+void gs_repo_row_unmark_busy (GsRepoRow *row);
+gboolean gs_repo_row_get_is_busy (GsRepoRow *row);
G_END_DECLS
diff --git a/src/gs-repo-row.ui b/src/gs-repo-row.ui
index 2cbe02532..86deee91c 100644
--- a/src/gs-repo-row.ui
+++ b/src/gs-repo-row.ui
@@ -2,118 +2,94 @@
<interface>
<!-- interface-requires gtk+ 3.10 -->
<template class="GsRepoRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
<child>
- <object class="GtkBox">
+ <object class="GtkGrid">
<property name="visible">True</property>
- <property name="margin-top">18</property>
- <property name="margin-bottom">18</property>
- <property name="margin-start">18</property>
- <property name="margin-end">18</property>
- <property name="orientation">horizontal</property>
- <property name="spacing">16</property>
+ <property name="margin">16</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">4</property>
<child>
- <object class="GtkBox" id="vbox">
+ <object class="GtkLabel" id="name_label">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
+ <property name="halign">start</property>
<property name="hexpand">True</property>
- <child>
- <object class="GtkBox" id="hbox">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <property name="spacing">4</property>
- <child>
- <object class="GtkLabel" id="name_label">
- <property name="visible">True</property>
- <property name="halign">start</property>
- <property name="ellipsize">end</property>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="status_label">
- <property name="visible">False</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="pack-type">end</property>
- </packing>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="comment_label">
- <property name="visible">False</property>
- <property name="halign">start</property>
- <property name="xalign">0</property>
- <property name="wrap">True</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkRevealer" id="details_revealer">
- <property name="visible">True</property>
- <property name="transition-type">slide-down</property>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkBox" id="url_box">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <property name="spacing">8</property>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="halign">start</property>
- <property name="valign">start</property>
- <property name="label" translatable="yes">URL</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="url_label">
- <property name="visible">True</property>
- <property name="halign">start</property>
- <property name="valign">start</property>
- <property name="ellipsize">end</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <property name="spacing">8</property>
- <child>
- <object class="GtkButton" id="button">
- <property name="visible">True</property>
- <property name="use_underline">True</property>
- <property name="width_request">105</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="halign">start</property>
- <property name="valign">start</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
+ <property name="ellipsize">end</property>
</object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="hostname_label">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="comment_label">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="scale" value="0.8"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="remove_button">
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="hexpand">False</property>
+ <property name="always-show-image">True</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ <property name="height">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="disable_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="hexpand">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ <property name="height">3</property>
+ </packing>
</child>
</object>
</child>
diff --git a/src/gs-repos-dialog.c b/src/gs-repos-dialog.c
index d5a4547ee..2a74bfb29 100644
--- a/src/gs-repos-dialog.c
+++ b/src/gs-repos-dialog.c
@@ -16,6 +16,7 @@
#include "gs-common.h"
#include "gs-os-release.h"
#include "gs-repo-row.h"
+#include "gs-repos-section.h"
#include "gs-third-party-repo-row.h"
#include "gs-utils.h"
#include <glib/gi18n.h>
@@ -25,15 +26,13 @@ struct _GsReposDialog
GtkDialog parent_instance;
GSettings *settings;
GsApp *third_party_repo;
+ GHashTable *sections; /* gchar * ~> GsReposSection * */
GCancellable *cancellable;
GsPluginLoader *plugin_loader;
- GtkWidget *label_description;
GtkWidget *label_empty;
GtkWidget *label_header;
- GtkWidget *listbox;
- GtkWidget *listbox_third_party;
- GtkWidget *row_third_party;
+ GtkWidget *content_box;
GtkWidget *spinner;
GtkWidget *stack;
};
@@ -43,6 +42,7 @@ G_DEFINE_TYPE (GsReposDialog, gs_repos_dialog, GTK_TYPE_DIALOG)
typedef struct {
GsReposDialog *dialog;
GsApp *repo;
+ GWeakRef row_weakref;
GsPluginAction action;
} InstallRemoveData;
@@ -51,97 +51,12 @@ install_remove_data_free (InstallRemoveData *data)
{
g_clear_object (&data->dialog);
g_clear_object (&data->repo);
+ g_weak_ref_clear (&data->row_weakref);
g_slice_free (InstallRemoveData, data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstallRemoveData, install_remove_data_free);
-static void reload_sources (GsReposDialog *dialog);
-static void reload_third_party_repo (GsReposDialog *dialog);
-
-static gchar *
-get_repo_installed_text (GsApp *repo)
-{
- GsAppList *related;
- guint cnt_addon = 0;
- guint cnt_apps = 0;
- g_autofree gchar *addons_text = NULL;
- g_autofree gchar *apps_text = NULL;
-
- related = gs_app_get_related (repo);
- for (guint i = 0; i < gs_app_list_length (related); i++) {
- GsApp *app_tmp = gs_app_list_index (related, i);
- switch (gs_app_get_kind (app_tmp)) {
- case AS_COMPONENT_KIND_WEB_APP:
- case AS_COMPONENT_KIND_DESKTOP_APP:
- cnt_apps++;
- break;
- case AS_COMPONENT_KIND_FONT:
- case AS_COMPONENT_KIND_CODEC:
- case AS_COMPONENT_KIND_INPUT_METHOD:
- case AS_COMPONENT_KIND_ADDON:
- cnt_addon++;
- break;
- default:
- break;
- }
- }
-
- if (cnt_apps == 0 && cnt_addon == 0) {
- /* nothing! */
- return NULL;
- }
- if (cnt_addon == 0) {
- /* TRANSLATORS: This string is used to construct the 'X applications
- installed' sentence, describing a software repository. */
- return g_strdup_printf (ngettext ("%u application installed",
- "%u applications installed",
- cnt_apps), cnt_apps);
- }
- if (cnt_apps == 0) {
- /* TRANSLATORS: This string is used to construct the 'X add-ons
- installed' sentence, describing a software repository. */
- return g_strdup_printf (ngettext ("%u add-on installed",
- "%u add-ons installed",
- cnt_addon), cnt_addon);
- }
-
- /* TRANSLATORS: This string is used to construct the 'X applications
- and y add-ons installed' sentence, describing a software repository.
- The correct form here depends on the number of applications. */
- apps_text = g_strdup_printf (ngettext ("%u application",
- "%u applications",
- cnt_apps), cnt_apps);
- /* TRANSLATORS: This string is used to construct the 'X applications
- and y add-ons installed' sentence, describing a software repository.
- The correct form here depends on the number of add-ons. */
- addons_text = g_strdup_printf (ngettext ("%u add-on",
- "%u add-ons",
- cnt_addon), cnt_addon);
- /* TRANSLATORS: This string is used to construct the 'X applications
- and y add-ons installed' sentence, describing a software repository.
- The correct form here depends on the total number of
- applications and add-ons. */
- return g_strdup_printf (ngettext ("%s and %s installed",
- "%s and %s installed",
- cnt_apps + cnt_addon),
- apps_text, addons_text);
-}
-
-static gboolean
-repo_supports_removal (GsApp *repo)
-{
- const gchar *management_plugin = gs_app_get_management_plugin (repo);
-
- /* can't remove a repo, only enable/disable existing ones */
- if (g_strcmp0 (management_plugin, "fwupd") == 0 ||
- g_strcmp0 (management_plugin, "packagekit") == 0 ||
- g_strcmp0 (management_plugin, "rpm-ostree") == 0)
- return FALSE;
-
- return TRUE;
-}
-
static void
repo_enabled_cb (GObject *source,
GAsyncResult *res,
@@ -150,9 +65,13 @@ repo_enabled_cb (GObject *source,
GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
g_autoptr(InstallRemoveData) install_remove_data = (InstallRemoveData *) user_data;
g_autoptr(GError) error = NULL;
+ g_autoptr(GsRepoRow) row = NULL;
const gchar *action_str;
action_str = gs_plugin_action_to_string (install_remove_data->action);
+ row = g_weak_ref_get (&install_remove_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
@@ -194,21 +113,30 @@ enable_repo_response_cb (GtkDialog *confirm_dialog,
gtk_widget_destroy (GTK_WIDGET (confirm_dialog));
/* not agreed */
- if (response != GTK_RESPONSE_OK)
+ if (response != GTK_RESPONSE_OK) {
+ g_autoptr(GsRepoRow) row = g_weak_ref_get (&install_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
return;
+ }
_enable_repo (g_steal_pointer (&install_data));
}
static void
-enable_repo (GsReposDialog *dialog, GsApp *repo)
+enable_repo (GsReposDialog *dialog,
+ GsRepoRow *row,
+ GsApp *repo)
{
g_autoptr(InstallRemoveData) install_data = NULL;
install_data = g_slice_new0 (InstallRemoveData);
install_data->action = GS_PLUGIN_ACTION_ENABLE_REPO;
- install_data->repo = g_object_ref (repo);
install_data->dialog = g_object_ref (dialog);
+ install_data->repo = g_object_ref (repo);
+ g_weak_ref_init (&install_data->row_weakref, row);
+
+ gs_repo_row_mark_busy (row);
/* user needs to confirm acceptance of an agreement */
if (gs_app_get_agreement (repo) != NULL) {
@@ -266,8 +194,12 @@ remove_repo_response_cb (GtkDialog *confirm_dialog,
gtk_widget_destroy (GTK_WIDGET (confirm_dialog));
/* not agreed */
- if (response != GTK_RESPONSE_OK)
+ if (response != GTK_RESPONSE_OK) {
+ g_autoptr(GsRepoRow) row = g_weak_ref_get (&remove_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
return;
+ }
g_debug ("removing repo %s", gs_app_get_id (remove_data->repo));
plugin_job = gs_plugin_job_newv (remove_data->action,
@@ -281,54 +213,44 @@ remove_repo_response_cb (GtkDialog *confirm_dialog,
}
static void
-remove_confirm_repo (GsReposDialog *dialog, GsApp *repo)
+remove_confirm_repo (GsReposDialog *dialog,
+ GsRepoRow *row,
+ GsApp *repo,
+ GsPluginAction action)
{
InstallRemoveData *remove_data;
GtkWidget *confirm_dialog;
g_autofree gchar *message = NULL;
- g_autofree gchar *title = NULL;
GtkWidget *button;
GtkStyleContext *context;
remove_data = g_slice_new0 (InstallRemoveData);
- remove_data->repo = g_object_ref (repo);
+ remove_data->action = action;
remove_data->dialog = g_object_ref (dialog);
+ remove_data->repo = g_object_ref (repo);
+ g_weak_ref_init (&remove_data->row_weakref, row);
- if (repo_supports_removal (repo)) {
- remove_data->action = GS_PLUGIN_ACTION_REMOVE_REPO;
- /* TRANSLATORS: this is a prompt message, and '%s' is a
- * repository name, e.g. 'GNOME Nightly' */
- title = g_strdup_printf (_("Remove “%s”?"),
- gs_app_get_name (repo));
- } else {
- remove_data->action = GS_PLUGIN_ACTION_DISABLE_REPO;
- /* TRANSLATORS: this is a prompt message, and '%s' is a
- * repository name, e.g. 'GNOME Nightly' */
- title = g_strdup_printf (_("Disable “%s”?"),
- gs_app_get_name (repo));
- }
- /* TRANSLATORS: longer dialog text */
- message = g_strdup (_("Software that has been installed from this "
- "repository will no longer receive updates, "
- "including security fixes."));
+ /* TRANSLATORS: The '%s' is replaced with a repository name, like "Fedora Modular - x86_64" */
+ message = g_strdup_printf (_("Software that has been installed from “%s” will cease receive
updates."),
+ gs_app_get_name (repo));
/* ask for confirmation */
confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_CANCEL,
- "%s", title);
+ "%s",
+ action == GS_PLUGIN_ACTION_DISABLE_REPO ? _("Disable
Repository?") : _("Remove Repository?"));
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog),
"%s", message);
- if (repo_supports_removal (repo)) {
- /* TRANSLATORS: this is button text to remove the repo */
- button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Remove"), GTK_RESPONSE_OK);
+ if (action == GS_PLUGIN_ACTION_DISABLE_REPO) {
+ /* TRANSLATORS: this is button text to disable a repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Disable"), GTK_RESPONSE_OK);
} else {
- /* TRANSLATORS: this is button text to remove the repo */
- button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Disable"), GTK_RESPONSE_OK);
+ /* TRANSLATORS: this is button text to remove a repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Remove"), GTK_RESPONSE_OK);
}
-
context = gtk_widget_get_style_context (button);
gtk_style_context_add_class (context, "destructive-action");
@@ -338,11 +260,14 @@ remove_confirm_repo (GsReposDialog *dialog, GsApp *repo)
gtk_window_set_modal (GTK_WINDOW (confirm_dialog), TRUE);
gtk_window_present (GTK_WINDOW (confirm_dialog));
+
+ gs_repo_row_mark_busy (row);
}
static void
-repo_button_clicked_cb (GsRepoRow *row,
- GsReposDialog *dialog)
+repo_section_switch_clicked_cb (GsReposSection *section,
+ GsRepoRow *row,
+ GsReposDialog *dialog)
{
GsApp *repo;
@@ -351,10 +276,10 @@ repo_button_clicked_cb (GsRepoRow *row,
switch (gs_app_get_state (repo)) {
case GS_APP_STATE_AVAILABLE:
case GS_APP_STATE_AVAILABLE_LOCAL:
- enable_repo (dialog, repo);
+ enable_repo (dialog, row, repo);
break;
case GS_APP_STATE_INSTALLED:
- remove_confirm_repo (dialog, repo);
+ remove_confirm_repo (dialog, row, repo, GS_PLUGIN_ACTION_DISABLE_REPO);
break;
default:
g_warning ("repo %s button clicked in unexpected state %s",
@@ -364,8 +289,18 @@ repo_button_clicked_cb (GsRepoRow *row,
}
}
-static GtkListBox *
-get_list_box_for_repo (GsReposDialog *dialog, GsApp *repo)
+static void
+repo_section_remove_clicked_cb (GsReposSection *section,
+ GsRepoRow *row,
+ GsReposDialog *dialog)
+{
+ GsApp *repo = gs_repo_row_get_repo (row);
+ remove_confirm_repo (dialog, row, repo, GS_PLUGIN_ACTION_REMOVE_REPO);
+}
+
+static gboolean
+is_third_party_repo (GsReposDialog *dialog,
+ GsApp *repo)
{
if (dialog->third_party_repo != NULL) {
const gchar *source_repo;
@@ -375,19 +310,20 @@ get_list_box_for_repo (GsReposDialog *dialog, GsApp *repo)
source_third_party_package = gs_app_get_source_id_default (dialog->third_party_repo);
/* group repos from the same repo-release package together */
- if (g_strcmp0 (source_repo, source_third_party_package) == 0)
- return GTK_LIST_BOX (dialog->listbox_third_party);
+ return g_strcmp0 (source_repo, source_third_party_package) == 0;
}
- return GTK_LIST_BOX (dialog->listbox);
+ return FALSE;
}
static void
-add_repo (GsReposDialog *dialog, GsApp *repo)
+add_repo (GsReposDialog *dialog,
+ GsApp *repo,
+ GSList **third_party_repos)
{
- GtkWidget *row;
- g_autofree gchar *text = NULL;
GsAppState state;
+ GtkWidget *section;
+ g_autofree gchar *origin_ui = NULL;
state = gs_app_get_state (repo);
if (!(state == GS_APP_STATE_AVAILABLE ||
@@ -401,122 +337,57 @@ add_repo (GsReposDialog *dialog, GsApp *repo)
return;
}
- row = gs_repo_row_new ();
- gs_repo_row_set_name (GS_REPO_ROW (row),
- gs_app_get_name (repo));
- text = get_repo_installed_text (repo);
- gs_repo_row_set_comment (GS_REPO_ROW (row), text);
- gs_repo_row_set_url (GS_REPO_ROW (row),
- gs_app_get_url (repo, AS_URL_KIND_HOMEPAGE));
- gs_repo_row_show_status (GS_REPO_ROW (row));
- gs_repo_row_set_repo (GS_REPO_ROW (row), repo);
-
- g_signal_connect (row, "button-clicked",
- G_CALLBACK (repo_button_clicked_cb), dialog);
-
- gtk_list_box_prepend (get_list_box_for_repo (dialog, repo), row);
- gtk_widget_show (row);
-}
-
-static void
-third_party_repo_installed_cb (GObject *source,
- GAsyncResult *res,
- gpointer user_data)
-{
- GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
- g_autoptr(InstallRemoveData) install_data = (InstallRemoveData *) user_data;
- g_autoptr(GError) error = NULL;
-
- if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
- const gchar *action_str = gs_plugin_action_to_string (install_data->action);
-
- if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
- g_debug ("third party repo %s cancelled", action_str);
- return;
- }
-
- g_warning ("failed to %s third party repo: %s", action_str, error->message);
+ if (third_party_repos && is_third_party_repo (dialog, repo)) {
+ *third_party_repos = g_slist_prepend (*third_party_repos, repo);
return;
}
- reload_sources (install_data->dialog);
-}
-
-static void
-install_third_party_repo (GsReposDialog *dialog, gboolean install)
-{
- InstallRemoveData *install_data;
- g_autoptr(GsPluginJob) plugin_job = NULL;
-
- install_data = g_slice_new0 (InstallRemoveData);
- install_data->dialog = g_object_ref (dialog);
- install_data->action = install ? GS_PLUGIN_ACTION_INSTALL : GS_PLUGIN_ACTION_REMOVE;
-
- plugin_job = gs_plugin_job_newv (install_data->action,
- "interactive", TRUE,
- "app", dialog->third_party_repo,
- NULL);
- gs_plugin_loader_job_process_async (dialog->plugin_loader,
- plugin_job,
- dialog->cancellable,
- third_party_repo_installed_cb,
- install_data);
-}
-
-static void
-third_party_repo_button_clicked_cb (GsThirdPartyRepoRow *row,
- gpointer user_data)
-{
- GsReposDialog *dialog = (GsReposDialog *) user_data;
- GsApp *app;
-
- app = gs_third_party_repo_row_get_app (row);
-
- switch (gs_app_get_state (app)) {
- case GS_APP_STATE_UNAVAILABLE:
- case GS_APP_STATE_AVAILABLE:
- case GS_APP_STATE_AVAILABLE_LOCAL:
- install_third_party_repo (dialog, TRUE);
- break;
- case GS_APP_STATE_UPDATABLE_LIVE:
- case GS_APP_STATE_UPDATABLE:
- case GS_APP_STATE_INSTALLED:
- install_third_party_repo (dialog, FALSE);
- break;
- default:
- g_warning ("third party repo %s button clicked in unexpected state %s",
- gs_app_get_id (app),
- gs_app_state_to_string (gs_app_get_state (app)));
- break;
+ origin_ui = gs_app_get_origin_ui (repo);
+ if (!origin_ui)
+ origin_ui = gs_app_get_packaging_format (repo);
+ if (!origin_ui)
+ origin_ui = g_strdup (gs_app_get_management_plugin (repo));
+ section = g_hash_table_lookup (dialog->sections, origin_ui);
+ if (section == NULL) {
+ section = gs_repos_section_new (dialog->plugin_loader, origin_ui);
+ g_object_set (G_OBJECT (section),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ NULL);
+ g_signal_connect_object (section, "remove-clicked",
+ G_CALLBACK (repo_section_remove_clicked_cb), dialog, 0);
+ g_signal_connect_object (section, "switch-clicked",
+ G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0);
+ g_hash_table_insert (dialog->sections, g_steal_pointer (&origin_ui), section);
+ gtk_widget_show (section);
}
- g_settings_set_boolean (dialog->settings, "show-nonfree-prompt", FALSE);
+ gs_repos_section_add_repo (GS_REPOS_SECTION (section), repo);
}
-static void
-refresh_third_party_repo (GsReposDialog *dialog)
+static gint
+repos_dialog_compare_sections_cb (gconstpointer aa,
+ gconstpointer bb)
{
- if (dialog->third_party_repo == NULL) {
- gtk_widget_hide (dialog->listbox_third_party);
- return;
- }
+ GsReposSection *section_a = (GsReposSection *) aa;
+ GsReposSection *section_b = (GsReposSection *) bb;
+ const gchar *section_sort_key_a;
+ const gchar *section_sort_key_b;
+ g_autofree gchar *title_sort_key_a = NULL;
+ g_autofree gchar *title_sort_key_b = NULL;
+ gint res;
- gtk_widget_show (dialog->listbox_third_party);
-}
+ section_sort_key_a = gs_repos_section_get_sort_key (section_a);
+ section_sort_key_b = gs_repos_section_get_sort_key (section_b);
-static void
-remove_all_repo_rows_cb (GtkWidget *widget, gpointer user_data)
-{
- GtkContainer *container = GTK_CONTAINER (user_data);
+ res = g_strcmp0 (section_sort_key_a, section_sort_key_b);
+ if (res != 0)
+ return res;
- if (GS_IS_REPO_ROW (widget))
- gtk_container_remove (container, widget);
-}
+ title_sort_key_a = gs_utils_sort_key (gs_repos_section_get_title (section_a));
+ title_sort_key_b = gs_utils_sort_key (gs_repos_section_get_title (section_b));
-static void
-container_remove_all_repo_rows (GtkContainer *container)
-{
- gtk_container_foreach (container, remove_all_repo_rows_cb, container);
+ return g_strcmp0 (title_sort_key_a, title_sort_key_b);
}
static void
@@ -527,6 +398,8 @@ get_sources_cb (GsPluginLoader *plugin_loader,
GsApp *app;
g_autoptr(GError) error = NULL;
g_autoptr(GsAppList) list = NULL;
+ g_autoptr(GSList) other_repos = NULL;
+ g_autoptr(GList) sections = NULL;
/* get the results */
list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
@@ -546,8 +419,8 @@ get_sources_cb (GsPluginLoader *plugin_loader,
}
/* remove previous */
- gs_container_remove_all (GTK_CONTAINER (dialog->listbox));
- container_remove_all_repo_rows (GTK_CONTAINER (dialog->listbox_third_party));
+ g_hash_table_remove_all (dialog->sections);
+ gs_container_remove_all (GTK_CONTAINER (dialog->content_box));
/* stop the spinner */
gs_stop_spinner (GTK_SPINNER (dialog->spinner));
@@ -567,11 +440,88 @@ get_sources_cb (GsPluginLoader *plugin_loader,
gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "sources");
for (guint i = 0; i < gs_app_list_length (list); i++) {
app = gs_app_list_index (list, i);
- add_repo (dialog, app);
+ add_repo (dialog, app, &other_repos);
}
- gtk_widget_set_visible (dialog->listbox,
- gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->listbox), 0) != NULL);
+ sections = g_hash_table_get_values (dialog->sections);
+ sections = g_list_sort (sections, repos_dialog_compare_sections_cb);
+ for (GList *link = sections; link; link = g_list_next (link)) {
+ GtkWidget *section = link->data;
+ gtk_container_add (GTK_CONTAINER (dialog->content_box), section);
+ }
+
+ gtk_widget_set_visible (dialog->content_box, sections != NULL);
+
+ if (other_repos) {
+ GsReposSection *section;
+ GtkWidget *label;
+ GtkStyleContext *style;
+ g_autofree gchar *anchor = NULL;
+ g_autofree gchar *hint = NULL;
+
+ section = GS_REPOS_SECTION (gs_repos_section_new (dialog->plugin_loader, _("Fedora Third
Party Repositories")));
+ g_object_set (G_OBJECT (section),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ NULL);
+ gs_repos_section_set_sort_key (section, "900");
+ g_signal_connect_object (section, "switch-clicked",
+ G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0);
+ gtk_widget_show (GTK_WIDGET (section));
+
+ anchor = g_strdup_printf ("<a href=\"%s\">%s</a>",
+
"https://fedoraproject.org/wiki/Workstation/Third_Party_Software_Repositories",
+ /* TRANSLATORS: this is the clickable
+ * link on the third party repositories info bar */
+ _("more information"));
+ hint = g_strdup_printf (
+ /* TRANSLATORS: this is the third party repositories info bar. The '%s' is
replaced
+ with a link consisting a text "more information", which constructs a
sentence:
+ "Additional repositories from selected third parties - more information."*/
+ _("Additional repositories from selected third parties — %s."),
+ anchor);
+
+ label = gtk_label_new ("");
+ g_object_set (G_OBJECT (label),
+ "halign", GTK_ALIGN_START,
+ "hexpand", TRUE,
+ "label", hint,
+ "use-markup", TRUE,
+ "visible", TRUE,
+ "wrap", TRUE,
+ "xalign", 0.0,
+ NULL);
+ style = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (style, "dim-label");
+
+ gtk_box_pack_start (GTK_BOX (section), label, FALSE, TRUE, 0);
+ gtk_box_reorder_child (GTK_BOX (section), label, 1);
+
+ for (GSList *link = other_repos; link; link = g_slist_next (link)) {
+ GsApp *repo = link->data;
+ gs_repos_section_add_repo (section, repo);
+ }
+
+ gtk_container_add (GTK_CONTAINER (dialog->content_box), GTK_WIDGET (section));
+ }
+}
+
+static void
+reload_sources (GsReposDialog *dialog)
+{
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* get the list of non-core software repositories */
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
+ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
+ "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ (GAsyncReadyCallback) get_sources_cb,
+ dialog);
}
static void
@@ -593,6 +543,7 @@ resolve_third_party_repo_cb (GsPluginLoader *plugin_loader,
return;
} else {
g_warning ("failed to resolve third party repo: %s", error->message);
+ reload_sources (dialog);
return;
}
}
@@ -604,27 +555,7 @@ resolve_third_party_repo_cb (GsPluginLoader *plugin_loader,
app = NULL;
g_set_object (&dialog->third_party_repo, app);
- gs_third_party_repo_row_set_app (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party), app);
-
- /* refresh widget */
- refresh_third_party_repo (dialog);
-}
-
-static void
-reload_sources (GsReposDialog *dialog)
-{
- g_autoptr(GsPluginJob) plugin_job = NULL;
-
- /* get the list of non-core software repositories */
- plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
- "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
- GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME,
- "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE,
- NULL);
- gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
- dialog->cancellable,
- (GAsyncReadyCallback) get_sources_cb,
- dialog);
+ reload_sources (dialog);
}
static gboolean
@@ -651,13 +582,16 @@ reload_third_party_repo (GsReposDialog *dialog)
g_autoptr(GsPluginJob) plugin_job = NULL;
/* Fedora-specific functionality */
- if (!is_fedora ())
+ if (!is_fedora ()) {
+ reload_sources (dialog);
return;
+ }
plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_PROVIDES,
"search", third_party_repo_package,
"refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
- GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
+ GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
NULL);
gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
dialog->cancellable,
@@ -665,64 +599,6 @@ reload_third_party_repo (GsReposDialog *dialog)
dialog);
}
-static gchar *
-get_row_sort_key (GtkListBoxRow *row)
-{
- GsApp *app;
- guint sort_order;
- g_autofree gchar *sort_key = NULL;
-
- /* sort third party repo rows first */
- if (GS_IS_THIRD_PARTY_REPO_ROW (row)) {
- sort_order = 1;
- app = gs_third_party_repo_row_get_app (GS_THIRD_PARTY_REPO_ROW (row));
- } else {
- sort_order = 2;
- app = gs_repo_row_get_repo (GS_REPO_ROW (row));
- }
-
- if (gs_app_get_name (app) != NULL) {
- sort_key = gs_utils_sort_key (gs_app_get_name (app));
- return g_strdup_printf ("%u:%s", sort_order, sort_key);
- } else {
- return g_strdup_printf ("%u:", sort_order);
- }
-}
-
-static gint
-list_sort_func (GtkListBoxRow *a,
- GtkListBoxRow *b,
- gpointer user_data)
-{
- g_autofree gchar *key1 = get_row_sort_key (a);
- g_autofree gchar *key2 = get_row_sort_key (b);
-
- /* compare the keys according to the algorithm above */
- return g_strcmp0 (key1, key2);
-}
-
-static void
-list_row_activated_cb (GtkListBox *list_box,
- GtkListBoxRow *row,
- GsReposDialog *dialog)
-{
- GtkListBoxRow *other_row;
-
- if (!GS_IS_REPO_ROW (row))
- return;
-
- gs_repo_row_show_details (GS_REPO_ROW (row));
-
- for (guint i = 0; (other_row = gtk_list_box_get_row_at_index (list_box, i)) != NULL; i++) {
- if (!GS_IS_REPO_ROW (other_row))
- continue;
- if (other_row == row)
- continue;
-
- gs_repo_row_hide_details (GS_REPO_ROW (other_row));
- }
-}
-
static gchar *
get_os_name (void)
{
@@ -744,7 +620,6 @@ get_os_name (void)
static void
reload_cb (GsPluginLoader *plugin_loader, GsReposDialog *dialog)
{
- reload_sources (dialog);
reload_third_party_repo (dialog);
}
@@ -767,6 +642,7 @@ gs_repos_dialog_dispose (GObject *object)
}
g_cancellable_cancel (dialog->cancellable);
+ g_clear_pointer (&dialog->sections, g_hash_table_unref);
g_clear_object (&dialog->cancellable);
g_clear_object (&dialog->settings);
g_clear_object (&dialog->third_party_repo);
@@ -777,7 +653,6 @@ gs_repos_dialog_dispose (GObject *object)
static void
gs_repos_dialog_init (GsReposDialog *dialog)
{
- g_autofree gchar *label_description_text = NULL;
g_autofree gchar *label_empty_text = NULL;
g_autofree gchar *os_name = NULL;
g_autoptr(GString) str = g_string_new (NULL);
@@ -786,47 +661,10 @@ gs_repos_dialog_init (GsReposDialog *dialog)
dialog->cancellable = g_cancellable_new ();
dialog->settings = g_settings_new ("org.gnome.software");
+ dialog->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
os_name = get_os_name ();
- gtk_list_box_set_sort_func (GTK_LIST_BOX (dialog->listbox),
- list_sort_func,
- dialog, NULL);
- g_signal_connect (dialog->listbox, "row-activated",
- G_CALLBACK (list_row_activated_cb), dialog);
-
- /* TRANSLATORS: This is the description text displayed in the Software Repositories dialog.
- %s gets replaced by the name of the actual distro, e.g. Fedora. */
- label_description_text = g_strdup_printf (_("These repositories supplement the default software
provided by %s."),
- os_name);
- gtk_label_set_text (GTK_LABEL (dialog->label_description), label_description_text);
-
- /* set up third party repository row */
- gtk_list_box_set_sort_func (GTK_LIST_BOX (dialog->listbox_third_party),
- list_sort_func,
- dialog, NULL);
- g_signal_connect (dialog->listbox_third_party, "row-activated",
- G_CALLBACK (list_row_activated_cb), dialog);
- g_signal_connect (dialog->row_third_party, "button-clicked",
- G_CALLBACK (third_party_repo_button_clicked_cb), dialog);
- gs_third_party_repo_row_set_name (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party),
- /* TRANSLATORS: info bar title in the software repositories dialog
*/
- _("Third Party Repositories"));
- g_string_append (str,
- /* TRANSLATORS: this is the third party repositories info bar. */
- _("Access additional software from selected third party sources."));
- g_string_append (str, " ");
- g_string_append (str,
- /* TRANSLATORS: this is the third party repositories info bar. */
- _("Some of this software is proprietary and therefore has restrictions on use,
sharing, and access to source code."));
- g_string_append_printf (str, " <a href=\"%s\">%s</a>",
-
"https://fedoraproject.org/wiki/Workstation/Third_Party_Software_Repositories",
- /* TRANSLATORS: this is the clickable
- * link on the third party repositories info bar */
- _("Find out more…"));
- gs_third_party_repo_row_set_comment (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party), str->str);
- refresh_third_party_repo (dialog);
-
/* TRANSLATORS: This is the description text displayed in the Software Repositories dialog.
%s gets replaced by the name of the actual distro, e.g. Fedora. */
label_empty_text = g_strdup_printf (_("These repositories supplement the default software provided by
%s."),
@@ -844,12 +682,9 @@ gs_repos_dialog_class_init (GsReposDialogClass *klass)
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-repos-dialog.ui");
- gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_description);
gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_empty);
gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_header);
- gtk_widget_class_bind_template_child (widget_class, GsReposDialog, listbox);
- gtk_widget_class_bind_template_child (widget_class, GsReposDialog, listbox_third_party);
- gtk_widget_class_bind_template_child (widget_class, GsReposDialog, row_third_party);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, content_box);
gtk_widget_class_bind_template_child (widget_class, GsReposDialog, spinner);
gtk_widget_class_bind_template_child (widget_class, GsReposDialog, stack);
}
@@ -867,7 +702,6 @@ gs_repos_dialog_new (GtkWindow *parent, GsPluginLoader *plugin_loader)
set_plugin_loader (dialog, plugin_loader);
gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "waiting");
gs_start_spinner (GTK_SPINNER (dialog->spinner));
- reload_sources (dialog);
reload_third_party_repo (dialog);
return GTK_WIDGET (dialog);
diff --git a/src/gs-repos-dialog.ui b/src/gs-repos-dialog.ui
index 246fa477c..b3b34efde 100644
--- a/src/gs-repos-dialog.ui
+++ b/src/gs-repos-dialog.ui
@@ -99,57 +99,14 @@
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">none</property>
<child>
- <object class="GtkBox" id="box1">
+ <object class="GtkBox" id="content_box">
<property name="visible">True</property>
<property name="margin_start">52</property>
<property name="margin_end">52</property>
- <property name="margin_top">24</property>
+ <property name="margin_top">32</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
- <property name="spacing">4</property>
- <child>
- <object class="GtkLabel" id="label_description">
- <property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="wrap">True</property>
- <property name="max_width_chars">30</property>
- <property name="margin_bottom">16</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkListBox" id="listbox_third_party">
- <property name="visible">True</property>
- <property name="selection_mode">none</property>
- <property name="halign">fill</property>
- <property name="valign">start</property>
- <property name="margin_bottom">16</property>
- <style>
- <class name="content" />
- </style>
- <child>
- <object class="GsThirdPartyRepoRow" id="row_third_party">
- <property name="visible">True</property>
- <property name="activatable">False</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkListBox" id="listbox">
- <property name="halign">fill</property>
- <property name="valign">start</property>
- <property name="vexpand">True</property>
- <property name="margin_top">9</property>
- <property name="visible">True</property>
- <property name="selection_mode">none</property>
- <style>
- <class name="content" />
- </style>
- </object>
- </child>
+ <property name="spacing">20</property>
</object>
</child>
</object>
diff --git a/src/gs-repos-section.c b/src/gs-repos-section.c
new file mode 100644
index 000000000..00c6ed13d
--- /dev/null
+++ b/src/gs-repos-section.c
@@ -0,0 +1,213 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Red Hat <www.redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gs-repo-row.h"
+#include "gs-repos-section.h"
+
+struct _GsReposSection
+{
+ GtkBox parent_instance;
+ GtkWidget *title;
+ GtkListBox *list;
+ GsPluginLoader *plugin_loader;
+ gchar *sort_key;
+};
+
+G_DEFINE_TYPE (GsReposSection, gs_repos_section, GTK_TYPE_BOX)
+
+enum {
+ SIGNAL_REMOVE_CLICKED,
+ SIGNAL_SWITCH_CLICKED,
+ SIGNAL_LAST
+};
+
+static guint signals [SIGNAL_LAST] = { 0 };
+
+static void
+repo_remove_clicked_cb (GsRepoRow *row,
+ GsReposSection *section)
+{
+ g_signal_emit (section, signals[SIGNAL_REMOVE_CLICKED], 0, row);
+}
+
+static void
+repo_switch_clicked_cb (GsRepoRow *row,
+ GsReposSection *section)
+{
+ g_signal_emit (section, signals[SIGNAL_SWITCH_CLICKED], 0, row);
+}
+
+static gchar *
+_get_app_sort_key (GsApp *app)
+{
+ if (gs_app_get_name (app) != NULL)
+ return gs_utils_sort_key (gs_app_get_name (app));
+
+ return NULL;
+}
+
+static gint
+_list_sort_func (GtkListBoxRow *a, GtkListBoxRow *b, gpointer user_data)
+{
+ GsApp *a1 = gs_repo_row_get_repo (GS_REPO_ROW (a));
+ GsApp *a2 = gs_repo_row_get_repo (GS_REPO_ROW (b));
+ g_autofree gchar *key1 = _get_app_sort_key (a1);
+ g_autofree gchar *key2 = _get_app_sort_key (a2);
+
+ return g_strcmp0 (key1, key2);
+}
+
+static void
+gs_repos_section_finalize (GObject *object)
+{
+ GsReposSection *self = GS_REPOS_SECTION (object);
+
+ g_clear_object (&self->plugin_loader);
+ g_free (self->sort_key);
+
+ G_OBJECT_CLASS (gs_repos_section_parent_class)->finalize (object);
+}
+
+static void
+gs_repos_section_class_init (GsReposSectionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gs_repos_section_finalize;
+
+ signals [SIGNAL_REMOVE_CLICKED] =
+ g_signal_new ("remove-clicked",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GS_TYPE_REPO_ROW);
+
+ signals [SIGNAL_SWITCH_CLICKED] =
+ g_signal_new ("switch-clicked",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GS_TYPE_REPO_ROW);
+
+ gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "repos-section");
+}
+
+static void
+gs_repos_section_init (GsReposSection *self)
+{
+ PangoAttrList *attrs;
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+
+ self->title = gtk_label_new ("");
+ g_object_set (G_OBJECT (self->title),
+ "visible", TRUE,
+ "halign", GTK_ALIGN_START,
+ "valign", GTK_ALIGN_CENTER,
+ "xalign", 0.0,
+ "yalign", 0.5,
+ "attributes", attrs,
+ NULL);
+
+ pango_attr_list_unref (attrs);
+
+ gtk_box_pack_start (GTK_BOX (self), self->title, FALSE, FALSE, 0);
+
+ self->list = GTK_LIST_BOX (gtk_list_box_new ());
+ g_object_set (G_OBJECT (self->list),
+ "visible", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_CENTER,
+ "vexpand", TRUE,
+ "margin-top", 4,
+ "margin-bottom", 4,
+ "selection-mode", GTK_SELECTION_NONE,
+ NULL);
+ gtk_list_box_set_sort_func (self->list, _list_sort_func, self, NULL);
+
+ gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (self->list), TRUE, TRUE, 0);
+}
+
+GtkWidget *
+gs_repos_section_new (GsPluginLoader *plugin_loader,
+ const gchar *title)
+{
+ GsReposSection *self;
+
+ g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+
+ self = g_object_new (GS_TYPE_REPOS_SECTION,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 8,
+ NULL);
+
+ gtk_label_set_text (GTK_LABEL (self->title), title);
+ self->plugin_loader = g_object_ref (plugin_loader);
+
+ return GTK_WIDGET (self);
+}
+
+void
+gs_repos_section_add_repo (GsReposSection *self,
+ GsApp *repo)
+{
+ GtkWidget *row;
+
+ g_return_if_fail (GS_IS_REPOS_SECTION (self));
+ g_return_if_fail (GS_IS_APP (repo));
+
+ /* Derive the sort key from the repository. All repositories of the same kind
+ should have set the same sort key. It's because there's no other way to provide
+ the section sort key by the plugin without breaking the abstraction. */
+ if (!self->sort_key)
+ self->sort_key = g_strdup (gs_app_get_metadata_item (repo, "GnomeSoftware::SortKey"));
+
+ row = gs_repo_row_new (self->plugin_loader, repo);
+
+ g_signal_connect (row, "remove-clicked",
+ G_CALLBACK (repo_remove_clicked_cb), self);
+ g_signal_connect (row, "switch-clicked",
+ G_CALLBACK (repo_switch_clicked_cb), self);
+
+ gtk_list_box_prepend (self->list, row);
+ gtk_widget_show (row);
+}
+
+const gchar *
+gs_repos_section_get_title (GsReposSection *self)
+{
+ g_return_val_if_fail (GS_IS_REPOS_SECTION (self), NULL);
+
+ return gtk_label_get_text (GTK_LABEL (self->title));
+}
+
+const gchar *
+gs_repos_section_get_sort_key (GsReposSection *self)
+{
+ g_return_val_if_fail (GS_IS_REPOS_SECTION (self), NULL);
+
+ return self->sort_key;
+}
+
+void
+gs_repos_section_set_sort_key (GsReposSection *self,
+ const gchar *sort_key)
+{
+ g_return_if_fail (GS_IS_REPOS_SECTION (self));
+
+ if (g_strcmp0 (sort_key, self->sort_key) != 0) {
+ g_free (self->sort_key);
+ self->sort_key = g_strdup (sort_key);
+ }
+}
diff --git a/src/gs-repos-section.h b/src/gs-repos-section.h
new file mode 100644
index 000000000..6a8c43004
--- /dev/null
+++ b/src/gs-repos-section.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Red Hat <www.redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "gnome-software-private.h"
+#include "gs-app.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_REPOS_SECTION (gs_repos_section_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsReposSection, gs_repos_section, GS, REPOS_SECTION, GtkBox)
+
+GtkWidget *gs_repos_section_new (GsPluginLoader *plugin_loader,
+ const gchar *title);
+void gs_repos_section_add_repo (GsReposSection *self,
+ GsApp *repo);
+const gchar *gs_repos_section_get_title (GsReposSection *self);
+const gchar *gs_repos_section_get_sort_key (GsReposSection *self);
+void gs_repos_section_set_sort_key (GsReposSection *self,
+ const gchar *sort_key);
+
+G_END_DECLS
diff --git a/src/gtk-style.css b/src/gtk-style.css
index fb74c8234..6dc7e30ec 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -43,6 +43,33 @@
font-size: x-small;
}
+repos-section list {
+ border-radius: 8px;
+ border: 1px solid darker(@theme_bg_color);
+ padding-bottom: 0px;
+ padding-top: 0px;
+}
+
+repos-section list reporow:not(:last-child) {
+ border-bottom: 1px solid darker(@theme_bg_color);
+}
+
+repos-section list reporow {
+ border-radius: 0;
+}
+
+repos-section list reporow:first-child {
+ border-radius: 8px 8px 0 0;
+}
+
+repos-section list reporow:last-child {
+ border-radius: 0 0 8px 8px;
+}
+
+repos-section list reporow:only-child {
+ border-radius: 8px;
+}
+
/* Adapted from Adwaita’s .needs-attention class for stacksidebar */
sidebar row.needs-attention > box > label {
animation: needs_attention 150ms ease-in;
@@ -340,6 +367,7 @@ review-bar {
.app-updates-section {
border-radius: 4px;
border: 1px solid darker(@theme_bg_color);
+ -gtk-outline-radius: 50%;
}
.app-listbox-header-title {
diff --git a/src/meson.build b/src/meson.build
index 454215103..dc99fb4ac 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -51,6 +51,7 @@ gnome_software_sources = [
'gs-progress-button.c',
'gs-removal-dialog.c',
'gs-repos-dialog.c',
+ 'gs-repos-section.c',
'gs-repo-row.c',
'gs-review-bar.c',
'gs-review-dialog.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]