# HG changeset patch
# Node ID 29028611fdee
# Parent  9c0a6010fbc5
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -41,6 +41,7 @@
 	'purpleauthorizationrequest.c',
 	'purpleavatar.c',
 	'purplebuddypresence.c',
+	'purplechanneljoindetails.c',
 	'purplechatconversation.c',
 	'purplechatuser.c',
 	'purpleconnectionerrorinfo.c',
@@ -161,6 +162,7 @@
 	'purpleauthorizationrequest.h',
 	'purpleavatar.h',
 	'purplebuddypresence.h',
+	'purplechanneljoindetails.h',
 	'purplechatconversation.h',
 	'purplechatuser.h',
 	'purpleconnectionerrorinfo.h',
diff --git a/libpurple/purplechanneljoindetails.h b/libpurple/purplechanneljoindetails.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplechanneljoindetails.h
@@ -0,0 +1,170 @@
+/*
+ * Purple - Internet Messaging Library
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_CHANNEL_JOIN_DETAILS_H
+#define PURPLE_CHANNEL_JOIN_DETAILS_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_CHANNEL_JOIN_DETAILS (purple_channel_join_details_get_type())
+G_DECLARE_FINAL_TYPE(PurpleChannelJoinDetails, purple_channel_join_details, PURPLE, CHANNEL_JOIN_DETAILS, GObject)
+
+/**
+ * PurpleChannelJoinDetails:
+ *
+ * The details that are necessary for a [class@Protocol] to join a channel. A
+ * user interface will ask a protocol for a [class@ChannelJoinDetails] and then
+ * fill it out and pass it back to the protocol to actually join a channel.
+ *
+ * Since: 3.0.0
+ */
+
+/**
+ * purple_channel_join_details_new:
+ * @nickname_supported: Whether or not nicknames are supported.
+ * @password_supported: Whether or not passwords are supported.
+ *
+ * Creates a new [class@ChannelJoinDetails].
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleChannelJoinDetails *purple_channel_join_details_new(gboolean nickname_supported, gboolean password_supported);
+
+/**
+ * purple_channel_join_details_get_name:
+ * @details: The instance.
+ *
+ * Gets the name of the channel.
+ *
+ * Returns: (transfer none) (nullable): The name of the channel.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_channel_join_details_get_name(PurpleChannelJoinDetails *details);
+
+/**
+ * purple_channel_join_details_set_name:
+ * @details: The instance.
+ * @name: (nullable): The new name.
+ *
+ * Sets the name of the channel to @name.
+ *
+ * Since: 3.0.0
+ */
+void purple_channel_join_details_set_name(PurpleChannelJoinDetails *details, const char *name);
+
+/**
+ * purple_channel_join_details_get_nickname:
+ * @details: The instance.
+ *
+ * Gets the nickname of the user in the channel.
+ *
+ * Returns: (nullable): The user's channel specific nickname to use.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_channel_join_details_get_nickname(PurpleChannelJoinDetails *details);
+
+/**
+ * purple_channel_join_details_set_nickname:
+ * @details: The instance.
+ * @nickname: (nullable): The new nickname.
+ *
+ * Sets the channel specific nickname for the user.
+ *
+ * Since: 3.0.0
+ */
+void purple_channel_join_details_set_nickname(PurpleChannelJoinDetails *details, const char *nickname);
+
+/**
+ * purple_channel_join_details_get_nickname_supported:
+ * @details: The instance.
+ *
+ * Gets whether or not channel specific nicknames are supported.
+ *
+ * Returns: %TRUE if channel specific nicknames are supported otherwise %FALSE.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_channel_join_details_get_nickname_supported(PurpleChannelJoinDetails *details);
+
+/**
+ * purple_channel_join_details_get_password:
+ * @details: The instance.
+ *
+ * Gets the password the user specific to join this channel.
+ *
+ * Returns: (nullable): The password to join this channel.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_channel_join_details_get_password(PurpleChannelJoinDetails *details);
+
+/**
+ * purple_channel_join_details_set_password:
+ * @details: The instance.
+ * @password: (nullable): The new password.
+ *
+ * Sets the password to use to join this channel.
+ *
+ * Since: 3.0.0
+ */
+void purple_channel_join_details_set_password(PurpleChannelJoinDetails *details, const char *password);
+
+/**
+ * purple_channel_join_details_get_password_supported:
+ * @details: The instance.
+ *
+ * Gets whether or not the protocols supports channel passwords.
+ *
+ * Returns: %TRUE if channel passwords are supported, otherwise %FALSE.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_channel_join_details_get_password_supported(PurpleChannelJoinDetails *details);
+
+/**
+ * purple_channel_join_details_merge:
+ * @source: The instance to copy from.
+ * @destination: The instance to copy to.
+ *
+ * Copies the values of [property@PurpleChannelJoinDetails:name],
+ * [property@PurpleChannelJoinDetails:nickname], and
+ * [property@PurpleChannelJoinDetails:password] from @source to @destination.
+ *
+ * This function is intended to be used in the join channel dialog when the
+ * user switches accounts and the user interface needs to ask the protocol for
+ * it's default [class@ChannelJoinDetails] so that any input from the user can
+ * be maintained.
+ *
+ * Since: 3.0.0
+ */
+void purple_channel_join_details_merge(PurpleChannelJoinDetails *source, PurpleChannelJoinDetails *destination);
+
+G_END_DECLS
+
+#endif /* PURPLE_CHANNEL_JOIN_DETAILS_H */
diff --git a/libpurple/purplechanneljoindetails.c b/libpurple/purplechanneljoindetails.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplechanneljoindetails.c
@@ -0,0 +1,362 @@
+/*
+ * Purple - Internet Messaging Library
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purplechanneljoindetails.h"
+#include "util.h"
+
+enum {
+	PROP_0,
+	PROP_NAME,
+	PROP_NICKNAME,
+	PROP_NICKNAME_SUPPORTED,
+	PROP_PASSWORD,
+	PROP_PASSWORD_SUPPORTED,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+struct _PurpleChannelJoinDetails {
+	GObject parent;
+
+	char *name;
+	char *nickname;
+	gboolean nickname_supported;
+	char *password;
+	gboolean password_supported;
+};
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_channel_join_details_set_nickname_supported(PurpleChannelJoinDetails *details,
+                                                   gboolean nickname_supported)
+{
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	if(details->nickname_supported != nickname_supported) {
+		details->nickname_supported = nickname_supported;
+
+		g_object_notify_by_pspec(G_OBJECT(details),
+		                         properties[PROP_NICKNAME_SUPPORTED]);
+	}
+}
+
+static void
+purple_channel_join_details_set_password_supported(PurpleChannelJoinDetails *details,
+                                                   gboolean password_supported)
+{
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	if(details->password_supported != password_supported) {
+		details->password_supported = password_supported;
+
+		g_object_notify_by_pspec(G_OBJECT(details),
+		                         properties[PROP_PASSWORD_SUPPORTED]);
+	}
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(PurpleChannelJoinDetails, purple_channel_join_details,
+              G_TYPE_OBJECT)
+
+static void
+purple_channel_join_details_finalize(GObject *obj) {
+	PurpleChannelJoinDetails *details = PURPLE_CHANNEL_JOIN_DETAILS(obj);
+
+	g_clear_pointer(&details->name, g_free);
+	g_clear_pointer(&details->nickname, g_free);
+	g_clear_pointer(&details->password, g_free);
+
+	G_OBJECT_CLASS(purple_channel_join_details_parent_class)->finalize(obj);
+}
+
+static void
+purple_channel_join_details_get_property(GObject *obj, guint param_id,
+                                         GValue *value, GParamSpec *pspec)
+{
+	PurpleChannelJoinDetails *details = PURPLE_CHANNEL_JOIN_DETAILS(obj);
+
+	switch(param_id) {
+	case PROP_NAME:
+		g_value_set_string(value,
+		                   purple_channel_join_details_get_name(details));
+		break;
+	case PROP_NICKNAME:
+		g_value_set_string(value,
+		                   purple_channel_join_details_get_nickname(details));
+		break;
+	case PROP_NICKNAME_SUPPORTED:
+		g_value_set_boolean(value,
+		                    purple_channel_join_details_get_nickname_supported(details));
+		break;
+	case PROP_PASSWORD:
+		g_value_set_string(value,
+		                   purple_channel_join_details_get_password(details));
+		break;
+	case PROP_PASSWORD_SUPPORTED:
+		g_value_set_boolean(value,
+		                    purple_channel_join_details_get_password_supported(details));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+purple_channel_join_details_set_property(GObject *obj, guint param_id,
+                                         const GValue *value,
+                                         GParamSpec *pspec)
+{
+	PurpleChannelJoinDetails *details = PURPLE_CHANNEL_JOIN_DETAILS(obj);
+
+	switch(param_id) {
+	case PROP_NAME:
+		purple_channel_join_details_set_name(details,
+		                                     g_value_get_string(value));
+		break;
+	case PROP_NICKNAME:
+		purple_channel_join_details_set_nickname(details,
+		                                         g_value_get_string(value));
+		break;
+	case PROP_NICKNAME_SUPPORTED:
+		purple_channel_join_details_set_nickname_supported(details,
+		                                                   g_value_get_boolean(value));
+		break;
+	case PROP_PASSWORD:
+		purple_channel_join_details_set_password(details,
+		                                         g_value_get_string(value));
+		break;
+	case PROP_PASSWORD_SUPPORTED:
+		purple_channel_join_details_set_password_supported(details,
+		                                                   g_value_get_boolean(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+purple_channel_join_details_init(G_GNUC_UNUSED PurpleChannelJoinDetails *details)
+{
+}
+
+static void
+purple_channel_join_details_class_init(PurpleChannelJoinDetailsClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = purple_channel_join_details_finalize;
+	obj_class->get_property = purple_channel_join_details_get_property;
+	obj_class->set_property = purple_channel_join_details_set_property;
+
+	/**
+	 * PurpleChannelJoinDetails:name:
+	 *
+	 * The name of the channel to join.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NAME] = g_param_spec_string(
+		"name", "name",
+		"The name of the channel to join.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleChannelJoinDetails:nickname:
+	 *
+	 * The nickname of the user in the channel.
+	 *
+	 * Not all protocols support a channel specific username, so user
+	 * interfaces should check [property@ChannelJoinDetails:nickname-supported]
+	 * before displaying this option to the user.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NICKNAME] = g_param_spec_string(
+		"nickname", "nickname",
+		"The channel specific nickname for the user.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleChannelJoinDetails:nickname-supported:
+	 *
+	 * Whether or not the protocol supports channel specific nicknames.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NICKNAME_SUPPORTED] = g_param_spec_boolean(
+		"nickname-supported", "nickname-supported",
+		"Whether or not the protocol supports channel specific nicknames.",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleChannelJoinDetails:password:
+	 *
+	 * The password to use to join the channel.
+	 *
+	 * This is protocol specific so be sure to check
+	 * [property@PurpleChannelJoinDetails:password-supported] before displaying
+	 * this option to the user.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_PASSWORD] = g_param_spec_string(
+		"password", "password",
+		"The password to join this channel.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleChannelJoinDetails:password-supported:
+	 *
+	 * Whether or not the protocol supports channel passwords.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_PASSWORD_SUPPORTED] = g_param_spec_boolean(
+		"password-supported", "password-supported",
+		"Whether or not the protocol supports channel passwords.",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleChannelJoinDetails *
+purple_channel_join_details_new(gboolean nickname_supported,
+                                gboolean password_supported)
+{
+	return g_object_new(
+		PURPLE_TYPE_CHANNEL_JOIN_DETAILS,
+		"nickname-supported", nickname_supported,
+		"password-supported", password_supported,
+		NULL);
+}
+
+const char *
+purple_channel_join_details_get_name(PurpleChannelJoinDetails *details) {
+	g_return_val_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details), NULL);
+
+	return details->name;
+}
+
+void
+purple_channel_join_details_set_name(PurpleChannelJoinDetails *details,
+                                     const char *name)
+{
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	if(!purple_strequal(details->name, name)) {
+		g_free(details->name);
+		details->name = g_strdup(name);
+
+		g_object_notify_by_pspec(G_OBJECT(details), properties[PROP_NAME]);
+	}
+}
+
+const char *
+purple_channel_join_details_get_nickname(PurpleChannelJoinDetails *details) {
+	g_return_val_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details), NULL);
+
+	return details->nickname;
+}
+
+void
+purple_channel_join_details_set_nickname(PurpleChannelJoinDetails *details,
+                                         const char *nickname)
+{
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	if(!purple_strequal(details->nickname, nickname)) {
+		g_free(details->nickname);
+		details->nickname = g_strdup(nickname);
+
+		g_object_notify_by_pspec(G_OBJECT(details), properties[PROP_NICKNAME]);
+	}
+}
+
+gboolean
+purple_channel_join_details_get_nickname_supported(PurpleChannelJoinDetails *details) {
+	g_return_val_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details), FALSE);
+
+	return details->nickname_supported;
+}
+
+const char *
+purple_channel_join_details_get_password(PurpleChannelJoinDetails *details) {
+	g_return_val_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details), NULL);
+
+	return details->password;
+}
+
+void
+purple_channel_join_details_set_password(PurpleChannelJoinDetails *details,
+                                         const char *password)
+{
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	if(!purple_strequal(details->password, password)) {
+		g_free(details->password);
+		details->password = g_strdup(password);
+
+		g_object_notify_by_pspec(G_OBJECT(details), properties[PROP_PASSWORD]);
+	}
+}
+
+gboolean
+purple_channel_join_details_get_password_supported(PurpleChannelJoinDetails *details)
+{
+	g_return_val_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(details), FALSE);
+
+	return details->password_supported;
+}
+
+void
+purple_channel_join_details_merge(PurpleChannelJoinDetails *source,
+                                  PurpleChannelJoinDetails *destination)
+{
+	GObject *obj = NULL;
+	const char *value = NULL;
+
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(source));
+	g_return_if_fail(PURPLE_IS_CHANNEL_JOIN_DETAILS(destination));
+
+	obj = G_OBJECT(destination);
+	g_object_freeze_notify(obj);
+
+	value = purple_channel_join_details_get_name(source);
+	purple_channel_join_details_set_name(destination, value);
+
+	value = purple_channel_join_details_get_nickname(source);
+	purple_channel_join_details_set_nickname(destination, value);
+
+	value = purple_channel_join_details_get_password(source);
+	purple_channel_join_details_set_password(destination, value);
+
+	g_object_thaw_notify(obj);
+}
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -2,6 +2,7 @@
     'account_option',
     'account_manager',
     'authorization_request',
+    'channel_join_details',
     'circular_buffer',
     'contact',
     'contact_info',
diff --git a/libpurple/tests/test_channel_join_details.c b/libpurple/tests/test_channel_join_details.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_channel_join_details.c
@@ -0,0 +1,92 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_channel_join_details_new(void) {
+	PurpleChannelJoinDetails *details = NULL;
+
+	details = purple_channel_join_details_new(FALSE, FALSE);
+
+	g_assert_true(PURPLE_IS_CHANNEL_JOIN_DETAILS(details));
+
+	g_clear_object(&details);
+}
+
+static void
+test_purple_channel_join_details_properties(void) {
+	PurpleChannelJoinDetails *details;
+	char *name = NULL;
+	char *nickname = NULL;
+	char *password = NULL;
+	gboolean nickname_supported = FALSE;
+	gboolean password_supported = FALSE;
+
+	details = g_object_new(
+		PURPLE_TYPE_CHANNEL_JOIN_DETAILS,
+		"name", "name",
+		"nickname", "nickname",
+		"nickname-supported", TRUE,
+		"password", "hunter2",
+		"password-supported", TRUE,
+		NULL);
+
+	g_object_get(G_OBJECT(details),
+		"name", &name,
+		"nickname", &nickname,
+		"nickname-supported", &nickname_supported,
+		"password", &password,
+		"password-supported", &password_supported,
+		NULL);
+
+	g_assert_cmpstr(name, ==, "name");
+	g_clear_pointer(&name, g_free);
+
+	g_assert_cmpstr(nickname, ==, "nickname");
+	g_clear_pointer(&nickname, g_free);
+
+	g_assert_true(nickname_supported);
+
+	g_assert_cmpstr(password, ==, "hunter2");
+	g_clear_pointer(&password, g_free);
+
+	g_assert_true(password_supported);
+
+	g_clear_object(&details);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/channel-join-details/new",
+	                test_purple_channel_join_details_new);
+	g_test_add_func("/channel-join-details/properties",
+	                test_purple_channel_join_details_properties);
+
+	return g_test_run();
+}
