Create a PidginProxyOptions widget

Mon, 02 May 2022 23:43:16 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 02 May 2022 23:43:16 -0500
changeset 41350
2bdf14aaf7a1
parent 41349
15aeaa1e84ec
child 41351
a2cd8d6d9483

Create a PidginProxyOptions widget

This replaces the old page in the account editor and uses a PurpleProxyInfo
object to for editing.

Testing Done:
Verified it populated saved values on an existing account, as well as saved values on existing account.
Also verified that you can create a new account with proxy options and that they're saved.

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

pidgin/gtkaccount.c file | annotate | diff | comparison | revisions
pidgin/meson.build file | annotate | diff | comparison | revisions
pidgin/pidginproxyoptions.c file | annotate | diff | comparison | revisions
pidgin/pidginproxyoptions.h file | annotate | diff | comparison | revisions
pidgin/resources/pidgin.gresource.xml file | annotate | diff | comparison | revisions
pidgin/resources/proxyoptions.ui file | annotate | diff | comparison | revisions
po/POTFILES.in file | annotate | diff | comparison | revisions
--- a/pidgin/gtkaccount.c	Mon May 02 21:57:35 2022 -0500
+++ b/pidgin/gtkaccount.c	Mon May 02 23:43:16 2022 -0500
@@ -36,6 +36,7 @@
 #include "pidgindialog.h"
 #include "minidialog.h"
 #include "pidginprotocolchooser.h"
+#include "pidginproxyoptions.h"
 
 enum
 {
@@ -92,8 +93,6 @@
 	char *protocol_id;
 	PurpleProtocol *protocol;
 
-	PurpleProxyType new_proxy_type;
-
 	GList *user_split_entries;
 	GList *protocol_opt_entries;
 
@@ -126,14 +125,7 @@
 	/* Protocol Options */
 	GtkWidget *protocol_frame;
 
-	/* Proxy Options */
-	GtkWidget *proxy_frame;
-	GtkWidget *proxy_vbox;
-	GtkWidget *proxy_dropdown;
-	GtkWidget *proxy_host_entry;
-	GtkWidget *proxy_port_entry;
-	GtkWidget *proxy_user_entry;
-	GtkWidget *proxy_pass_entry;
+	GtkWidget *proxy_options;
 
 	/* Voice & Video Options*/
 	GtkWidget *voice_frame;
@@ -159,7 +151,6 @@
 static void add_login_options(AccountPrefsDialog *dialog, GtkWidget *parent);
 static void add_user_options(AccountPrefsDialog *dialog, GtkWidget *parent);
 static void add_account_options(AccountPrefsDialog *dialog);
-static void add_proxy_options(AccountPrefsDialog *dialog, GtkWidget *parent);
 static void add_voice_options(AccountPrefsDialog *dialog);
 
 static GtkWidget *
@@ -917,210 +908,6 @@
 	g_list_free_full(opts, (GDestroyNotify)purple_account_option_destroy);
 }
 
