Convert credentials page to use HdyPreferencesGroup and a list box.

Wed, 12 May 2021 05:36:35 -0500

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Wed, 12 May 2021 05:36:35 -0500
changeset 40873
68c7ef6c5320
parent 40872
648527ab9ae3
child 40874
0cf92aa817fd

Convert credentials page to use HdyPreferencesGroup and a list box.

* Convert credentials page to use `HdyPreferencesGroup` and a list box.
* Add widget for displaying a credential provider in a list box.
* Convert `PidginCredentialsPage` into `HdyPreferencesPage`.

Testing Done:
Opened Preferences, switch to Credentials page, and see/toggle selected provider.

Reviewed at https://reviews.imfreedom.org/r/644/

doc/reference/pidgin/pidgin-docs.xml file | annotate | diff | comparison | revisions
meson.build file | annotate | diff | comparison | revisions
pidgin/meson.build file | annotate | diff | comparison | revisions
pidgin/pidginapplication.c file | annotate | diff | comparison | revisions
pidgin/pidgincredentialproviderrow.c file | annotate | diff | comparison | revisions
pidgin/pidgincredentialproviderrow.h file | annotate | diff | comparison | revisions
pidgin/pidgincredentialspage.c file | annotate | diff | comparison | revisions
pidgin/pidgincredentialspage.h file | annotate | diff | comparison | revisions
pidgin/resources/Prefs/credentialprovider.ui file | annotate | diff | comparison | revisions
pidgin/resources/Prefs/credentials.ui file | annotate | diff | comparison | revisions
pidgin/resources/Prefs/prefs.ui file | annotate | diff | comparison | revisions
pidgin/resources/pidgin.gresource.xml file | annotate | diff | comparison | revisions
po/POTFILES.in file | annotate | diff | comparison | revisions
--- a/doc/reference/pidgin/pidgin-docs.xml	Thu May 06 22:36:35 2021 -0500
+++ b/doc/reference/pidgin/pidgin-docs.xml	Wed May 12 05:36:35 2021 -0500
@@ -64,6 +64,7 @@
       <xi:include href="xml/pidgincontactlist.xml" />
       <xi:include href="xml/pidginconversationwindow.xml" />
       <xi:include href="xml/pidgincore.xml" />
+      <xi:include href="xml/pidgincredentialproviderrow.xml" />
       <xi:include href="xml/pidgincredentialproviderstore.xml" />
       <xi:include href="xml/pidgincredentialspage.xml" />
       <xi:include href="xml/pidgindebug.xml" />
--- a/meson.build	Thu May 06 22:36:35 2021 -0500
+++ b/meson.build	Wed May 12 05:36:35 2021 -0500
@@ -257,6 +257,7 @@
 #######################################################################
 if get_option('gtkui')
 	gtk = dependency('gtk+-3.0', version : '>= 3.22.0')
+	libhandy = dependency('libhandy-1', version : '>= 1')
 
 	talkatu_dep = dependency('talkatu', version: '>=0.1.0', required : false)
 	if talkatu_dep.found()
--- a/pidgin/meson.build	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/meson.build	Wed May 12 05:36:35 2021 -0500
@@ -44,6 +44,7 @@
 	'pidgincontactcompletion.c',
 	'pidginconversationwindow.c',
 	'pidgincontactlist.c',
+	'pidgincredentialproviderrow.c',
 	'pidgincredentialproviderstore.c',
 	'pidgincredentialspage.c',
 	'pidgindebug.c',
@@ -114,6 +115,7 @@
 	'pidginconversationwindow.h',
 	'pidgincontactlist.h',
 	'pidgincore.h',
+	'pidgincredentialproviderrow.h',
 	'pidgincredentialproviderstore.h',
 	'pidgincredentialspage.h',
 	'pidgindialog.h',
@@ -216,6 +218,7 @@
 		gtk,
 		IOKIT,
 		json,
+		libhandy,
 		math,
 		nice,
 		libsoup,
@@ -248,7 +251,7 @@
 	    include_directories : [toplevel_inc, libpidgin_inc],
 	    link_with : libpidgin,
 	    sources : libpidgin_built_headers,
