# HG changeset patch
# Node ID 14cff41e0282
# Parent  0472e404b129
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -871,6 +871,7 @@
 static void
 account_abled_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
                  PurpleAccount *account,
+                 G_GNUC_UNUSED GParamSpec *pspec,
                  G_GNUC_UNUSED gpointer data)
 {
 	if(accounts.window == NULL) {
diff --git a/libpurple/account.h b/libpurple/account.h
--- a/libpurple/account.h
+++ b/libpurple/account.h
@@ -964,6 +964,38 @@
  */
 PurpleContact *purple_account_get_contact(PurpleAccount *account);
 
+/**
+ * purple_account_freeze_notify_settings:
+ * @account: The instance.
+ *
+ * Increment the freeze count for settings on @account. When the freeze count
+ * is greater than 0, the [signal@Purple.Account::setting-changed] signal will
+ * not be emitted until the freeze count returns to 0.
+ *
+ * This is intended to only notify once @account has reached a consistent
+ * state. Most user interfaces change all of the properties on an account at
+ * once and some of them may be co-dependent, so all values need to be updated
+ * before the change can be acted upon.
+ *
+ * Call [method@Purple.Account.thaw_notify_settings] to decrement the freeze
+ * counter.
+ *
+ * Since: 3.0.0
+ */
+void purple_account_freeze_notify_settings(PurpleAccount *account);
+
+/**
+ * purple_account_thaw_notify_settings:
+ * @account: The instance.
+ *
+ * Decrements the freeze count for settings on @account.
+ *
+ * See [method@Purple.Account.freeze_notify_settings] for more information.
+ *
+ * Since: 3.0.0
+ */
+void purple_account_thaw_notify_settings(PurpleAccount *account);
+
 G_END_DECLS
 
 #endif /* PURPLE_ACCOUNT_H */
diff --git a/libpurple/account.c b/libpurple/account.c
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -44,6 +44,13 @@
 #include "signals.h"
 #include "util.h"
 
+typedef struct {
+	GSList *names;
+	guint ref_count;
+} PurpleAccountSettingFreezeQueue;
+
+G_LOCK_DEFINE_STATIC(setting_notify_lock);
+
 struct _PurpleAccount
 {
 	GObject gparent;
@@ -70,6 +77,7 @@
 	gboolean disconnecting;     /* The account is currently disconnecting */
 
 	GHashTable *settings;       /* Protocol-specific settings.            */
+	PurpleAccountSettingFreezeQueue *freeze_queue;
 
 	PurpleProxyInfo *proxy_info;  /* Proxy information.  This will be set */
 								/*   to NULL when the account inherits      */
@@ -104,9 +112,7 @@
 
 } PurpleAccountSetting;
 
-/* GObject Property enums */
-enum
-{
+enum {
 	PROP_0,
 	PROP_ID,
 	PROP_USERNAME,
@@ -123,8 +129,13 @@
 	PROP_CONTACT,
 	PROP_LAST
 };
-
-static GParamSpec    *properties[PROP_LAST];
+static GParamSpec *properties[PROP_LAST];
+
+enum {
+	SIG_SETTING_CHANGED,
+	N_SIGNALS
+};
+static guint signals[N_SIGNALS] = {0, };
 
 G_DEFINE_TYPE(PurpleAccount, purple_account, G_TYPE_OBJECT);
 
@@ -132,6 +143,38 @@
  * Helpers
  *****************************************************************************/
 static void
+purple_account_free_notify_settings(PurpleAccount *account) {
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	g_slist_free_full(account->freeze_queue->names, g_free);
+
+	g_slice_free(PurpleAccountSettingFreezeQueue, account->freeze_queue);
+	account->freeze_queue = NULL;
+}
+
+static void
+purple_account_setting_changed_emit(PurpleAccount *account, const char *name) {
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+	g_return_if_fail(name != NULL);
+
+	G_LOCK(setting_notify_lock);
+
+	if(account->freeze_queue != NULL) {
+		GSList *names = account->freeze_queue->names;
+
+		if(g_slist_find_custom(names, name, (GCompareFunc)g_strcmp0) == NULL) {
+			names = g_slist_prepend(names, g_strdup(name));
+			account->freeze_queue->names = names;
+		}
+	} else {
+		g_signal_emit(account, signals[SIG_SETTING_CHANGED],
+		              g_quark_from_string(name), name);
+	}
+
+	G_UNLOCK(setting_notify_lock);
+}
+
+static void
 purple_account_real_connect(PurpleAccount *account, const char *password) {
 	PurpleConnection *connection = NULL;
 	PurpleProtocol *protocol = NULL;
@@ -842,6 +885,8 @@
 
 	purple_debug_info("account", "Destroying account %p", account);
 
+	purple_account_free_notify_settings(account);
+
 	manager = purple_conversation_manager_get_default();
 	l = purple_conversation_manager_get_all(manager);
 	while(l != NULL) {
@@ -877,8 +922,7 @@
 }
 
 static void
-purple_account_class_init(PurpleAccountClass *klass)
-{
+purple_account_class_init(PurpleAccountClass *klass) {
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
 	obj_class->constructed = purple_account_constructed;
@@ -988,6 +1032,33 @@
 		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties(obj_class, PROP_LAST, properties);
+
+	/**
+	 * PurpleAccount::setting-changed:
+	 * @account: The account whose setting changed.
+	 * @name: The name of the setting that changed.
+	 *
+	 * The ::setting-changed signal is emitted whenever an account setting is
+	 * changed.
+	 *
+	 * This signal supports details, so you can be notified when a single
+	 * setting changes. For example, say there's a setting named `foo`,
+	 * connecting to `setting-changed::foo` will only be called when the `foo`
+	 * setting is changed.
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_SETTING_CHANGED] = g_signal_new_class_handler(
+		"setting-changed",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		1,
+		G_TYPE_STRING);
 }
 
 /******************************************************************************
@@ -1488,6 +1559,8 @@
 
 	g_hash_table_insert(account->settings, g_strdup(name), setting);
 
+	purple_account_setting_changed_emit(account, name);
+
 	purple_accounts_schedule_save();
 }
 
@@ -1507,6 +1580,8 @@
 
 	g_hash_table_insert(account->settings, g_strdup(name), setting);
 
+	purple_account_setting_changed_emit(account, name);
+
 	purple_accounts_schedule_save();
 }
 
@@ -1525,6 +1600,8 @@
 
 	g_hash_table_insert(account->settings, g_strdup(name), setting);
 
+	purple_account_setting_changed_emit(account, name);
+
 	purple_accounts_schedule_save();
 }
 
@@ -2341,3 +2418,59 @@
 
 	return account->contact;
 }
+
+void
+purple_account_freeze_notify_settings(PurpleAccount *account) {
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	G_LOCK(setting_notify_lock);
+	if(account->freeze_queue == NULL) {
+		account->freeze_queue = g_slice_new0(PurpleAccountSettingFreezeQueue);
+	}
+
+	account->freeze_queue->ref_count++;
+
+	G_UNLOCK(setting_notify_lock);
+}
+
+void
+purple_account_thaw_notify_settings(PurpleAccount *account) {
+	GSList *names = NULL;
+
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	G_LOCK(setting_notify_lock);
+	if(G_UNLIKELY(account->freeze_queue->ref_count == 0)) {
+		G_UNLOCK(setting_notify_lock);
+
+		g_critical("purple_account_settings_thaw_notify called for account %s "
+		           "(%s) when not frozen",
+		           purple_account_get_username(account),
+		           purple_account_get_protocol_id(account));
+
+		return;
+	}
+
+	account->freeze_queue->ref_count--;
+	if(account->freeze_queue->ref_count > 0) {
+		G_UNLOCK(setting_notify_lock);
+
+		return;
+	}
+
+	/* This was the last ref, so fire off the signals. */
+	names = account->freeze_queue->names;
+	while(names != NULL) {
+		char *name = names->data;
+
+		g_signal_emit(account, signals[SIG_SETTING_CHANGED],
+		              g_quark_from_string(name), name);
+
+		names = g_slist_delete_link(names, names);
+		g_free(name);
+	}
+
+	purple_account_free_notify_settings(account);
+
+	G_UNLOCK(setting_notify_lock);
+}
diff --git a/libpurple/purpleaccountmanager.c b/libpurple/purpleaccountmanager.c
--- a/libpurple/purpleaccountmanager.c
+++ b/libpurple/purpleaccountmanager.c
@@ -27,6 +27,7 @@
 	SIG_ADDED,
 	SIG_REMOVED,
 	SIG_ACCOUNT_CHANGED,
+	SIG_ACCOUNT_SETTING_CHANGED,
 	N_SIGNALS,
 };
 static guint signals[N_SIGNALS] = {0, };
@@ -54,7 +55,16 @@
 {
 	g_signal_emit(data, signals[SIG_ACCOUNT_CHANGED],
 	              g_param_spec_get_name_quark(pspec),
-	              source);
+	              source, pspec);
+}
+
+static void
+purple_account_manager_account_setting_changed_cb(PurpleAccount *account,
+                                                  const char *name,
+                                                  gpointer data)
+{
+	g_signal_emit(data, signals[SIG_ACCOUNT_SETTING_CHANGED],
+	              g_quark_from_string(name), account, name);
 }
 
 /******************************************************************************
@@ -168,6 +178,7 @@
 	 * PurpleAccountManager::account-changed:
 	 * @manager: The account manager instance.
 	 * @account: The account that was changed.
+	 * @pspec: The [class@GObject.ParamSpec] for the property that changed.
 	 *
 	 * This is a propagation of the notify signal from @account. This means
 	 * that your callback will be called for any account that @manager knows
@@ -188,8 +199,38 @@
 		NULL,
 		NULL,
 		G_TYPE_NONE,
-		1,
-		PURPLE_TYPE_ACCOUNT);
+		2,
+		PURPLE_TYPE_ACCOUNT,
+		G_TYPE_PARAM);
+
+	/**
+	 * PurpleAccountManager::account-setting-changed:
+	 * @manager: The account manager instance.
+	 * @account: The account that was changed.
+	 * @name: The name of the setting that was changed.
+	 *
+	 * This is a propagation of [signal@Purple.Account::setting-changed]
+	 * signal. This means that your callback will be called for any account
+	 * that @manager knows about.
+	 *
+	 * This also supports details, so you can specify the signal name as
+	 * something like `account-setting-changed::use-tls` and your callback will
+	 * only be called when the `use-tls` setting of @account has been changed.
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_ACCOUNT_SETTING_CHANGED] = g_signal_new_class_handler(
+		"account-setting-changed",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		2,
+		PURPLE_TYPE_ACCOUNT,
+		G_TYPE_STRING);
 }
 
 /******************************************************************************
@@ -243,6 +284,9 @@
 	g_signal_connect_object(account, "notify",
 	                        G_CALLBACK(purple_account_manager_account_notify_cb),
 	                        manager, 0);
+	g_signal_connect_object(account, "setting-changed",
+	                        G_CALLBACK(purple_account_manager_account_setting_changed_cb),
+	                        manager, 0);
 
 	purple_accounts_schedule_save();
 
diff --git a/pidgin/pidginaccounteditor.c b/pidgin/pidginaccounteditor.c
--- a/pidgin/pidginaccounteditor.c
+++ b/pidgin/pidginaccounteditor.c
@@ -780,6 +780,8 @@
 	const gchar *svalue = NULL;
 	gboolean bvalue = FALSE;
 
+	purple_account_freeze_notify_settings(editor->account);
+
 	/* Set the alias. */
 	svalue = gtk_editable_get_text(GTK_EDITABLE(editor->alias));
 	if(*svalue == '\0') {
@@ -800,10 +802,14 @@
 	} else {
 		# warning set the global buddy icon when buddy icons do not suck so bad.
 	}
+
+	purple_account_thaw_notify_settings(editor->account);
 }
 
 static void
 pidgin_account_editor_save_advanced_options(PidginAccountEditor *editor) {
+	purple_account_freeze_notify_settings(editor->account);
+
 	for(GList *l = editor->advanced_entries; l != NULL; l = l->next) {
 		GtkWidget *widget = l->data;
 		PurpleAccountOption *option = NULL;
@@ -841,6 +847,8 @@
 				break;
 		}
 	}
+
+	purple_account_thaw_notify_settings(editor->account);
 }
 
 static void
diff --git a/pidgin/pidginaccountsdisabledmenu.c b/pidgin/pidginaccountsdisabledmenu.c
--- a/pidgin/pidginaccountsdisabledmenu.c
+++ b/pidgin/pidginaccountsdisabledmenu.c
@@ -64,6 +64,7 @@
 static void
 pidgin_accounts_disabled_menu_changed_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
                                          G_GNUC_UNUSED PurpleAccount *account,
+                                         G_GNUC_UNUSED GParamSpec *pspec,
                                          gpointer data)
 {
 	PidginAccountsDisabledMenu *menu = data;
diff --git a/pidgin/pidginaccountsenabledmenu.c b/pidgin/pidginaccountsenabledmenu.c
--- a/pidgin/pidginaccountsenabledmenu.c
+++ b/pidgin/pidginaccountsenabledmenu.c
@@ -43,6 +43,7 @@
 static void
 pidgin_accounts_enabled_menu_changed_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
                                         PurpleAccount *account,
+                                        G_GNUC_UNUSED GParamSpec *pspec,
                                         gpointer data)
 {
 	PidginAccountsEnabledMenu *menu = data;