-static GtkWidget *
-make_proxy_dropdown(void)
-{
-	GtkWidget *dropdown;
-	GtkListStore *model;
-	GtkTreeIter iter;
-	GtkCellRenderer *renderer;
-
-	model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
-	dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, purple_running_gnome() ? _("Use GNOME Proxy Settings")
-			:_("Use Global Proxy Settings"),
-			1, PURPLE_PROXY_TYPE_USE_GLOBAL,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("No Proxy"),
-			1, PURPLE_PROXY_TYPE_NONE,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("SOCKS 4"),
-			1, PURPLE_PROXY_TYPE_SOCKS4,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("SOCKS 5"),
-			1, PURPLE_PROXY_TYPE_SOCKS5,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("Tor/Privacy (SOCKS5)"),
-			1, PURPLE_PROXY_TYPE_TOR,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("HTTP"),
-			1, PURPLE_PROXY_TYPE_HTTP,
-			-1);
-
-	gtk_list_store_append(model, &iter);
-	gtk_list_store_set(model, &iter,
-			0, _("Use Environmental Settings"),
-			1, PURPLE_PROXY_TYPE_USE_ENVVAR,
-			-1);
-
-	renderer = gtk_cell_renderer_text_new();
-	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, TRUE);
-	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
-			"text", 0, NULL);
-
-	return dropdown;
-}
-
-static void
-proxy_type_changed_cb(GtkWidget *menu, AccountPrefsDialog *dialog)
-{
-	GtkTreeIter iter;
-
-	if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(menu), &iter)) {
-		int int_value;
-		gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(menu)), &iter,
-			1, &int_value, -1);
-		dialog->new_proxy_type = int_value;
-	}
-
-	if (dialog->new_proxy_type == PURPLE_PROXY_TYPE_USE_GLOBAL ||
-		dialog->new_proxy_type == PURPLE_PROXY_TYPE_NONE ||
-		dialog->new_proxy_type == PURPLE_PROXY_TYPE_USE_ENVVAR) {
-
-		gtk_widget_hide(dialog->proxy_vbox);
-	}
-	else
-		gtk_widget_show_all(dialog->proxy_vbox);
-}
-
-static void
-port_popup_cb(GtkWidget *w, GtkMenu *menu, gpointer data)
-{
-	GtkWidget *item1;
-	GtkWidget *item2;
-
-	/* This is an easter egg.
-	   It means one of two things, both intended as humourus:
-	   A) your network is really slow and you have nothing better to do than
-	      look at butterflies.
-	   B)You are looking really closely at something that shouldn't matter. */
-	item1 = gtk_menu_item_new_with_label(_("If you look real closely"));
-
-	/* This is an easter egg. See the comment on the previous line in the source. */
-	item2 = gtk_menu_item_new_with_label(_("you can see the butterflies mating"));
-
-	gtk_widget_show(item1);
-	gtk_widget_show(item2);
-
-	/* Prepend these in reverse order so they appear correctly. */
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), item2);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), item1);
-}
-
-static void
-add_proxy_options(AccountPrefsDialog *dialog, GtkWidget *parent)
-{
-	PurpleProxyInfo *proxy_info;
-	GtkWidget *vbox;
-	GtkWidget *vbox2;
-	GtkTreeIter iter;
-	GtkTreeModel *proxy_model;
-
-	if (dialog->proxy_frame != NULL)
-		gtk_widget_destroy(dialog->proxy_frame);
-
-	/* Main vbox */
-	dialog->proxy_frame = vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
-	gtk_container_add(GTK_CONTAINER(parent), vbox);
-	gtk_widget_show(vbox);
-
-	/* Proxy Type drop-down. */
-	dialog->proxy_dropdown = make_proxy_dropdown();
-
-	add_pref_box(dialog, vbox, _("Proxy _type:"), dialog->proxy_dropdown);
-
-	/* Setup the second vbox, which may be hidden at times. */
-	dialog->proxy_vbox = vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
-	gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 12);
-	gtk_widget_show(vbox2);
-
-	/* Host */
-	dialog->proxy_host_entry = gtk_entry_new();
-	add_pref_box(dialog, vbox2, _("_Host:"), dialog->proxy_host_entry);
-
-	/* Port */
-	dialog->proxy_port_entry = gtk_entry_new();
-	add_pref_box(dialog, vbox2, _("_Port:"), dialog->proxy_port_entry);
-
-	g_signal_connect(G_OBJECT(dialog->proxy_port_entry), "populate-popup",
-					 G_CALLBACK(port_popup_cb), NULL);
-
-	/* User */
-	dialog->proxy_user_entry = gtk_entry_new();
-
-	add_pref_box(dialog, vbox2, _("_Username:"), dialog->proxy_user_entry);
-
-	/* Password */
-	dialog->proxy_pass_entry = gtk_entry_new();
-	gtk_entry_set_visibility(GTK_ENTRY(dialog->proxy_pass_entry), FALSE);
-	add_pref_box(dialog, vbox2, _("Pa_ssword:"), dialog->proxy_pass_entry);
-
-	if (dialog->account != NULL &&
-		(proxy_info = purple_account_get_proxy_info(dialog->account)) != NULL) {
-		const char *value;
-		int int_val;
-
-		dialog->new_proxy_type = purple_proxy_info_get_proxy_type(proxy_info);
-
-		if ((value = purple_proxy_info_get_hostname(proxy_info)) != NULL)
-			gtk_entry_set_text(GTK_ENTRY(dialog->proxy_host_entry), value);
-
-		if ((int_val = purple_proxy_info_get_port(proxy_info)) != 0) {
-			char buf[11];
-
-			g_snprintf(buf, sizeof(buf), "%d", int_val);
-
-			gtk_entry_set_text(GTK_ENTRY(dialog->proxy_port_entry), buf);
-		}
-
-		if ((value = purple_proxy_info_get_username(proxy_info)) != NULL)
-			gtk_entry_set_text(GTK_ENTRY(dialog->proxy_user_entry), value);
-
-		if ((value = purple_proxy_info_get_password(proxy_info)) != NULL)
-			gtk_entry_set_text(GTK_ENTRY(dialog->proxy_pass_entry), value);
-
-	} else
-		dialog->new_proxy_type = PURPLE_PROXY_TYPE_USE_GLOBAL;
-
-	proxy_model = gtk_combo_box_get_model(
-		GTK_COMBO_BOX(dialog->proxy_dropdown));
-	if (gtk_tree_model_get_iter_first(proxy_model, &iter)) {
-		int int_val;
-		do {
-			gtk_tree_model_get(proxy_model, &iter, 1, &int_val, -1);
-			if (int_val == dialog->new_proxy_type) {
-				gtk_combo_box_set_active_iter(
-					GTK_COMBO_BOX(dialog->proxy_dropdown), &iter);
-				break;
-			}
-		} while(gtk_tree_model_iter_next(proxy_model, &iter));
-	}
-
-	proxy_type_changed_cb(dialog->proxy_dropdown, dialog);
-
-	/* Connect signals. */
-	g_signal_connect(G_OBJECT(dialog->proxy_dropdown), "changed",
-					 G_CALLBACK(proxy_type_changed_cb), dialog);
-}
-
 static void
 add_voice_options(AccountPrefsDialog *dialog)
 {
@@ -1372,63 +1159,8 @@
 		}
 	}
 