-	    dependencies : [gtk, glib, math, talkatu_dep, gplugin_gtk_dep])
+	    dependencies : [gtk, glib, libhandy, math, talkatu_dep, gplugin_gtk_dep])
 
 	pidgin = executable('pidgin3',
 	    pidgin_SOURCES,
--- a/pidgin/pidginapplication.c	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/pidginapplication.c	Wed May 12 05:36:35 2021 -0500
@@ -31,6 +31,8 @@
 #include <gplugin.h>
 #include <purple.h>
 
+#include <handy.h>
+
 #include "pidginapplication.h"
 
 #include "gtkaccount.h"
@@ -492,6 +494,7 @@
 	gpointer handle = NULL;
 
 	G_APPLICATION_CLASS(pidgin_application_parent_class)->startup(application);
+	hdy_init();
 
 	/* set a user-specified config directory */
 	if (opt_config_dir_arg != NULL) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgincredentialproviderrow.c	Wed May 12 05:36:35 2021 -0500
@@ -0,0 +1,224 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <purple.h>
+
+#include <handy.h>
+
+#include "pidgincredentialproviderrow.h"
+
+struct _PidginCredentialProviderRow {
+	HdyActionRow parent;
+
+	PurpleCredentialProvider *provider;
+
+	GtkWidget *active;
+	GtkWidget *configure;
+};
+
+enum {
+	PROP_0,
+	PROP_PROVIDER,
+	PROP_ACTIVE,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+G_DEFINE_TYPE(PidginCredentialProviderRow, pidgin_credential_provider_row,
+              HDY_TYPE_ACTION_ROW)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_credential_provider_row_set_provider(PidginCredentialProviderRow *row,
+                                            PurpleCredentialProvider *provider)
+{
+	if(!g_set_object(&row->provider, provider)) {
+		return;
+	}
+
+	if(PURPLE_IS_CREDENTIAL_PROVIDER(provider)) {
+		hdy_preferences_row_set_title(
+		    HDY_PREFERENCES_ROW(row),
+		    purple_credential_provider_get_name(provider));
+		hdy_action_row_set_subtitle(
+		    HDY_ACTION_ROW(row),
+		    purple_credential_provider_get_description(provider));
+		/* Not implemented yet, so always hide the configure button. */
+		gtk_widget_set_visible(row->configure, FALSE);
+	}
+
+	/* Notify that we changed. */
+	g_object_notify_by_pspec(G_OBJECT(row), properties[PROP_PROVIDER]);
+}
+
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_credential_provider_row_get_property(GObject *obj, guint param_id,
+                                            GValue *value, GParamSpec *pspec)
+{
+	PidginCredentialProviderRow *row = PIDGIN_CREDENTIAL_PROVIDER_ROW(obj);
+
+	switch(param_id) {
+		case PROP_PROVIDER:
+			g_value_set_object(value,
+			                   pidgin_credential_provider_row_get_provider(row));
+			break;
+		case PROP_ACTIVE:
+			g_value_set_boolean(value,
+			                    pidgin_credential_provider_row_get_active(row));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_credential_provider_row_set_property(GObject *obj, guint param_id,
+                                            const GValue *value,
+                                            GParamSpec *pspec)
+{
+	PidginCredentialProviderRow *row = PIDGIN_CREDENTIAL_PROVIDER_ROW(obj);
+
+	switch(param_id) {
+		case PROP_PROVIDER:
+			pidgin_credential_provider_row_set_provider(row,
+			                                            g_value_get_object(value));
+			break;
+		case PROP_ACTIVE:
+			pidgin_credential_provider_row_set_active(row,
+			                                          g_value_get_boolean(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_credential_provider_row_finalize(GObject *obj)
+{
+	PidginCredentialProviderRow *row = PIDGIN_CREDENTIAL_PROVIDER_ROW(obj);
+
+	g_clear_object(&row->provider);
+}
+
+static void
+pidgin_credential_provider_row_init(PidginCredentialProviderRow *row)
+{
+	gtk_widget_init_template(GTK_WIDGET(row));
+
+	/* If this row is active, then enable the provider properties button (which
+	 * may or may not be visible). */
+	g_object_bind_property(G_OBJECT(row), "active",
+	                       G_OBJECT(row->configure), "sensitive",
+	                       G_BINDING_DEFAULT);
+}
+
+static void
+pidgin_credential_provider_row_class_init(PidginCredentialProviderRowClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->get_property = pidgin_credential_provider_row_get_property;
+	obj_class->set_property = pidgin_credential_provider_row_set_property;
+	obj_class->finalize = pidgin_credential_provider_row_finalize;
+
+	/**
+	 * PidginCredentialProviderRow::provider
+	 *
+	 * The #PurpleCredentialProvider whose information will be displayed.
+	 */
+	properties[PROP_PROVIDER] = g_param_spec_object(
+		"provider", "provider",
+		"The PurpleCredentialProvider instance",
+		PURPLE_TYPE_CREDENTIAL_PROVIDER,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginCredentialProviderRow::active
+	 *
+	 * Whether the #PurpleCredentialProvider is currently active.
+	 */
+	properties[PROP_ACTIVE] = g_param_spec_boolean(
+		"active", "active",
+		"Whether the PurpleCredentialProvider is active",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+	gtk_widget_class_set_template_from_resource(
+	    widget_class,
+	    "/im/pidgin/Pidgin/Prefs/credentialprovider.ui"
+	);
+
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginCredentialProviderRow,
+	                                     active);
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginCredentialProviderRow,
+	                                     configure);
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+GtkWidget *
+pidgin_credential_provider_row_new(PurpleCredentialProvider *provider) {
+	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), NULL);
+
+	return GTK_WIDGET(g_object_new(PIDGIN_TYPE_CREDENTIAL_PROVIDER_ROW,
+	                               "provider", provider,
+	                               NULL));
+}
+
+PurpleCredentialProvider *
+pidgin_credential_provider_row_get_provider(PidginCredentialProviderRow *row) {
+	g_return_val_if_fail(PIDGIN_IS_CREDENTIAL_PROVIDER_ROW(row), NULL);
+
+	return row->provider;
+}
+
+gboolean
+pidgin_credential_provider_row_get_active(PidginCredentialProviderRow *row) {
+	g_return_val_if_fail(PIDGIN_IS_CREDENTIAL_PROVIDER_ROW(row), FALSE);
+
+	return gtk_widget_get_visible(row->active);
+}
+
+void
+pidgin_credential_provider_row_set_active(PidginCredentialProviderRow *row,
+                                          gboolean active)
+{
+	g_return_if_fail(PIDGIN_IS_CREDENTIAL_PROVIDER_ROW(row));
+
+	gtk_widget_set_visible(row->active, active);
+
+	g_object_notify_by_pspec(G_OBJECT(row), properties[PROP_ACTIVE]);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgincredentialproviderrow.h	Wed May 12 05:36:35 2021 -0500
@@ -0,0 +1,94 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_CREDENTIAL_PROVIDER_ROW_H
+#define PIDGIN_CREDENTIAL_PROVIDER_ROW_H
+
+/**
+ * SECTION:pidgincredentialproviderrow
+ * @section_id: pidgin-pidgincredentialproviderrow
+ * @short_description: The preferences widget for a credential provider.
+ * @title: Credential provider widget
+ *
+ * #PidginCredentialProviderRow is a widget for the preferences window to let
+ * users configure their credential provider.
+ */
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <handy.h>
+
+G_BEGIN_DECLS
+
+#define PIDGIN_TYPE_CREDENTIAL_PROVIDER_ROW (pidgin_credential_provider_row_get_type())
+G_DECLARE_FINAL_TYPE(PidginCredentialProviderRow,
+                     pidgin_credential_provider_row,
+                     PIDGIN, CREDENTIAL_PROVIDER_ROW, HdyActionRow)
+
+/**
+ * pidgin_credential_provider_row_new:
+ * @provider: The credential provider to bind.
+ *
+ * Creates a new #PidginCredentialProviderRow instance.
+ *
+ * Returns: (transfer full): The new #PidginCredentialProviderRow instance.
+ */
+GtkWidget *pidgin_credential_provider_row_new(PurpleCredentialProvider *provider);
+
+/**
+ * pidgin_credential_provider_row_get_provider:
+ * @row: The row instance.
+ *
+ * Gets the #PurpleCredentialProvider displayed by this widget.
+ *
+ * Returns: (transfer none): The displayed #PurpleCredentialProvider.
+ */
+PurpleCredentialProvider *pidgin_credential_provider_row_get_provider(PidginCredentialProviderRow *row);
+
+/**
+ * pidgin_credential_provider_row_get_active:
+ * @row: The row instance.
+ *
+ * Gets whether the row is displayed as active.
+ *
+ * Returns: Whether the row is active.
+ */
+gboolean pidgin_credential_provider_row_get_active(PidginCredentialProviderRow *row);
+
+/**
+ * pidgin_credential_provider_row_set_active:
+ * @row: The row instance.
+ * @active: Whether to display as active.
+ *
+ * Sets whether the row is displayed as active.
+ */
+void pidgin_credential_provider_row_set_active(PidginCredentialProviderRow *row, gboolean active);
+
+G_END_DECLS
+
+#endif /* PIDGIN_CREDENTIAL_PROVIDER_ROW_H */
--- a/pidgin/pidgincredentialspage.c	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/pidgincredentialspage.c	Wed May 12 05:36:35 2021 -0500
@@ -22,95 +22,114 @@
 
 #include <purple.h>
 
+#include <handy.h>
+
 #include "pidgincredentialspage.h"
 
-#include "pidgincredentialproviderstore.h"
+#include "pidgincredentialproviderrow.h"
 
 struct _PidginCredentialsPage {
-	GtkBox parent;
+	HdyPreferencesPage parent;
 
-	GtkWidget *combo;
-	GtkCellRenderer *renderer;
+	GtkWidget *credential_list;
 };
 
 G_DEFINE_TYPE(PidginCredentialsPage, pidgin_credentials_page,
-              GTK_TYPE_BOX)
+              HDY_TYPE_PREFERENCES_PAGE)
 
 /******************************************************************************
  * Helpers
  *****************************************************************************/
 static void
-pidgin_credentials_page_combo_changed_cb(GtkComboBox *widget, gpointer data) {
-	PidginCredentialsPage *page = PIDGIN_CREDENTIALS_PAGE(data);
-	GtkTreeIter iter;
+pidgin_credentials_page_create_row(PurpleCredentialProvider *provider,
+                                   gpointer data)
+{
+	GtkListBox *box = GTK_LIST_BOX(data);
+	GtkWidget *row = NULL;
+
+	row = pidgin_credential_provider_row_new(provider);
+	gtk_list_box_prepend(box, row);
+}
 
-	if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(page->combo), &iter)) {
-		PurpleCredentialManager *manager = NULL;
-		GError *error = NULL;
-		GtkTreeModel *model = NULL;
-		gchar *id = NULL;
+static gint
+pidgin_credentials_page_sort_rows(GtkListBoxRow *row1, GtkListBoxRow *row2,
+                                  G_GNUC_UNUSED gpointer user_data)
+{
+	PidginCredentialProviderRow *pcprow = NULL;
+	PurpleCredentialProvider *provider = NULL;
+	const gchar *id1 = NULL;
+	gboolean is_noop1 = FALSE;
+	const gchar *id2 = NULL;
+	gboolean is_noop2 = FALSE;
 
-		model = gtk_combo_box_get_model(GTK_COMBO_BOX(page->combo));
+	pcprow = PIDGIN_CREDENTIAL_PROVIDER_ROW(row1);
+	provider = pidgin_credential_provider_row_get_provider(pcprow);
+	id1 = purple_credential_provider_get_id(provider);
+	is_noop1 = purple_strequal(id1, "noop-provider");
 
-		gtk_tree_model_get(model, &iter,
-		                   PIDGIN_CREDENTIAL_PROVIDER_STORE_COLUMN_ID, &id,
-		                   -1);
+	pcprow = PIDGIN_CREDENTIAL_PROVIDER_ROW(row2);
+	provider = pidgin_credential_provider_row_get_provider(pcprow);
+	id2 = purple_credential_provider_get_id(provider);
+	is_noop2 = purple_strequal(id2, "noop-provider");
 
-		manager = purple_credential_manager_get_default();
-		if(purple_credential_manager_set_active_provider(manager, id, &error)) {
-			purple_prefs_set_string("/purple/credentials/active-provider", id);
-
-			g_free(id);
-
-			return;
-		}
+	/* Sort None provider after everything else. */
+	if (is_noop1 && is_noop2) {
+		return 0;
+	} else if (is_noop1 && !is_noop2) {
+		return 1;
+	} else if (!is_noop1 && is_noop2) {
+		return -1;
+	}
+	/* Sort normally by ID. */
+	return g_strcmp0(id1, id2);
+}
 
-		purple_debug_warning("credentials-page", "failed to set the active "
-		                     "credential provider to '%s': %s",
-		                     id,
-		                     error ? error->message : "unknown error");
+static void
+pidgin_credential_page_list_row_activated_cb(GtkListBox *box,
+                                             GtkListBoxRow *row,
+                                             G_GNUC_UNUSED gpointer data)
+{
+	PurpleCredentialManager *manager = NULL;
+	PurpleCredentialProvider *provider = NULL;
+	const gchar *id = NULL;
+	GError *error = NULL;
 
-		g_free(id);
-		g_clear_error(&error);
+	provider = pidgin_credential_provider_row_get_provider(
+	    PIDGIN_CREDENTIAL_PROVIDER_ROW(row));
+	id = purple_credential_provider_get_id(provider);
+
+	manager = purple_credential_manager_get_default();
+	if(purple_credential_manager_set_active_provider(manager, id, &error)) {
+		purple_prefs_set_string("/purple/credentials/active-provider", id);
+
+		return;
 	}
+
+	purple_debug_warning("credentials-page", "failed to set the active "
+			     "credential provider to '%s': %s",
+			     id, error ? error->message : "unknown error");
+
+	g_clear_error(&error);
 }
 
 static void
 pidgin_credentials_page_set_active_provider(PidginCredentialsPage *page,
                                             const gchar *new_id)
 {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
-
-	model = gtk_combo_box_get_model(GTK_COMBO_BOX(page->combo));
-
-	if(gtk_tree_model_get_iter_first(model, &iter)) {
-		do {
-			gchar *id = NULL;
-
-			gtk_tree_model_get(model, &iter,
-			                   PIDGIN_CREDENTIAL_PROVIDER_STORE_COLUMN_ID, &id,
-			                   -1);
+	GList *rows = NULL;
 
-			if(purple_strequal(new_id, id)) {
-				g_signal_handlers_block_by_func(page->combo,
-				                                pidgin_credentials_page_combo_changed_cb,
-				                                page);
-
-				gtk_combo_box_set_active_iter(GTK_COMBO_BOX(page->combo),
-				                              &iter);
+	rows = gtk_container_get_children(GTK_CONTAINER(page->credential_list));
+	for (; rows; rows = g_list_delete_link(rows, rows)) {
+		PidginCredentialProviderRow *row = NULL;
+		PurpleCredentialProvider *provider = NULL;
+		const gchar *id = NULL;
 
-				g_signal_handlers_unblock_by_func(page->combo,
-				                                  pidgin_credentials_page_combo_changed_cb,
-				                                  page);
-
-				g_free(id);
+		row = PIDGIN_CREDENTIAL_PROVIDER_ROW(rows->data);
+		provider = pidgin_credential_provider_row_get_provider(row);
+		id = purple_credential_provider_get_id(provider);
 
-				return;
-			}
-
-			g_free(id);
-		} while(gtk_tree_model_iter_next(model, &iter));
+		pidgin_credential_provider_row_set_active(row,
+		                                          purple_strequal(new_id, id));
 	}
 }
 
@@ -126,7 +145,7 @@
 }
 
 /******************************************************************************
- * GObjectImplementation
+ * GObject Implementation
  *****************************************************************************/
 static void
 pidgin_credentials_page_finalize(GObject *obj) {
@@ -137,30 +156,26 @@
 
 static void
 pidgin_credentials_page_init(PidginCredentialsPage *page) {
+	PurpleCredentialManager *manager = NULL;
 	const gchar *active = NULL;
 
 	gtk_widget_init_template(GTK_WIDGET(page));
 
-	/* Set some constant properties on the renderer. This stuff is kind of
-	 * dodgy, but it does stop the dialog from growing to fit a long
-	 * description.
-	 */
-	g_object_set(G_OBJECT(page->renderer),
-	             "width-chars", 40,
-	             "wrap-mode", PANGO_WRAP_WORD_CHAR,
-	             NULL);
-
 	purple_prefs_add_none("/purple/credentials");
 	purple_prefs_add_string("/purple/credentials/active-provider", NULL);
 
+	manager = purple_credential_manager_get_default();
+	purple_credential_manager_foreach_provider(
+	    manager,
+	    pidgin_credentials_page_create_row,
+	    page->credential_list);
+	gtk_list_box_set_sort_func(GTK_LIST_BOX(page->credential_list),
+	                           pidgin_credentials_page_sort_rows, NULL, NULL);
+
 	purple_prefs_connect_callback(page, "/purple/credentials/active-provider",
 	                              pidgin_credentials_page_active_provider_changed_cb,
 	                              page);
 
-	g_signal_connect(G_OBJECT(page->combo), "changed",
-	                 G_CALLBACK(pidgin_credentials_page_combo_changed_cb),
-	                 page);
-
 	active = purple_prefs_get_string("/purple/credentials/active-provider");
 	if(active != NULL) {
 		pidgin_credentials_page_set_active_provider(page, active);
@@ -180,9 +195,9 @@
 	);
 
 	gtk_widget_class_bind_template_child(widget_class, PidginCredentialsPage,
-	                                     combo);
-	gtk_widget_class_bind_template_child(widget_class, PidginCredentialsPage,
-	                                     renderer);
+	                                     credential_list);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_credential_page_list_row_activated_cb);
 }
 
 /******************************************************************************
--- a/pidgin/pidgincredentialspage.h	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/pidgincredentialspage.h	Wed May 12 05:36:35 2021 -0500
@@ -40,12 +40,13 @@
 #include <glib.h>
 
 #include <gtk/gtk.h>
+#include <handy.h>
 
 G_BEGIN_DECLS
 
 #define PIDGIN_TYPE_CREDENTIALS_PAGE (pidgin_credentials_page_get_type())
 G_DECLARE_FINAL_TYPE(PidginCredentialsPage, pidgin_credentials_page,
-                     PIDGIN, CREDENTIALS_PAGE, GtkBox)
+                     PIDGIN, CREDENTIALS_PAGE, HdyPreferencesPage)
 
 /**
  * pidgin_credentials_page_new:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/resources/Prefs/credentialprovider.ui	Wed May 12 05:36:35 2021 -0500
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 
+
+Pidgin - Internet Messenger
+Copyright (C) Pidgin Developers <devel@pidgin.im>
+
+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.
+
+-->
+<interface>
+  <requires lib="gtk+" version="3.22"/>
+  <requires lib="libhandy" version="0.0"/>
+  <!-- interface-license-type gplv2 -->
+  <!-- interface-name Pidgin -->
+  <!-- interface-description Internet Messenger -->
+  <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+  <object class="GtkImage" id="image1">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="icon-name">document-properties-symbolic</property>
+  </object>
+  <template class="PidginCredentialProviderRow" parent="HdyActionRow">
+    <property name="visible">True</property>
+    <property name="can-focus">True</property>
+    <property name="activatable">True</property>
+    <child>
+      <object class="GtkButton" id="configure">
+        <property name="sensitive">False</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">True</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="image">image1</property>
+        <style>
+          <class name="circular"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkImage" id="active">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="valign">center</property>
+        <property name="xpad">6</property>
+        <property name="ypad">6</property>
+        <property name="icon-name">emblem-default-symbolic</property>
+      </object>
+    </child>
+  </template>
+</interface>
--- a/pidgin/resources/Prefs/credentials.ui	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/resources/Prefs/credentials.ui	Wed May 12 05:36:35 2021 -0500
@@ -21,80 +21,33 @@
 -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
-  <requires lib="pidgin" version="3.0"/>
+  <requires lib="libhandy" version="0.0"/>
   <!-- interface-license-type gplv2 -->
   <!-- interface-name Pidgin -->
   <!-- interface-description Internet Messenger -->
   <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
-  <object class="PidginCredentialProviderStore" id="store"/>
-  <template class="PidginCredentialsPage" parent="GtkBox">
+  <template class="PidginCredentialsPage" parent="HdyPreferencesPage">
     <property name="visible">True</property>
     <property name="can-focus">False</property>
-    <property name="border-width">12</property>
-    <property name="orientation">vertical</property>
-    <property name="spacing">18</property>
+    <property name="title" translatable="yes">Credentials</property>
     <child>
-      <object class="GtkFrame">
+      <object class="HdyPreferencesGroup">
         <property name="visible">True</property>
         <property name="can-focus">False</property>
-        <property name="label-xalign">0</property>
-        <property name="shadow-type">none</property>
+        <property name="description" translatable="yes">Pidgin does not store passwords directly, but uses the provider selected below to store passwords. Changing providers will not migrate existing stored passwords.</property>
+        <property name="title" translatable="yes">Credential Provider</property>
         <child>
-          <object class="GtkBox">
+          <object class="GtkListBox" id="credential_list">
             <property name="visible">True</property>
             <property name="can-focus">False</property>
-            <property name="margin-start">12</property>
-            <property name="spacing">6</property>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label" translatable="yes">Provider:</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkComboBox" id="combo">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="model">store</property>
-                <child>
-                  <object class="GtkCellRendererText" id="renderer"/>
-                  <attributes>
-                    <attribute name="markup">1</attribute>
-                  </attributes>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-        </child>
-        <child type="label">
-          <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="margin-bottom">6</property>
-            <property name="label" translatable="yes">Credential Provider</property>
-            <attributes>
-              <attribute name="weight" value="bold"/>
-            </attributes>
+            <property name="selection-mode">none</property>
+            <signal name="row-activated" handler="pidgin_credential_page_list_row_activated_cb" after="yes" swapped="no"/>
+            <style>
+              <class name="content"/>
+            </style>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">0</property>
-      </packing>
     </child>
   </template>
 </interface>
--- a/pidgin/resources/Prefs/prefs.ui	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/resources/Prefs/prefs.ui	Wed May 12 05:36:35 2021 -0500
@@ -2467,8 +2467,6 @@
                   <object class="PidginCredentialsPage">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">18</property>
                     <child>
                       <placeholder/>
                     </child>
--- a/pidgin/resources/pidgin.gresource.xml	Thu May 06 22:36:35 2021 -0500
+++ b/pidgin/resources/pidgin.gresource.xml	Wed May 12 05:36:35 2021 -0500
@@ -24,6 +24,7 @@
     <file compressed="true">Plugins/dialog.ui</file>
     <file compressed="true">Plugins/menu.ui</file>
     <file compressed="true">Prefs/credentials.ui</file>
+    <file compressed="true">Prefs/credentialprovider.ui</file>
     <file compressed="true">Prefs/ip.css</file>
     <file compressed="true">Prefs/prefs.ui</file>
     <file compressed="true">Prefs/vv.ui</file>
--- a/po/POTFILES.in	Thu May 06 22:36:35 2021 -0500
+++ b/po/POTFILES.in	Wed May 12 05:36:35 2021 -0500
@@ -353,6 +353,7 @@
 pidgin/pidgincontactcompletion.c
 pidgin/pidgincontactlist.c
 pidgin/pidginconversationwindow.c
+pidgin/pidgincredentialproviderrow.c
 pidgin/pidgincredentialproviderstore.c
 pidgin/pidgincredentialspage.c
 pidgin/pidgindebug.c
@@ -409,6 +410,7 @@
 pidgin/resources/Plugins/dialog.ui
 pidgin/resources/Plugins/menu.ui
 pidgin/resources/Prefs/credentials.ui
+pidgin/resources/Prefs/credentialprovider.ui
 pidgin/resources/Prefs/prefs.ui
 pidgin/resources/Prefs/vv.ui
 pidgin/resources/Privacy/dialog.ui

mercurial