pidgin/pidgin

Create PidginAddBuddyDialog.

20 months ago, Gary Kramlich
565b31cc5238
Parents 2b1695327414
Children a1db3bb55aac
Create PidginAddBuddyDialog.

This is a GtkBuilder based version of the old hand built code. Nearly all
functionality. The only thing we dropped was the "auto personize" stuff as
that API is private to gtkblist.c.

Testing Done:
* Added a buddy with and without groups and aliases.
* Verified only accounts that allow adding buddies show up in the account list.
* Verified that hitting enter on the username entry will submit the ok response.
* Couldn't test invite message as no in tree prpls support them.

Reviewed at https://reviews.imfreedom.org/r/1574/
--- a/pidgin/gtkblist.c Fri Aug 12 01:32:07 2022 -0500
+++ b/pidgin/gtkblist.c Fri Aug 12 03:10:49 2022 -0500
@@ -40,6 +40,7 @@
#include "pidgin/pidginaccountfilterconnected.h"
#include "pidgin/pidginaccountstore.h"
#include "pidgin/pidginactiongroup.h"
+#include "pidgin/pidginaddbuddydialog.h"
#include "pidgin/pidgincellrendererexpander.h"
#include "pidgin/pidgincontactlistwindow.h"
#include "pidgin/pidgincore.h"
@@ -67,16 +68,6 @@
typedef struct
{
PidginBlistRequestData rq_data;
- GtkWidget *combo;
- GtkWidget *entry;
- GtkWidget *entry_for_alias;
- GtkWidget *entry_for_invite;
-
-} PidginAddBuddyData;
-
-typedef struct
-{
- PidginBlistRequestData rq_data;
gchar *default_chat_name;
GList *entries;
} PidginChatData;
@@ -997,20 +988,6 @@
}
static void
-set_sensitive_if_input_buddy_cb(GtkWidget *entry, gpointer user_data)
-{
- PurpleProtocol *protocol;
- PidginAddBuddyData *data = user_data;
- const char *text;
-
- protocol = purple_account_get_protocol(data->rq_data.account);
- text = gtk_entry_get_text(GTK_ENTRY(entry));
-
- gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
- GTK_RESPONSE_OK, purple_validate(protocol, text));
-}
-
-static void
pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
{
PidginBlistNode *ui_data = g_object_get_data(G_OBJECT(buddy), UI_DATA);
@@ -1021,15 +998,6 @@
}
static gboolean
-add_buddy_account_filter_func(PurpleAccount *account)
-{
- PurpleConnection *gc = purple_account_get_connection(account);
- PurpleProtocol *protocol = purple_connection_get_protocol(gc);
-
- return PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, add_buddy);
-}
-
-static gboolean
chat_account_filter_func(PurpleAccount *account)
{
PurpleConnection *gc = purple_account_get_connection(account);
@@ -3925,187 +3893,15 @@
}
static void
-add_buddy_select_account_cb(GObject *w, PidginAddBuddyData *data)
-{
- PidginAccountChooser *chooser = PIDGIN_ACCOUNT_CHOOSER(w);
- PurpleAccount *account = pidgin_account_chooser_get_selected(chooser);
- PurpleConnection *pc = NULL;
- PurpleProtocol *protocol = NULL;
- gboolean invite_enabled = TRUE;
-
- /* Save our account */
- data->rq_data.account = account;
-
- if (account)
- pc = purple_account_get_connection(account);
- if (pc)
- protocol = purple_connection_get_protocol(pc);
- if (protocol && !(purple_protocol_get_options(protocol) & OPT_PROTO_INVITE_MESSAGE))
- invite_enabled = FALSE;
-
- gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled);
- set_sensitive_if_input_buddy_cb(data->entry, data);
-}
-
-static void
-destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
-{
- g_free(data);
-}
-
-static void
-add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
-{
- const char *grp, *who, *whoalias, *invite;
- PurpleAccount *account;
- PurpleGroup *g;
- PurpleBuddy *b;
- PurpleConversation *im;
- PurpleBuddyIcon *icon;
-
- if (resp == GTK_RESPONSE_OK)
- {
- PurpleConversationManager *manager;
-
- who = gtk_entry_get_text(GTK_ENTRY(data->entry));
- grp = pidgin_text_combo_box_entry_get_text(data->combo);
- whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
- if (*whoalias == '\0')
- whoalias = NULL;
- invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite));
- if (*invite == '\0')
- invite = NULL;
-
- account = data->rq_data.account;
-
- g = NULL;
- if ((grp != NULL) && (*grp != '\0'))
- {
- if ((g = purple_blist_find_group(grp)) == NULL)
- {
- g = purple_group_new(grp);
- purple_blist_add_group(g, NULL);
- }
-
- b = purple_blist_find_buddy_in_group(account, who, g);
- }
- else if ((b = purple_blist_find_buddy(account, who)) != NULL)
- {
- g = purple_buddy_get_group(b);
- }
-
- if (b == NULL)
- {
- b = purple_buddy_new(account, who, whoalias);
- purple_blist_add_buddy(b, NULL, g, NULL);
- }
-
- purple_account_add_buddy(account, b, invite);
-
- /* Offer to merge people with the same alias. */
- if (whoalias != NULL && g != NULL)
- gtk_blist_auto_personize(PURPLE_BLIST_NODE(g), whoalias);
-
- /*
- * XXX
- * It really seems like it would be better if the call to
- * purple_account_add_buddy() and purple_conversation_update() were done in
- * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
- * purple_account_add_buddy() should be renamed to
- * purple_blist_add_new_buddy() or something, and have it call
- * purple_blist_add_buddy() after it creates it. --Mark
- *
- * No that's not good. blist.c should only deal with adding nodes to the
- * local list. We need a new, non-gtk file that calls both
- * purple_account_add_buddy and purple_blist_add_buddy().
- * Or something. --Mark
- */
-
- manager = purple_conversation_manager_get_default();
- im = purple_conversation_manager_find_im(manager, data->rq_data.account,
- who);
- if(PURPLE_IS_IM_CONVERSATION(im)) {
- icon = purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(im));
- if(icon != NULL) {
- purple_buddy_icon_update(icon);
- }
- }
- }
-
- gtk_widget_destroy(data->rq_data.window);
-}
-
-static void
pidgin_blist_request_add_buddy(PurpleBuddyList *list, PurpleAccount *account,
const char *username, const char *group,
const char *alias)
{
- PidginAccountChooser *chooser = NULL;
- PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
- PidginBlistRequestData *blist_req_data = &data->rq_data;
-
- if (account == NULL)
- account = purple_connection_get_account(purple_connections_get_all()->data);
-
- make_blist_request_dialog(blist_req_data,
- account,
- _("Add Buddy"), "add_buddy",
- _("Add a buddy.\n"),
- G_CALLBACK(add_buddy_select_account_cb), add_buddy_account_filter_func,
- G_CALLBACK(add_buddy_cb));
- gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
- _("Cancel"), GTK_RESPONSE_CANCEL,
- _("Add"), GTK_RESPONSE_OK,
- NULL);
- gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
- GTK_RESPONSE_OK);
-
- g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
- G_CALLBACK(destroy_add_buddy_dialog_cb), data);
-
- data->entry = gtk_entry_new();
-
- pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
- data->rq_data.sg, data->entry, TRUE, NULL);
- gtk_widget_grab_focus(data->entry);
-
- if (username != NULL)
- gtk_entry_set_text(GTK_ENTRY(data->entry), username);
- else
- gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
- GTK_RESPONSE_OK, FALSE);
-
- gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
-
- g_signal_connect(G_OBJECT(data->entry), "changed",
- G_CALLBACK(set_sensitive_if_input_buddy_cb),
- data);
-
- data->entry_for_alias = gtk_entry_new();
- pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
- data->rq_data.sg, data->entry_for_alias, TRUE,
- NULL);
-
- if (alias != NULL)
- gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
-
- if (username != NULL)
- gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
-
- data->entry_for_invite = gtk_entry_new();
- pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"),
- data->rq_data.sg, data->entry_for_invite, TRUE,
- NULL);
-
- data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
- pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
- data->rq_data.sg, data->combo, TRUE, NULL);
-
- gtk_widget_show_all(data->rq_data.window);
-
- /* Force update of invite message entry sensitivity */
- chooser = PIDGIN_ACCOUNT_CHOOSER(blist_req_data->account_menu);
- pidgin_account_chooser_set_selected(chooser, account);
+ GtkWidget *dialog = NULL;
+
+ dialog = pidgin_add_buddy_dialog_new(account, username, alias, NULL, group);
+
+ gtk_widget_show(dialog);
}
static void
--- a/pidgin/meson.build Fri Aug 12 01:32:07 2022 -0500
+++ b/pidgin/meson.build Fri Aug 12 03:10:49 2022 -0500
@@ -27,6 +27,7 @@
'pidginaccountsenabledmenu.c',
'pidginaccountstore.c',
'pidginactiongroup.c',
+ 'pidginaddbuddydialog.c',
'pidginapplication.c',
'pidginattachment.c',
'pidginavatar.c',
@@ -98,6 +99,7 @@
'pidginaccountsenabledmenu.h',
'pidginaccountstore.h',
'pidginactiongroup.h',
+ 'pidginaddbuddydialog.h',
'pidginapplication.h',
'pidginattachment.h',
'pidginavatar.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginaddbuddydialog.c Fri Aug 12 03:10:49 2022 -0500
@@ -0,0 +1,326 @@
+/*
+ * 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 "pidginaddbuddydialog.h"
+
+#include "gtkaccount.h"
+#include "pidginaccountchooser.h"
+#include "pidginaccountstore.h"
+#include "pidgincore.h"
+
+struct _PidginAddBuddyDialog {
+ GtkDialog parent;
+
+ GtkTreeModel *filter;
+
+ GtkWidget *account;
+ GtkWidget *username;
+ GtkWidget *alias;
+ GtkWidget *message;
+ GtkWidget *group;
+};
+
+G_DEFINE_TYPE(PidginAddBuddyDialog, pidgin_add_buddy_dialog, GTK_TYPE_DIALOG)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static gboolean
+pidgin_add_buddy_dialog_filter_accounts(GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer data)
+{
+ PurpleAccount *account = NULL;
+ PurpleProtocol *protocol = NULL;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail(GTK_IS_TREE_MODEL(model), FALSE);
+ g_return_val_if_fail(iter != NULL, FALSE);
+
+ gtk_tree_model_get(model, iter, PIDGIN_ACCOUNT_STORE_COLUMN_ACCOUNT,
+ &account, -1);
+
+ if(!PURPLE_IS_ACCOUNT(account)) {
+ return FALSE;
+ }
+
+ protocol = purple_account_get_protocol(account);
+ if(PURPLE_IS_PROTOCOL(protocol)) {
+ ret = PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, add_buddy);
+ }
+
+ g_object_unref(G_OBJECT(account));
+
+ return ret;
+}
+
+static void
+pidgin_add_buddy_dialog_validate(PidginAddBuddyDialog *dialog) {
+ PurpleAccount *account = NULL;
+ gboolean valid = FALSE;
+
+ account = pidgin_account_chooser_get_selected(PIDGIN_ACCOUNT_CHOOSER(dialog->account));
+ if(PURPLE_IS_ACCOUNT(account)) {
+ PurpleProtocol *protocol = NULL;
+
+ protocol = purple_account_get_protocol(account);
+ if(PURPLE_IS_PROTOCOL(protocol)) {
+ const gchar *username = gtk_entry_get_text(GTK_ENTRY(dialog->username));
+
+ valid = purple_validate(protocol, username);
+ }
+ }
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
+ valid);
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_add_buddy_dialog_response_cb(GtkDialog *dialog, gint response_id,
+ G_GNUC_UNUSED gpointer data)
+{
+ PidginAddBuddyDialog *abdialog = PIDGIN_ADD_BUDDY_DIALOG(dialog);
+
+ if(response_id == GTK_RESPONSE_OK) {
+ PurpleAccount *account = NULL;
+ PurpleBuddy *buddy = NULL;
+ PurpleConversation *im = NULL;
+ PurpleConversationManager *manager = NULL;
+ PurpleGroup *group = NULL;
+ const gchar *username = NULL, *alias = NULL, *message = NULL;
+ gchar *groupname = NULL;
+
+ /* Grab all of the values that the user entered. */
+ account = pidgin_account_chooser_get_selected(PIDGIN_ACCOUNT_CHOOSER(abdialog->account));
+ username = gtk_entry_get_text(GTK_ENTRY(abdialog->username));
+ alias = gtk_entry_get_text(GTK_ENTRY(abdialog->alias));
+ message = gtk_entry_get_text(GTK_ENTRY(abdialog->message));
+ groupname = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(abdialog->group));
+
+ /* Make anything that is an empty string NULL. */
+ if(alias != NULL && *alias == '\0') {
+ alias = NULL;
+ }
+
+ if(message != NULL && *message == '\0') {
+ message = NULL;
+ }
+
+ if(groupname != NULL && *groupname == '\0') {
+ g_clear_pointer(&groupname, g_free);
+ }
+
+ /* Find the PurpleGroup that the user requested. */
+ if(groupname != NULL) {
+ group = purple_blist_find_group(groupname);
+
+ if(group == NULL) {
+ /* Create the group if it's new. */
+ group = purple_group_new(groupname);
+ purple_blist_add_group(group, NULL);
+ } else {
+ /* Otherwise see if we already have a buddy in the existing
+ * group.
+ */
+ buddy = purple_blist_find_buddy_in_group(account, username,
+ group);
+ }
+ } else {
+ /* If a group name was not provided, look if the buddy exists, and
+ * use its group.
+ */
+ buddy = purple_blist_find_buddy(account, username);
+ if(PURPLE_IS_BUDDY(buddy)) {
+ group = purple_buddy_get_group(buddy);
+ }
+ }
+
+ /* If we don't a buddy yet, create it now. */
+ if(!PURPLE_IS_BUDDY(buddy)) {
+ buddy = purple_buddy_new(account, username, alias);
+ purple_blist_add_buddy(buddy, NULL, group, NULL);
+ }
+
+ /* Add the buddy to the account. */
+ purple_account_add_buddy(account, buddy, message);
+
+#if 0
+ /* This is disabled for now because gtk_blist_auto_personize is static
+ * and we're going to be changing the data structure of the contact list
+ * in the near future, so this probably completely change.
+ */
+
+ /* Offer to merge people with the same alias. */
+ if(alias != NULL && PURPLE_IS_GROUP(group)) {
+ gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), alias);
+ }
+#endif
+
+ /* Finally update the icon for any open im's with this person. */
+ manager = purple_conversation_manager_get_default();
+ im = purple_conversation_manager_find_im(manager, account, username);
+ if(PURPLE_IS_IM_CONVERSATION(im)) {
+ PurpleBuddyIcon *icon = NULL;
+
+ icon = purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(im));
+ if(icon != NULL) {
+ purple_buddy_icon_update(icon);
+ }
+ }
+
+ g_free(groupname);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(abdialog));
+}
+
+static void
+pidgin_add_buddy_dialog_account_changed_cb(GtkComboBox *widget, gpointer data) {
+ PidginAddBuddyDialog *dialog = data;
+ PurpleAccount *account = NULL;
+ gboolean message_sensitive = TRUE;
+
+ account = pidgin_account_chooser_get_selected(PIDGIN_ACCOUNT_CHOOSER(widget));
+
+ if(PURPLE_IS_ACCOUNT(account)) {
+ PurpleProtocol *protocol = purple_account_get_protocol(account);
+
+ if(PURPLE_IS_PROTOCOL(protocol)) {
+ PurpleProtocolOptions opts = purple_protocol_get_options(protocol);
+
+ message_sensitive = (opts & OPT_PROTO_INVITE_MESSAGE);
+ }
+ }
+
+ gtk_widget_set_sensitive(dialog->message, message_sensitive);
+
+ pidgin_add_buddy_dialog_validate(dialog);
+}
+
+static void
+pidgin_add_buddy_dialog_username_changed_cb(G_GNUC_UNUSED GtkEditable *editable,
+ gpointer data)
+{
+ pidgin_add_buddy_dialog_validate(data);
+}
+
+static void
+pidgin_add_buddy_dialog_group_cb(PurpleBlistNode *node, gpointer data) {
+ PidginAddBuddyDialog *dialog = data;
+ PurpleGroup *group = PURPLE_GROUP(node);
+
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(dialog->group),
+ purple_group_get_name(group));
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_add_buddy_dialog_init(PidginAddBuddyDialog *dialog) {
+ gtk_widget_init_template(GTK_WIDGET(dialog));
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(dialog->filter),
+ pidgin_add_buddy_dialog_filter_accounts,
+ NULL, NULL);
+ gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(dialog->filter));
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dialog->account), 0);
+
+ purple_blist_walk(pidgin_add_buddy_dialog_group_cb, NULL, NULL, NULL,
+ dialog);
+}
+
+static void
+pidgin_add_buddy_dialog_class_init(PidginAddBuddyDialogClass *klass) {
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ gtk_widget_class_set_template_from_resource(
+ widget_class,
+ "/im/pidgin/Pidgin3/Dialogs/addbuddy.ui"
+ );
+
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ filter);
+
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ account);
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ username);
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ alias);
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ message);
+ gtk_widget_class_bind_template_child(widget_class, PidginAddBuddyDialog,
+ group);
+
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_add_buddy_dialog_response_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_add_buddy_dialog_account_changed_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_add_buddy_dialog_username_changed_cb);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+pidgin_add_buddy_dialog_new(PurpleAccount *account, const gchar *username,
+ const gchar *alias, const gchar *message,
+ const gchar *group)
+{
+ GtkWidget *dialog = g_object_new(PIDGIN_TYPE_ADD_BUDDY_DIALOG, NULL);
+ PidginAddBuddyDialog *abdialog = PIDGIN_ADD_BUDDY_DIALOG(dialog);
+
+ if(PURPLE_IS_ACCOUNT(account)) {
+ pidgin_account_chooser_set_selected(PIDGIN_ACCOUNT_CHOOSER(abdialog->account),
+ account);
+ }
+
+ if(username != NULL) {
+ gtk_entry_set_text(GTK_ENTRY(abdialog->username), username);
+ }
+
+ if(alias != NULL) {
+ gtk_entry_set_text(GTK_ENTRY(abdialog->alias), alias);
+ }
+
+ if(message != NULL) {
+ gtk_entry_set_text(GTK_ENTRY(abdialog->message), message);
+ }
+
+ if(group != NULL) {
+ GtkWidget *entry = NULL;
+
+ entry = gtk_bin_get_child(GTK_BIN(abdialog->group));
+ gtk_entry_set_text(GTK_ENTRY(entry), group);
+ }
+
+ return dialog;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginaddbuddydialog.h Fri Aug 12 03:10:49 2022 -0500
@@ -0,0 +1,66 @@
+/*
+ * 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_ADD_BUDDY_DIALOG_H
+#define PIDGIN_ADD_BUDDY_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginAddBuddyDialog:
+ *
+ * A dialog for adding buddies to your contact list.
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_ADD_BUDDY_DIALOG (pidgin_add_buddy_dialog_get_type())
+G_DECLARE_FINAL_TYPE(PidginAddBuddyDialog, pidgin_add_buddy_dialog, PIDGIN,
+ ADD_BUDDY_DIALOG, GtkDialog)
+
+/**
+ * pidgin_add_buddy_dialog_new:
+ * @account: (nullable): The [class@Purple.Account] to pre-select.
+ * @username: (nullable): The username to pre-fill.
+ * @alias: (nullable): The alias to pre-fill.
+ * @message: (nullable): The invite message to pre-fill.
+ * @group: (nullable): The group anem to pre-fill.
+ *
+ * Creates a add buddy dialog with the pre-filled optional values.
+ *
+ * Returns: (transfer full): The widget.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_add_buddy_dialog_new(PurpleAccount *account, const gchar *username, const gchar *alias, const gchar *message, const gchar *group);
+
+G_END_DECLS
+
+#endif /* PIDGIN_ADD_BUDDY_DIALOG_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/resources/Dialogs/addbuddy.ui Fri Aug 12 03:10:49 2022 -0500
@@ -0,0 +1,350 @@
+<?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"/>
+ <requires lib="pidgin" version="3.0"/>
+ <!-- interface-license-type gplv2 -->
+ <!-- interface-name Pidgin -->
+ <!-- interface-description Internet Messenger -->
+ <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+ <object class="PidginAccountStore" id="account_store"/>
+ <object class="PidginAccountFilterConnected" id="account_filter_connected">
+ <property name="child-model">account_store</property>
+ </object>
+ <object class="GtkTreeModelFilter" id="filter">
+ <property name="child-model">account_filter_connected</property>
+ </object>
+ <template class="PidginAddBuddyDialog" parent="GtkDialog">
+ <property name="can-focus">False</property>
+ <property name="border-width">12</property>
+ <property name="title" translatable="yes">Add Buddy</property>
+ <property name="resizable">False</property>
+ <property name="type-hint">dialog</property>
+ <signal name="response" handler="pidgin_add_buddy_dialog_response_cb" swapped="no"/>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can-focus">False</property>
+ <property name="layout-style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can-focus">True</property>
+ <property name="can-default">True</property>
+ <property name="receives-default">True</property>
+ <property name="use-underline">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">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Add a buddy.</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="GtkLabel" id="spacer">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ </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>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">A_ccount:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">account</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="PidginAccountChooser" id="account">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="model">filter</property>
+ <signal name="changed" handler="pidgin_add_buddy_dialog_account_changed_cb" object="PidginAddBuddyDialog" 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">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Buddy's _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>
+ <property name="has-focus">True</property>
+ <property name="can-default">True</property>
+ <property name="activates-default">True</property>
+ <signal name="changed" handler="pidgin_add_buddy_dialog_username_changed_cb" object="PidginAddBuddyDialog" 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">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">(Optional) A_lias:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">alias</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="alias">
+ <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">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">(Optional) _Invite message:</property>
+ <property name="use-underline">True</property>
+ <property name="mnemonic-widget">message</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="message">
+ <property name="visible">True</property>
+ <property name="sensitive">False</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">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Add buddy to _group:</property>
+ <property name="use-underline">True</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="GtkComboBoxText" id="group">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="has-entry">True</property>
+ <child internal-child="entry">
+ <object class="GtkEntry">
+ <property name="can-focus">True</property>
+ </object>
+ </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">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button1</action-widget>
+ <action-widget response="-5">button2</action-widget>
+ </action-widgets>
+ </template>
+ <object class="GtkSizeGroup">
+ <widgets>
+ <widget name="label1"/>
+ <widget name="label2"/>
+ <widget name="label3"/>
+ <widget name="label4"/>
+ <widget name="label5"/>
+ </widgets>
+ </object>
+</interface>
--- a/pidgin/resources/pidgin.gresource.xml Fri Aug 12 01:32:07 2022 -0500
+++ b/pidgin/resources/pidgin.gresource.xml Fri Aug 12 03:10:49 2022 -0500
@@ -19,6 +19,7 @@
<file compressed="true">Debug/debug.ui</file>
<file compressed="true">Debug/filter.css</file>
<file compressed="true">Debug/plugininfo.ui</file>
+ <file compressed="true">Dialogs/addbuddy.ui</file>
<file compressed="true">Keypad/keypad.ui</file>
<file compressed="true">Log/log-viewer.ui</file>
<file compressed="true">Media/window.ui</file>
--- a/po/POTFILES.in Fri Aug 12 01:32:07 2022 -0500
+++ b/po/POTFILES.in Fri Aug 12 03:10:49 2022 -0500
@@ -343,6 +343,7 @@
pidgin/pidginaccountsenabledmenu.c
pidgin/pidginaccountstore.c
pidgin/pidginactiongroup.c
+pidgin/pidginaddbuddydialog.c
pidgin/pidginapplication.c
pidgin/pidginattachment.c
pidgin/pidginavatar.c
@@ -408,6 +409,7 @@
pidgin/resources/Conversations/window.ui
pidgin/resources/Debug/debug.ui
pidgin/resources/Debug/plugininfo.ui
+pidgin/resources/Dialogs/addbuddy.ui
pidgin/resources/Keypad/keypad.ui
pidgin/resources/Log/log-viewer.ui
pidgin/resources/Media/window.ui