-	/* Set the proxy stuff. */
-	proxy_info = purple_account_get_proxy_info(account);
-
-	/* Create the proxy info if it doesn't exist. */
-	if (proxy_info == NULL) {
-		proxy_info = purple_proxy_info_new();
-		purple_account_set_proxy_info(account, proxy_info);
-	} else {
-		/* Add a reference to make sure the proxy info stays around. */
-		g_object_ref(proxy_info);
-	}
-
-	/* Set the proxy info type. */
-	purple_proxy_info_set_proxy_type(proxy_info, dialog->new_proxy_type);
-
-	/* Host */
-	value = gtk_entry_get_text(GTK_ENTRY(dialog->proxy_host_entry));
-
-	if (*value != '\0')
-		purple_proxy_info_set_hostname(proxy_info, value);
-	else
-		purple_proxy_info_set_hostname(proxy_info, NULL);
-
-	/* Port */
-	value = gtk_entry_get_text(GTK_ENTRY(dialog->proxy_port_entry));
-
-	if (*value != '\0')
-		purple_proxy_info_set_port(proxy_info, atoi(value));
-	else
-		purple_proxy_info_set_port(proxy_info, 0);
-
-	/* Username */
-	value = gtk_entry_get_text(GTK_ENTRY(dialog->proxy_user_entry));
-
-	if (*value != '\0')
-		purple_proxy_info_set_username(proxy_info, value);
-	else
-		purple_proxy_info_set_username(proxy_info, NULL);
-
-	/* Password */
-	value = gtk_entry_get_text(GTK_ENTRY(dialog->proxy_pass_entry));
-
-	if (*value != '\0')
-		purple_proxy_info_set_password(proxy_info, value);
-	else
-		purple_proxy_info_set_password(proxy_info, NULL);
-
-	/* If there are no values set then proxy_info NULL */
-	if ((purple_proxy_info_get_proxy_type(proxy_info) == PURPLE_PROXY_TYPE_USE_GLOBAL) &&
-		(purple_proxy_info_get_hostname(proxy_info) == NULL) &&
-		(purple_proxy_info_get_port(proxy_info) == 0) &&
-		(purple_proxy_info_get_username(proxy_info) == NULL) &&
-		(purple_proxy_info_get_password(proxy_info) == NULL))
-	{
-		purple_account_set_proxy_info(account, NULL);
-		g_clear_object(&proxy_info);
-	}
+	proxy_info = pidgin_proxy_options_get_info(PIDGIN_PROXY_OPTIONS(dialog->proxy_options));
+	purple_account_set_proxy_info(account, proxy_info);
 
 	/* Voice and Video settings */
 	if (dialog->voice_frame) {
@@ -1459,8 +1191,6 @@
 
 	/* We no longer need the data from the dialog window */
 	account_win_destroy_cb(NULL, NULL, dialog);
-
-	g_clear_object(&proxy_info);
 }
 
 static void
@@ -1488,7 +1218,6 @@
 	GtkWidget *win;
 	GtkWidget *main_vbox;
 	GtkWidget *vbox;
-	GtkWidget *dbox;
 	GtkWidget *notebook;
 	GtkWidget *button;
 
@@ -1564,13 +1293,15 @@
 	/* Setup the page with 'Advanced' (protocol options). */
 	add_account_options(dialog);
 
-	/* Setup the page with 'Proxy'. */
-	dbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
-	gtk_container_set_border_width(GTK_CONTAINER(dbox), 12);
-	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dbox,
-			gtk_label_new_with_mnemonic(_("P_roxy")));
-	gtk_widget_show(dbox);
-	add_proxy_options(dialog, dbox);
+	/* Setup the proxy options page. */
+	dialog->proxy_options = pidgin_proxy_options_new();
+	if(PURPLE_IS_ACCOUNT(dialog->account)) {
+		pidgin_proxy_options_set_info(PIDGIN_PROXY_OPTIONS(dialog->proxy_options),
+		                              purple_account_get_proxy_info(dialog->account));
+	}
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dialog->proxy_options,
+	                         gtk_label_new_with_mnemonic(_("Proxy")));
+	gtk_widget_show(dialog->proxy_options);
 
 	add_voice_options(dialog);
 
--- a/pidgin/meson.build	Mon May 02 21:57:35 2022 -0500
+++ b/pidgin/meson.build	Mon May 02 23:43:16 2022 -0500
@@ -50,6 +50,7 @@
 	'pidginpresenceicon.c',
 	'pidginprotocolchooser.c',
 	'pidginprotocolstore.c',
+	'pidginproxyoptions.c',
 	'pidginscrollbook.c',
 	'pidginstatusbox.c',
 	'pidginstatusmanager.c',
@@ -113,6 +114,7 @@
 	'pidginpresenceicon.h',
 	'pidginprotocolchooser.h',
 	'pidginprotocolstore.h',
+	'pidginproxyoptions.h',
 	'pidginscrollbook.h',
 	'pidginstatusbox.h',
 	'pidginstatusmanager.h',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginproxyoptions.c	Mon May 02 23:43:16 2022 -0500
@@ -0,0 +1,463 @@
+/*
+ * 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 <glib/gi18n.h>
+
+#include "pidginproxyoptions.h"
+
+#include "gtkaccount.h"
+#include "pidgincore.h"
+
+struct _PidginProxyOptions {
+	GtkBox parent;
+
+	gboolean show_global;
+	PurpleProxyInfo *info;
+
+	GtkListStore *model;
+	GtkTreeModelFilter *filter;
+
+	GtkWidget *proxy_type;
+
+	GtkWidget *options;
+	GtkWidget *hostname;
+	GtkWidget *port;
+	GtkWidget *username;
+	GtkWidget *password;
+};
+
+enum {
+	PROP_0,
+	PROP_SHOW_GLOBAL,
+	PROP_INFO,
+	N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+enum {
+	COLUMN_TYPE,
+	COLUMN_TITLE,
+};
+
+G_DEFINE_TYPE(PidginProxyOptions, pidgin_proxy_options, GTK_TYPE_BOX)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static gboolean
+pidgin_proxy_options_null_to_empty_str(G_GNUC_UNUSED GBinding *binding,
+                                       const GValue *from_value,
+                                       GValue *to_value,
+                                       G_GNUC_UNUSED gpointer data)
+{
+	const gchar *str_val = g_value_get_string(from_value);
+
+	if(str_val == NULL) {
+		str_val = "";
+	}
+
+	g_value_set_string(to_value, str_val);
+
+	return TRUE;
+}
+
+static gboolean
+pidgin_proxy_options_empty_str_to_null(G_GNUC_UNUSED GBinding *binding,
+                                       const GValue *from_value,
+                                       GValue *to_value,
+                                       G_GNUC_UNUSED gpointer data)
+{
+	const gchar *str_val = g_value_get_string(from_value);
+
+	if(str_val != NULL && *str_val == '\0') {
+		str_val = NULL;
+	}
+
+	g_value_set_string(to_value, str_val);
+
+	return TRUE;
+}
+
+static gboolean
+pidgin_proxy_options_gint_to_double(G_GNUC_UNUSED GBinding *binding,
+                                    const GValue *from_value, GValue *to_value,
+                                    G_GNUC_UNUSED gpointer data)
+{
+	g_value_set_double(to_value, (gdouble)g_value_get_int(from_value));
+
+	return TRUE;
+}
+
+static gboolean
+pidgin_proxy_options_double_to_gint(G_GNUC_UNUSED GBinding *binding,
+                                    const GValue *from_value, GValue *to_value,
+                                    G_GNUC_UNUSED gpointer user_data)
+{
+	g_value_set_int(to_value, (gint)g_value_get_double(from_value));
+
+	return TRUE;
+}
+
+static gboolean
+pidgin_proxy_options_filter_visible(GtkTreeModel *model, GtkTreeIter *iter,
+                                    gpointer data)
+{
+	PidginProxyOptions *options = data;
+	PurpleProxyType type;
+
+	gtk_tree_model_get(model, iter, COLUMN_TYPE, &type, -1);
+
+	if(type == PURPLE_PROXY_TYPE_USE_GLOBAL) {
+		return options->show_global;
+	}
+
+	return TRUE;
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_proxy_options_proxy_type_changed_cb(GtkComboBox *box, gpointer data) {
+	PidginProxyOptions *options = data;
+	PurpleProxyType type;
+	GtkTreeIter iter;
+	gboolean sensitive = TRUE;
+
+	if(!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(options->proxy_type), &iter)) {
+		return;
+	}
+
+	gtk_tree_model_get(GTK_TREE_MODEL(options->filter), &iter,
+	                   COLUMN_TYPE, &type,
+	                   -1);
+
+	purple_proxy_info_set_proxy_type(options->info, type);
+
+	switch(type) {
+		case PURPLE_PROXY_TYPE_USE_GLOBAL:
+		case PURPLE_PROXY_TYPE_NONE:
+		case PURPLE_PROXY_TYPE_USE_ENVVAR:
+			sensitive = FALSE;
+			break;
+		default:
+			break;
+	}
+
+	gtk_widget_set_sensitive(options->options, sensitive);
+}
+
+static void
+pidgin_proxy_options_ports_popup_cb(G_GNUC_UNUSED GtkEntry *entry,
+                                    GtkWidget *widget,
+                                    G_GNUC_UNUSED gpointer data)
+{
+	GtkWidget *item = NULL;
+
+	/* This is an easter egg. The items are in reverse order because they are
+	 * prepended to the menu.
+	 *
+	 * It means one of two things, both intended as humorous:
+	 * A) your network is really slow and you have nothing better to do than
+	 *    look at butterflies.
+	 * B) You are looking really closely at something that shouldn't matter.
+	 */
+
+	item = gtk_menu_item_new_with_label(_("you can see the butterflies mating"));
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(widget), item);
+	gtk_widget_show(item);
+
+	/* This is also an easter egg, see the previous comment. */
+	item = gtk_menu_item_new_with_label(_("If you look real closely"));
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(widget), item);
+	gtk_widget_show(item);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_proxy_options_get_property(GObject *obj, guint param_id, GValue *value,
+                                  GParamSpec *pspec)
+{
+	PidginProxyOptions *options = PIDGIN_PROXY_OPTIONS(obj);
+
+	switch(param_id) {
+		case PROP_SHOW_GLOBAL:
+			g_value_set_boolean(value,
+			                    pidgin_proxy_options_get_show_global(options));
+			break;
+		case PROP_INFO:
+			g_value_set_object(value, pidgin_proxy_options_get_info(options));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_proxy_options_set_property(GObject *obj, guint param_id,
+                                  const GValue *value, GParamSpec *pspec)
+{
+	PidginProxyOptions *options = PIDGIN_PROXY_OPTIONS(obj);
+
+	switch(param_id) {
+		case PROP_SHOW_GLOBAL:
+			pidgin_proxy_options_set_show_global(options,
+			                                     g_value_get_boolean(value));
+			break;
+		case PROP_INFO:
+			pidgin_proxy_options_set_info(options, g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_proxy_options_init(PidginProxyOptions *options) {
+	gtk_widget_init_template(GTK_WIDGET(options));
+
+	/* Set the visible function so we can control the visibility of the "Use
+	 * Global Proxy" option.
+	 */
+	gtk_tree_model_filter_set_visible_func(options->filter,
+	                                       pidgin_proxy_options_filter_visible,
+	                                       options, NULL);
+}
+
+static void
+pidgin_proxy_options_constructed(GObject *obj) {
+	PidginProxyOptions *options = PIDGIN_PROXY_OPTIONS(obj);
+
+	G_OBJECT_CLASS(pidgin_proxy_options_parent_class)->constructed(obj);
+
+	if(options->info == NULL) {
+		pidgin_proxy_options_set_info(options, NULL);
+	}
+}
+
+static void
+pidgin_proxy_options_class_init(PidginProxyOptionsClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->get_property = pidgin_proxy_options_get_property;
+	obj_class->set_property = pidgin_proxy_options_set_property;
+	obj_class->constructed = pidgin_proxy_options_constructed;
+
+	/**
+	 * PidginProxyOptions::show-global:
+	 *
+	 * Whether or not to show the "Use Global Proxy Settings" option. This
+	 * is turned off for the preferences where we use this widget to define
+	 * the global proxy settings.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_SHOW_GLOBAL] = g_param_spec_boolean(
+		"show-global", "show-global",
+		"Whether or not to show the global proxy settings option",
+		TRUE,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginProxyOptions::info:
+	 *
+	 * The [class@Purple.ProxyInfo] that this options widget is configuring. If
+	 * unset, a new instance will be created.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_INFO] = g_param_spec_object(
+		"info", "info",
+		"The proxy info to configure",
+		PURPLE_TYPE_PROXY_INFO,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | 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/Pidgin3/proxyoptions.ui"
+	);
+
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     model);
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     filter);
+
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     proxy_type);
+
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     options);
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     hostname);
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     port);
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     username);
+	gtk_widget_class_bind_template_child(widget_class, PidginProxyOptions,
+	                                     password);
+
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_proxy_options_proxy_type_changed_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_proxy_options_ports_popup_cb);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+pidgin_proxy_options_new(void) {
+	return g_object_new(PIDGIN_TYPE_PROXY_OPTIONS, NULL);
+}
+
+void
+pidgin_proxy_options_set_show_global(PidginProxyOptions *options,
+                                     gboolean show_global)
+{
+	PurpleProxyType proxy_type = PURPLE_PROXY_TYPE_USE_GLOBAL;
+
+	g_return_if_fail(PIDGIN_IS_PROXY_OPTIONS(options));
+
+	if(show_global == options->show_global) {
+		return;
+	}
+
+	options->show_global = show_global;
+
+	if(options->info == NULL) {
+		g_object_notify_by_pspec(G_OBJECT(options),
+		                         properties[PROP_SHOW_GLOBAL]);
+
+		return;
+	}
+
+	proxy_type = purple_proxy_info_get_proxy_type(options->info);
+	if(proxy_type == PURPLE_PROXY_TYPE_USE_GLOBAL && show_global == FALSE) {
+		proxy_type = PURPLE_PROXY_TYPE_NONE;
+	}
+
+	purple_proxy_info_set_proxy_type(options->info, proxy_type);
+
+	options->show_global = show_global;
+
+	g_object_notify_by_pspec(G_OBJECT(options), properties[PROP_SHOW_GLOBAL]);
+
+	/* Tell the filter to rerun. */
+	gtk_tree_model_filter_refilter(options->filter);
+}
+
+gboolean
+pidgin_proxy_options_get_show_global(PidginProxyOptions *options) {
+	g_return_val_if_fail(PIDGIN_IS_PROXY_OPTIONS(options), FALSE);
+
+	return options->show_global;
+}
+
+void
+pidgin_proxy_options_set_info(PidginProxyOptions *options,
+                              PurpleProxyInfo *info)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	g_return_if_fail(PIDGIN_IS_PROXY_OPTIONS(options));
+
+	/* We want to always have a PurpleProxyInfo instance to make management
+	 * easier. So if someone clears it, just replace it with an empty one
+	 * instead.
+	 */
+	if(!PURPLE_IS_PROXY_INFO(info)) {
+		PurpleProxyInfo *empty_info = purple_proxy_info_new();
+
+		if(!g_set_object(&options->info, empty_info)) {
+			g_assert_not_reached();
+		}
+
+		g_object_unref(empty_info);
+	} else if(!g_set_object(&options->info, info)) {
+		return;
+	}
+
+	model = GTK_TREE_MODEL(options->filter);
+	if(gtk_tree_model_get_iter_first(model, &iter)) {
+		do {
+			PurpleProxyType type = purple_proxy_info_get_proxy_type(options->info);
+			PurpleProxyType row_type;
+
+			gtk_tree_model_get(model, &iter, COLUMN_TYPE, &row_type, -1);
+			if(row_type == type) {
+				gtk_combo_box_set_active_iter(GTK_COMBO_BOX(options->proxy_type),
+				                              &iter);
+				break;
+			}
+		} while(gtk_tree_model_iter_next(model, &iter));
+	}
+
+	g_object_bind_property_full(options->info, "hostname",
+	                            options->hostname, "text",
+	                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+	                            pidgin_proxy_options_null_to_empty_str,
+	                            pidgin_proxy_options_empty_str_to_null,
+	                            NULL,
+	                            NULL);
+
+	g_object_bind_property_full(options->info, "port",
+	                            options->port, "value",
+	                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+	                            pidgin_proxy_options_gint_to_double,
+	                            pidgin_proxy_options_double_to_gint,
+	                            NULL,
+	                            NULL);
+
+	g_object_bind_property_full(options->info, "username",
+	                            options->username, "text",
+	                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+	                            pidgin_proxy_options_null_to_empty_str,
+	                            pidgin_proxy_options_empty_str_to_null,
+	                            NULL,
+	                            NULL);
+
+
+	g_object_bind_property_full(options->info, "password",
+	                            options->password, "text",
+	                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+	                            pidgin_proxy_options_null_to_empty_str,
+	                            pidgin_proxy_options_empty_str_to_null,
+	                            NULL,
+	                            NULL);
+
+	g_object_notify_by_pspec(G_OBJECT(options), properties[PROP_INFO]);
+}
+
+PurpleProxyInfo *
+pidgin_proxy_options_get_info(PidginProxyOptions *options) {
+	g_return_val_if_fail(PIDGIN_IS_PROXY_OPTIONS(options), NULL);
+
+	return options->info;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginproxyoptions.h	Mon May 02 23:43:16 2022 -0500
@@ -0,0 +1,108 @@
+/*
+ * 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_PROXY_OPTIONS_H
+#define PIDGIN_PROXY_OPTIONS_H
+
+#include <gtk/gtk.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginProxyOptions:
+ *
+ * A widget for the proxy options in the account editor.
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_PROXY_OPTIONS (pidgin_proxy_options_get_type())
+G_DECLARE_FINAL_TYPE(PidginProxyOptions, pidgin_proxy_options, PIDGIN,
+                     PROXY_OPTIONS, GtkBox)
+
+/**
+ * pidgin_proxy_options_new:
+ *
+ * Creates a new proxy options widget.
+ *
+ * Returns: (transfer full): The widget.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_proxy_options_new(void);
+
+/**
+ * pidgin_proxy_options_set_show_global:
+ * @options: The instance.
+ * @show_global: Whether or not to show the use global settings proxy item.
+ *
+ * Sets whether or not to show the "Use Global Proxy Settings" item.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_proxy_options_set_show_global(PidginProxyOptions *options, gboolean show_global);
+
+/**
+ * pidgin_proxy_options_get_show_global:
+ * @options: The instance.
+ *
+ * Gets whether or not @options is displaying the "Use Global Proxy Settings"
+ * item.
+ *
+ * Returns: %TRUE if displaying it, %FALSE otherwise.
+ *
+ * Since: 3.0.0
+ */
+gboolean pidgin_proxy_options_get_show_global(PidginProxyOptions *options);
+
+/**
+ * pidgin_proxy_options_get_info:
+ * @options: The instance.
+ *
+ * Gets the [class@Purple.ProxyInfo] that is being configured.
+ *
+ * Returns: (transfer none): The proxy info.
+ *
+ * Since: 3.0.0
+ */
+PurpleProxyInfo *pidgin_proxy_options_get_info(PidginProxyOptions *options);
+
+/**
+ * pidgin_proxy_options_set_info:
+ * @options: The instance.
+ * @info: The [class@Purple.ProxyInfo] to set.
+ *
+ * The proxy info that will be configured.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_proxy_options_set_info(PidginProxyOptions *options, PurpleProxyInfo *info);
+
+G_END_DECLS
+
+#endif /* PIDGIN_PROXY_OPTIONS_H */
--- a/pidgin/resources/pidgin.gresource.xml	Mon May 02 21:57:35 2022 -0500
+++ b/pidgin/resources/pidgin.gresource.xml	Mon May 02 23:43:16 2022 -0500
@@ -37,6 +37,7 @@
     <file compressed="true">Xfer/xfer.ui</file>
     <file compressed="true">closebutton.ui</file>
     <file compressed="true">gtk/menus.ui</file>
+    <file compressed="true">proxyoptions.ui</file>
     <file compressed="true">statusprimitivechooser.ui</file>
     <file>icons/16x16/status/pidgin-user-available.png</file>
     <file>icons/16x16/status/pidgin-user-away.png</file>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/resources/proxyoptions.ui	Mon May 02 23:43:16 2022 -0500
@@ -0,0 +1,305 @@
+<?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.24"/>
+  <!-- interface-license-type gplv2 -->
+  <!-- interface-name Pidgin -->
+  <!-- interface-description Internet Messenger -->
+  <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+  <object class="GtkListStore" id="model">
+    <columns>
+      <!-- column-name type -->
+      <column type="PurpleProxyType"/>
+      <!-- column-name description -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_USE_GLOBAL</col>
+        <col id="1" translatable="yes">Use Global Proxy Settings</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_NONE</col>
+        <col id="1" translatable="yes">No Proxy</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_SOCKS4</col>
+        <col id="1" translatable="yes">SOCKS 4</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_SOCKS5</col>
+        <col id="1" translatable="yes">SOCKS 5</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_TOR</col>
+        <col id="1" translatable="yes">TOR/Privacy (SOCKS 5)</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_HTTP</col>
+        <col id="1" translatable="yes">HTTP</col>
+      </row>
+      <row>
+        <col id="0">PURPLE_PROXY_TYPE_USE_ENVVAR</col>
+        <col id="1" translatable="yes">Use Environmental Settings</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkTreeModelFilter" id="filter">
+    <property name="child-model">model</property>
+  </object>
+  <object class="GtkAdjustment" id="port_adjustment">
+    <property name="lower">-1</property>
+    <property name="upper">65535</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
+  </object>
+  <template class="PidginProxyOptions" parent="GtkBox">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <child>
+          <object class="GtkLabel" id="proxy_type_label">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="label" translatable="yes">Proxy _type:</property>
+            <property name="use-underline">True</property>
+            <property name="mnemonic-widget">proxy_type</property>
+            <property name="xalign">0</property>
+            <property name="yalign">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="proxy_type">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="model">filter</property>
+            <signal name="changed" handler="pidgin_proxy_options_proxy_type_changed_cb" object="PidginProxyOptions" swapped="no"/>
+            <child>
+              <object class="GtkCellRendererText"/>
+              <attributes>
+                <attribute name="text">1</attribute>
+              </attributes>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox" id="options">
+        <property name="visible">True</property>
+        <property name="sensitive">False</property>
+        <property name="can-focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <child>
+              <object class="GtkLabel" id="host_label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">_Host:</property>
+                <property name="use-underline">True</property>
+                <property name="mnemonic-widget">hostname</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="hostname">
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="progress-pulse-step">0</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <child>
+              <object class="GtkLabel" id="port_label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">_Port:</property>
+                <property name="use-underline">True</property>
+                <property name="mnemonic-widget">port</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSpinButton" id="port">
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="input-hints">GTK_INPUT_HINT_NO_EMOJI | GTK_INPUT_HINT_NONE</property>
+                <property name="adjustment">port_adjustment</property>
+                <property name="numeric">True</property>
+                <signal name="populate-popup" handler="pidgin_proxy_options_ports_popup_cb" object="PidginProxyOptions" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <child>
+              <object class="GtkLabel" id="username_label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">_Username:</property>
+                <property name="use-underline">True</property>
+                <property name="mnemonic-widget">username</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="username">
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <child>
+              <object class="GtkLabel" id="password_label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">Pa_ssword:</property>
+                <property name="use-underline">True</property>
+                <property name="mnemonic-widget">password</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="password">
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <widgets>
+      <widget name="proxy_type_label"/>
+      <widget name="host_label"/>
+      <widget name="port_label"/>
+      <widget name="username_label"/>
+      <widget name="password_label"/>
+    </widgets>
+  </object>
+</interface>
--- a/po/POTFILES.in	Mon May 02 21:57:35 2022 -0500
+++ b/po/POTFILES.in	Mon May 02 23:43:16 2022 -0500
@@ -336,6 +336,7 @@
 pidgin/pidginaccountchooser.c
 pidgin/pidginaccountfilterconnected.c
 pidgin/pidginaccountfilterprotocol.c
+pidgin/pidginaccountmanager.c
 pidgin/pidginaccountsenabledmenu.c
 pidgin/pidginaccountsmenu.c
 pidgin/pidginaccountstore.c
@@ -364,6 +365,7 @@
 pidgin/pidginpresenceicon.c
 pidgin/pidginprotocolchooser.c
 pidgin/pidginprotocolstore.c
+pidgin/pidginproxyoptions.c
 pidgin/pidginscrollbook.c
 pidgin/pidginstatusbox.c
 pidgin/pidginstatusprimitivechooser.c
@@ -415,6 +417,7 @@
 pidgin/resources/Whiteboard/whiteboard.ui
 pidgin/resources/Xfer/xfer.ui
 pidgin/resources/gtk/menus.ui
+pidgin/resources/proxyoptions.ui
 pidgin/win32/gtkwin32dep.c
 pidgin/win32/winpidgin.c
 purple-history/purplehistorycore.c

mercurial