pidgin/gtkrequest.c

Mon, 23 May 2022 22:05:35 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 23 May 2022 22:05:35 -0500
branch
gtk4
changeset 41547
a6a75a9d8a09
parent 41531
2b86501d13fe
child 41567
517ac516af27
permissions
-rw-r--r--

Update the GTK request implementation for GTK4

I had to `#if 0` a fair amount of code as it needs rewriting and there's no point in doing that now when we can't test the rewrite.

Testing Done:
Compiled

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

/* pidgin
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include <glib/gi18n-lib.h>

#include <talkatu.h>

#include <purple.h>

#include "gtkrequest.h"
#include "gtkblist.h"
#include "gtkutils.h"
#include "pidginaccountchooser.h"
#include "pidginaccountfilterconnected.h"
#include "pidginaccountstore.h"
#include "pidgincore.h"
#include "pidgindialog.h"

#include <gdk/gdkkeysyms.h>

typedef struct
{
	PurpleRequestType type;

	void *user_data;
	/* May be GtkWidget or GtkNativeDialog */
	gpointer dialog;

	GtkWidget *ok_button;

	size_t cb_count;
	GCallback *cbs;

	union
	{
		struct
		{
			GtkProgressBar *progress_bar;
		} wait;

		struct
		{
			GtkWidget *entry;

			gboolean multiline;
			gchar *hint;

		} input;

		struct
		{
			PurpleRequestFields *fields;

		} multifield;

		struct
		{
			gboolean savedialog;
			gchar *name;

		} file;

	} u;

} PidginRequestData;

static GHashTable *datasheet_stock = NULL;

static GtkWidget * create_account_field(PurpleRequestField *field);

static void
pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
{
	GtkWidget *image;
	GdkPixbuf *pixbuf;

	if(!PURPLE_IS_ACCOUNT(account)) {
		return;
	}

	pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
	if(!GDK_IS_PIXBUF(pixbuf)) {
		return;
	}

	image = gtk_image_new_from_pixbuf(pixbuf);
	g_object_unref(G_OBJECT(pixbuf));

	gtk_widget_set_tooltip_text(image, purple_account_get_username(account));

	if (GTK_IS_BOX(cont)) {
		gtk_widget_set_halign(image, GTK_ALIGN_START);
		gtk_widget_set_valign(image, GTK_ALIGN_START);
		gtk_box_append(GTK_BOX(cont), image);
	}
}

static void
generic_response_start(PidginRequestData *data)
{
	g_return_if_fail(data != NULL);

	g_object_set_data(G_OBJECT(data->dialog),
		"pidgin-window-is-closing", GINT_TO_POINTER(TRUE));
	gtk_widget_set_visible(GTK_WIDGET(data->dialog), FALSE);
}

static void
input_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
{
	const char *value;
	char *multiline_value = NULL;

	generic_response_start(data);

	if (data->u.input.multiline || purple_strequal(data->u.input.hint, "html")) {
		GtkTextBuffer *buffer =
			gtk_text_view_get_buffer(GTK_TEXT_VIEW(data->u.input.entry));

		if (purple_strequal(data->u.input.hint, "html")) {
			multiline_value = talkatu_markup_get_html(buffer, NULL);
		} else {
			GtkTextIter start_iter, end_iter;

			gtk_text_buffer_get_start_iter(buffer, &start_iter);
			gtk_text_buffer_get_end_iter(buffer, &end_iter);

			multiline_value = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter,
										 FALSE);
		}

		value = multiline_value;
	}
	else {
		value = gtk_editable_get_text(GTK_EDITABLE(data->u.input.entry));
	}

	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
		((PurpleRequestInputCb)data->cbs[id])(data->user_data, value);
	else if (data->cbs[1] != NULL)
		((PurpleRequestInputCb)data->cbs[1])(data->user_data, value);

	if (data->u.input.multiline) {
		g_free(multiline_value);
	}

	purple_request_close(PURPLE_REQUEST_INPUT, data);
}

static void
action_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
{
	generic_response_start(data);

	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
		((PurpleRequestActionCb)data->cbs[id])(data->user_data, id);

	purple_request_close(PURPLE_REQUEST_INPUT, data);
}


static void
choice_response_cb(GtkDialog *dialog, gint id, PidginRequestData *data)
{
#warning please rewrite me
#if 0
	GtkWidget *radio = g_object_get_data(G_OBJECT(dialog), "radio");
	GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));

	generic_response_start(data);

	if (id >= 0 && (gsize)id < data->cb_count && data->cbs[id] != NULL)
		while (group) {
			if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) {
				((PurpleRequestChoiceCb)data->cbs[id])(data->user_data, g_object_get_data(G_OBJECT(group->data), "choice_value"));
				break;
			}
			group = group->next;
		}
	purple_request_close(PURPLE_REQUEST_INPUT, data);
#endif
}

static gboolean
field_string_focus_out_cb(GtkEventControllerFocus *controller,
                          PurpleRequestField *field)
{
	GtkWidget *entry = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(controller));
	const char *value;

	if (purple_request_field_string_is_multiline(field))
	{
		GtkTextBuffer *buffer;
		GtkTextIter start_iter, end_iter;

		buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));

		gtk_text_buffer_get_start_iter(buffer, &start_iter);
		gtk_text_buffer_get_end_iter(buffer, &end_iter);

		value = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
	}
	else
		value = gtk_editable_get_text(GTK_EDITABLE(entry));

	purple_request_field_string_set_value(field,
			(*value == '\0' ? NULL : value));

	return FALSE;
}

static void
field_bool_cb(GtkToggleButton *button, PurpleRequestField *field)
{
	purple_request_field_bool_set_value(field,
			gtk_toggle_button_get_active(button));
}

static void
field_choice_menu_cb(GtkComboBox *menu, PurpleRequestField *field)
{
	int active = gtk_combo_box_get_active(menu);
	gpointer *values = g_object_get_data(G_OBJECT(menu), "values");

	g_return_if_fail(values != NULL);
	g_return_if_fail(active >= 0);

	purple_request_field_choice_set_value(field, values[active]);
}

static void
field_choice_option_cb(GtkCheckButton *button, PurpleRequestField *field)
{
#warning please rewrite me
#if 0
	int active;
	gpointer *values = g_object_get_data(G_OBJECT(g_object_get_data(
		G_OBJECT(button), "box")), "values");

	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
		return;

	active = (g_slist_length(gtk_radio_button_get_group(button)) -
		g_slist_index(gtk_radio_button_get_group(button), button)) - 1;

	g_return_if_fail(values != NULL);
	g_return_if_fail(active >= 0);

	purple_request_field_choice_set_value(field, values[active]);
#endif
}

static void
field_account_cb(GObject *w, PurpleRequestField *field)
{
	PidginAccountChooser *chooser = PIDGIN_ACCOUNT_CHOOSER(w);

	purple_request_field_account_set_value(
	        field, pidgin_account_chooser_get_selected(chooser));
}

static void
multifield_ok_cb(GtkWidget *button, PidginRequestData *data)
{
	generic_response_start(data);

	if (!gtk_widget_has_focus(button))
		gtk_widget_grab_focus(button);

	if (data->cbs[0] != NULL)
		((PurpleRequestFieldsCb)data->cbs[0])(data->user_data,
											data->u.multifield.fields);

	purple_request_close(PURPLE_REQUEST_FIELDS, data);
}

static void
multifield_cancel_cb(GtkWidget *button, PidginRequestData *data)
{
	generic_response_start(data);

	if (data->cbs[1] != NULL)
		((PurpleRequestFieldsCb)data->cbs[1])(data->user_data,
											data->u.multifield.fields);

	purple_request_close(PURPLE_REQUEST_FIELDS, data);
}

static void
multifield_extra_cb(GtkWidget *button, PidginRequestData *data)
{
	PurpleRequestFieldsCb cb;

	generic_response_start(data);

	cb = g_object_get_data(G_OBJECT(button), "extra-cb");

	if (cb != NULL)
		cb(data->user_data, data->u.multifield.fields);

	purple_request_close(PURPLE_REQUEST_FIELDS, data);
}

static gboolean
destroy_multifield_cb(GtkWidget *dialog, GdkEvent *event,
					  PidginRequestData *data)
{
	multifield_cancel_cb(NULL, data);
	return FALSE;
}

static gchar *
pidgin_request_escape(PurpleRequestCommonParameters *cpar, const gchar *text)
{
	if (text == NULL)
		return NULL;

	if (purple_request_cpar_is_html(cpar)) {
		gboolean valid;

		valid = pango_parse_markup(text, -1, 0, NULL, NULL, NULL, NULL);

		if (valid)
			return g_strdup(text);
		else {
			purple_debug_error("pidgin", "Passed label text is not "
				"a valid markup. Falling back to plain text.");
		}
	}

	return g_markup_escape_text(text, -1);
}

static GtkWidget *
pidgin_request_dialog_icon(PurpleRequestType dialog_type,
	PurpleRequestCommonParameters *cpar)
{
	GtkWidget *img = NULL;
	PurpleRequestIconType icon_type;
	gconstpointer icon_data;
	gsize icon_size;
	const gchar *icon_name = "dialog-question";

	/* Dialog icon. */
	icon_data = purple_request_cpar_get_custom_icon(cpar, &icon_size);
	if (icon_data) {
		GdkPixbuf *pixbuf;

		pixbuf = purple_gdk_pixbuf_from_data(icon_data, icon_size);
		if (pixbuf) {
			/* scale the image if it is too large */
			int width = gdk_pixbuf_get_width(pixbuf);
			int height = gdk_pixbuf_get_height(pixbuf);
			if (width > 128 || height > 128) {
				int scaled_width = width > height ?
					128 : (128 * width) / height;
				int scaled_height = height > width ?
					128 : (128 * height) / width;
				GdkPixbuf *scaled;

				purple_debug_info("pidgin", "dialog icon was "
					"too large, scaling it down");

				scaled = gdk_pixbuf_scale_simple(pixbuf,
					scaled_width, scaled_height,
					GDK_INTERP_BILINEAR);
				if (scaled) {
					g_object_unref(pixbuf);
					pixbuf = scaled;
				}
			}
			img = gtk_image_new_from_pixbuf(pixbuf);
			g_object_unref(pixbuf);
		} else {
			purple_debug_info("pidgin",
				"failed to parse dialog icon");
		}
	}

	if (img)
		return img;

	icon_type = purple_request_cpar_get_icon(cpar);
	switch (icon_type)
	{
		case PURPLE_REQUEST_ICON_DEFAULT:
			icon_name = NULL;
			break;
		case PURPLE_REQUEST_ICON_REQUEST:
			icon_name = "dialog-question";
			break;
		case PURPLE_REQUEST_ICON_DIALOG:
		case PURPLE_REQUEST_ICON_INFO:
		case PURPLE_REQUEST_ICON_WAIT: /* TODO: we need another icon */
			icon_name = "dialog-information";
			break;
		case PURPLE_REQUEST_ICON_WARNING:
			icon_name = "dialog-warning";
			break;
		case PURPLE_REQUEST_ICON_ERROR:
			icon_name = "dialog-error";
			break;
		/* intentionally no default value */
	}

	if (icon_name == NULL) {
		switch (dialog_type) {
			case PURPLE_REQUEST_INPUT:
			case PURPLE_REQUEST_CHOICE:
			case PURPLE_REQUEST_ACTION:
			case PURPLE_REQUEST_FIELDS:
			case PURPLE_REQUEST_FILE:
			case PURPLE_REQUEST_FOLDER:
				icon_name = "dialog-question";
				break;
			case PURPLE_REQUEST_WAIT:
				icon_name = "dialog-information";
				break;
			/* intentionally no default value */
		}
	}

	if(icon_name == NULL) {
		icon_name = "dialog-question";
	}

	img = gtk_image_new_from_icon_name(icon_name);
	gtk_image_set_icon_size(GTK_IMAGE(img), GTK_ICON_SIZE_LARGE);

	return img;
}

static void
pidgin_request_help_clicked(GtkButton *button, gpointer _unused)
{
	PurpleRequestHelpCb cb;
	gpointer data;

	cb = g_object_get_data(G_OBJECT(button), "pidgin-help-cb");
	data = g_object_get_data(G_OBJECT(button), "pidgin-help-data");

	g_return_if_fail(cb != NULL);
	cb(data);
}

static void
pidgin_request_add_help(GtkDialog *dialog, PurpleRequestCommonParameters *cpar)
{
	GtkWidget *button;
	PurpleRequestHelpCb help_cb;
	gpointer help_data;

	help_cb = purple_request_cpar_get_help_cb(cpar, &help_data);
	if (help_cb == NULL)
		return;

	button = gtk_dialog_add_button(dialog, _("_Help"), GTK_RESPONSE_HELP);

	g_object_set_data(G_OBJECT(button), "pidgin-help-cb", help_cb);
	g_object_set_data(G_OBJECT(button), "pidgin-help-data", help_data);

	g_signal_connect(G_OBJECT(button), "clicked",
		G_CALLBACK(pidgin_request_help_clicked), NULL);
}

static void *
pidgin_request_input(const char *title, const char *primary,
					   const char *secondary, const char *default_value,
					   gboolean multiline, gboolean masked, gchar *hint,
					   const char *ok_text, GCallback ok_cb,
					   const char *cancel_text, GCallback cancel_cb,
					   PurpleRequestCommonParameters *cpar,
					   void *user_data)
{
	PidginRequestData *data;
	GtkWidget *dialog;
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkLabel *label;
	GtkWidget *img;
	GtkWidget *content;
	char *label_text;
	char *primary_esc, *secondary_esc;

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_INPUT;
	data->user_data = user_data;

	data->cb_count = 2;
	data->cbs = g_new0(GCallback, 2);

	data->cbs[0] = ok_cb;
	data->cbs[1] = cancel_cb;

	/* Create the dialog. */
	dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
					     NULL, 0,
					     cancel_text, 1,
					     ok_text,     0,
					     NULL);
	data->dialog = dialog;

	g_signal_connect(G_OBJECT(dialog), "response",
					 G_CALLBACK(input_response_cb), data);

	/* Setup the dialog */
	gtk_widget_set_margin_top(dialog, 6);
	gtk_widget_set_margin_bottom(dialog, 6);
	gtk_widget_set_margin_start(dialog, 6);
	gtk_widget_set_margin_end(dialog, 6);

	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
	gtk_widget_set_margin_top(content, 6);
	gtk_widget_set_margin_bottom(content, 6);
	gtk_widget_set_margin_start(content, 6);
	gtk_widget_set_margin_end(content, 6);

	if (!multiline)
		gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
	gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0);
	gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
	                    12);

	/* Setup the main horizontal box */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_append(GTK_BOX(content), hbox);

	/* Dialog icon. */
	img = pidgin_request_dialog_icon(PURPLE_REQUEST_INPUT, cpar);
	gtk_widget_set_halign(img, GTK_ALIGN_START);
	gtk_widget_set_valign(img, GTK_ALIGN_START);
	gtk_box_append(GTK_BOX(hbox), img);

	pidgin_request_add_help(GTK_DIALOG(dialog), cpar);

	/* Vertical box */
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_widget_set_halign(vbox, GTK_ALIGN_FILL);
	gtk_widget_set_hexpand(vbox, TRUE);
	gtk_box_append(GTK_BOX(hbox), vbox);

	pidgin_widget_decorate_account(hbox, purple_request_cpar_get_account(cpar));

	/* Descriptive label */
	primary_esc = pidgin_request_escape(cpar, primary);
	secondary_esc = pidgin_request_escape(cpar, secondary);
	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
								 "%s</span>%s%s" : "%s%s%s"),
								 (primary ? primary_esc : ""),
								 ((primary && secondary) ? "\n\n" : ""),
								 (secondary ? secondary_esc : ""));
	g_free(primary_esc);
	g_free(secondary_esc);

	label = GTK_LABEL(gtk_label_new(NULL));

	gtk_label_set_markup(label, label_text);
	gtk_label_set_wrap(label, TRUE);
	gtk_label_set_xalign(label, 0);
	gtk_label_set_yalign(label, 0);
	gtk_box_append(GTK_BOX(vbox), GTK_WIDGET(label));

	g_free(label_text);

	/* Entry field. */
	data->u.input.multiline = multiline;
	data->u.input.hint = g_strdup(hint);

	if(multiline || purple_strequal(data->u.input.hint, "html")) {
		GtkWidget *editor = talkatu_editor_new();
		GtkWidget *input = talkatu_editor_get_input(TALKATU_EDITOR(editor));
		GtkTextBuffer *buffer = NULL;

		gtk_widget_set_size_request(input, 320, 130);
		gtk_widget_set_name(input, "pidgin_request_input");
		gtk_widget_set_valign(editor, GTK_ALIGN_FILL);
		gtk_widget_set_vexpand(editor, TRUE);
		gtk_box_append(GTK_BOX(vbox), editor);

		if (purple_strequal(data->u.input.hint, "html")) {
			buffer = talkatu_html_buffer_new();

			if(default_value != NULL) {
				talkatu_markup_set_html(TALKATU_BUFFER(buffer), default_value, -1);
			}
		} else {
			buffer = gtk_text_buffer_new(NULL);

			if(default_value != NULL) {
				gtk_text_buffer_set_text(buffer, default_value, -1);
			}
		}

		gtk_text_view_set_buffer(GTK_TEXT_VIEW(input), buffer);

		data->u.input.entry = input;
	} else {
		GtkWidget *entry = gtk_entry_new();

		gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
		gtk_box_append(GTK_BOX(vbox), entry);

		if(default_value != NULL) {
			gtk_editable_set_text(GTK_EDITABLE(entry), default_value);
		}

		if(masked) {
			gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
		}

		data->u.input.entry = entry;
	}

	pidgin_set_accessible_label(data->u.input.entry, label);

	pidgin_auto_parent_window(dialog);

	/* Show everything. */
	gtk_widget_show(dialog);

	return data;
}

static void *
pidgin_request_choice(const char *title, const char *primary,
	const char *secondary, gpointer default_value, const char *ok_text,
	GCallback ok_cb, const char *cancel_text, GCallback cancel_cb,
	PurpleRequestCommonParameters *cpar, void *user_data, va_list args)
{
	PidginRequestData *data;
	GtkWidget *dialog;
	GtkWidget *vbox, *vbox2;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *img;
	GtkWidget *first_radio = NULL;
	GtkWidget *content;
	char *label_text;
	char *radio_text;
	char *primary_esc, *secondary_esc;

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_ACTION;
	data->user_data = user_data;

	data->cb_count = 2;
	data->cbs = g_new0(GCallback, 2);
	data->cbs[0] = cancel_cb;
	data->cbs[1] = ok_cb;

	/* Create the dialog. */
	data->dialog = dialog = gtk_dialog_new();

	if (title != NULL)
		gtk_window_set_title(GTK_WINDOW(dialog), title);
#ifdef _WIN32
		gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
#endif

	gtk_dialog_add_button(GTK_DIALOG(dialog), cancel_text, 0);
	gtk_dialog_add_button(GTK_DIALOG(dialog), ok_text, 1);

	g_signal_connect(G_OBJECT(dialog), "response",
			 G_CALLBACK(choice_response_cb), data);

	/* Setup the dialog */
	gtk_widget_set_margin_top(dialog, 6);
	gtk_widget_set_margin_bottom(dialog, 6);
	gtk_widget_set_margin_start(dialog, 6);
	gtk_widget_set_margin_end(dialog, 6);
	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
	gtk_widget_set_margin_top(content, 6);
	gtk_widget_set_margin_bottom(content, 6);
	gtk_widget_set_margin_start(content, 6);
	gtk_widget_set_margin_end(content, 6);
	gtk_box_set_spacing(GTK_BOX(content), 12);

	/* Setup the main horizontal box */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_append(GTK_BOX(content), hbox);

	/* Dialog icon. */
	img = pidgin_request_dialog_icon(PURPLE_REQUEST_CHOICE, cpar);
	gtk_widget_set_halign(img, GTK_ALIGN_START);
	gtk_widget_set_valign(img, GTK_ALIGN_START);
	gtk_box_append(GTK_BOX(hbox), img);

	pidgin_widget_decorate_account(hbox, purple_request_cpar_get_account(cpar));

	pidgin_request_add_help(GTK_DIALOG(dialog), cpar);

	/* Vertical box */
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_box_append(GTK_BOX(hbox), vbox);

	/* Descriptive label */
	primary_esc = pidgin_request_escape(cpar, primary);
	secondary_esc = pidgin_request_escape(cpar, secondary);
	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
				      "%s</span>%s%s" : "%s%s%s"),
				     (primary ? primary_esc : ""),
				     ((primary && secondary) ? "\n\n" : ""),
				     (secondary ? secondary_esc : ""));
	g_free(primary_esc);
	g_free(secondary_esc);

	label = gtk_label_new(NULL);

	gtk_label_set_markup(GTK_LABEL(label), label_text);
	gtk_label_set_wrap(GTK_LABEL(label), TRUE);
	gtk_label_set_xalign(GTK_LABEL(label), 0);
	gtk_label_set_yalign(GTK_LABEL(label), 0);
	gtk_widget_set_valign(label, GTK_ALIGN_FILL);
	gtk_widget_set_vexpand(label, TRUE);
	gtk_box_append(GTK_BOX(vbox), label);

	g_free(label_text);

	vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
	gtk_box_append(GTK_BOX(vbox), vbox2);
	while ((radio_text = va_arg(args, char*))) {
		GtkWidget *radio = NULL;
		gpointer resp = va_arg(args, gpointer);

		radio = gtk_check_button_new_with_label(radio_text);

		if(first_radio == NULL) {
			first_radio = radio;
		} else {
			gtk_check_button_set_group(GTK_CHECK_BUTTON(radio),
			                           GTK_CHECK_BUTTON(first_radio));
		}

		gtk_box_append(GTK_BOX(vbox2), radio);

		g_object_set_data(G_OBJECT(radio), "choice_value", resp);
		if (resp == default_value)
		   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
	}

	g_object_set_data(G_OBJECT(dialog), "radio", first_radio);

	/* Show everything. */
	pidgin_auto_parent_window(dialog);

	gtk_widget_show(dialog);

	return data;
}

static void *
pidgin_request_action(const char *title, const char *primary,
	const char *secondary, int default_action,
	PurpleRequestCommonParameters *cpar, void *user_data,
	size_t action_count, va_list actions)
{
	PidginRequestData *data;
	GtkWidget *dialog;
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *img = NULL;
	GtkWidget *content;
	void **buttons;
	char *label_text;
	char *primary_esc, *secondary_esc;
	gsize i;

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_ACTION;
	data->user_data = user_data;

	data->cb_count = action_count;
	data->cbs = g_new0(GCallback, action_count);

	/* Reverse the buttons */
	buttons = g_new0(void *, action_count * 2);

	for (i = 0; i < action_count * 2; i += 2) {
		buttons[(action_count * 2) - i - 2] = va_arg(actions, char *);
		buttons[(action_count * 2) - i - 1] = va_arg(actions, GCallback);
	}

	/* Create the dialog. */
	data->dialog = dialog = gtk_dialog_new();

	gtk_window_set_deletable(GTK_WINDOW(data->dialog), FALSE);

	if (title != NULL)
		gtk_window_set_title(GTK_WINDOW(dialog), title);
#ifdef _WIN32
	else
		gtk_window_set_title(GTK_WINDOW(dialog), PIDGIN_ALERT_TITLE);
#endif

	for (i = 0; i < action_count; i++) {
		gtk_dialog_add_button(GTK_DIALOG(dialog), buttons[2 * i], i);

		data->cbs[i] = buttons[2 * i + 1];
	}

	g_free(buttons);

	g_signal_connect(G_OBJECT(dialog), "response",
					 G_CALLBACK(action_response_cb), data);

	/* Setup the dialog */
	gtk_widget_set_margin_top(dialog, 6);
	gtk_widget_set_margin_bottom(dialog, 6);
	gtk_widget_set_margin_start(dialog, 6);
	gtk_widget_set_margin_end(dialog, 6);
	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
	gtk_widget_set_margin_top(content, 6);
	gtk_widget_set_margin_bottom(content, 6);
	gtk_widget_set_margin_start(content, 6);
	gtk_widget_set_margin_end(content, 6);
	gtk_box_set_spacing(GTK_BOX(content), 12);

	/* Setup the main horizontal box */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_append(GTK_BOX(content), hbox);

	img = pidgin_request_dialog_icon(PURPLE_REQUEST_ACTION, cpar);
	gtk_widget_set_halign(img, GTK_ALIGN_START);
	gtk_widget_set_valign(img, GTK_ALIGN_START);
	gtk_box_append(GTK_BOX(hbox), img);

	/* Vertical box */
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_box_append(GTK_BOX(hbox), vbox);

	pidgin_widget_decorate_account(hbox,
		purple_request_cpar_get_account(cpar));

	pidgin_request_add_help(GTK_DIALOG(dialog), cpar);

	/* Descriptive label */
	primary_esc = pidgin_request_escape(cpar, primary);
	secondary_esc = pidgin_request_escape(cpar, secondary);
	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
								 "%s</span>%s%s" : "%s%s%s"),
								 (primary ? primary_esc : ""),
								 ((primary && secondary) ? "\n\n" : ""),
								 (secondary ? secondary_esc : ""));
	g_free(primary_esc);
	g_free(secondary_esc);

	label = gtk_label_new(NULL);

	gtk_label_set_markup(GTK_LABEL(label), label_text);
	gtk_label_set_wrap(GTK_LABEL(label), TRUE);
	gtk_label_set_xalign(GTK_LABEL(label), 0);
	gtk_label_set_yalign(GTK_LABEL(label), 0);
	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
	gtk_widget_set_valign(label, GTK_ALIGN_FILL);
	gtk_widget_set_vexpand(label, TRUE);
	gtk_box_append(GTK_BOX(vbox), label);

	g_free(label_text);


	if (default_action != PURPLE_DEFAULT_ACTION_NONE) {
		/*
		 * Need to invert the default_action number because the
		 * buttons are added to the dialog in reverse order.
		 */
		gtk_dialog_set_default_response(GTK_DIALOG(dialog), action_count - 1 - default_action);
	}

	/* Show everything. */
	pidgin_auto_parent_window(dialog);

	gtk_widget_show(dialog);

	return data;
}

static void
wait_cancel_cb(GtkWidget *button, PidginRequestData *data)
{
	generic_response_start(data);

	if (data->cbs[0] != NULL)
		((PurpleRequestCancelCb)data->cbs[0])(data->user_data);

	purple_request_close(PURPLE_REQUEST_FIELDS, data);
}

static void *
pidgin_request_wait(const char *title, const char *primary,
	const char *secondary, gboolean with_progress,
	PurpleRequestCancelCb cancel_cb, PurpleRequestCommonParameters *cpar,
	void *user_data)
{
	PidginRequestData *data;
	GtkWidget *dialog, *content;
	GtkWidget *hbox, *vbox, *img, *label, *button;
	gchar *primary_esc, *secondary_esc, *label_text;

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_WAIT;
	data->user_data = user_data;

	data->cb_count = 1;
	data->cbs = g_new0(GCallback, 1);
	data->cbs[0] = (GCallback)cancel_cb;

	data->dialog = dialog = gtk_dialog_new();

	gtk_window_set_deletable(GTK_WINDOW(data->dialog), cancel_cb != NULL);

	if (title != NULL)
		gtk_window_set_title(GTK_WINDOW(dialog), title);
	else
		gtk_window_set_title(GTK_WINDOW(dialog), _("Please wait"));

	/* Setup the dialog */
	gtk_widget_set_margin_top(dialog, 6);
	gtk_widget_set_margin_bottom(dialog, 6);
	gtk_widget_set_margin_start(dialog, 6);
	gtk_widget_set_margin_end(dialog, 6);
	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);

	content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
	gtk_widget_set_margin_top(content, 6);
	gtk_widget_set_margin_bottom(content, 6);
	gtk_widget_set_margin_start(content, 6);
	gtk_widget_set_margin_end(content, 6);
	gtk_box_set_spacing(GTK_BOX(content), 12);

	/* Setup the main horizontal box */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_append(GTK_BOX(content), hbox);

	img = pidgin_request_dialog_icon(PURPLE_REQUEST_WAIT, cpar);
	gtk_widget_set_halign(img, GTK_ALIGN_START);
	gtk_widget_set_valign(img, GTK_ALIGN_START);
	gtk_box_append(GTK_BOX(hbox), img);

	/* Cancel button */
	button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
	g_signal_connect(G_OBJECT(button), "clicked",
		G_CALLBACK(wait_cancel_cb), data);

	/* Vertical box */
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_box_append(GTK_BOX(hbox), vbox);

	pidgin_widget_decorate_account(hbox,
		purple_request_cpar_get_account(cpar));

	pidgin_request_add_help(GTK_DIALOG(dialog), cpar);

	/* Descriptive label */
	primary_esc = pidgin_request_escape(cpar, primary);
	secondary_esc = pidgin_request_escape(cpar, secondary);
	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" "
		"size=\"larger\">%s</span>%s%s" : "%s%s%s"),
		(primary ? primary_esc : ""),
		((primary && secondary) ? "\n\n" : ""),
		(secondary ? secondary_esc : ""));
	g_free(primary_esc);
	g_free(secondary_esc);

	label = gtk_label_new(NULL);

	gtk_label_set_markup(GTK_LABEL(label), label_text);
	gtk_label_set_wrap(GTK_LABEL(label), TRUE);
	gtk_label_set_xalign(GTK_LABEL(label), 0);
	gtk_label_set_yalign(GTK_LABEL(label), 0);
	gtk_label_set_selectable(GTK_LABEL(label), FALSE);
	gtk_widget_set_valign(label, GTK_ALIGN_FILL);
	gtk_widget_set_vexpand(label, TRUE);
	gtk_box_append(GTK_BOX(vbox), label);

	g_free(label_text);

	if (with_progress) {
		GtkProgressBar *bar;

		bar = data->u.wait.progress_bar =
			GTK_PROGRESS_BAR(gtk_progress_bar_new());
		gtk_progress_bar_set_fraction(bar, 0);
		gtk_box_append(GTK_BOX(vbox), GTK_WIDGET(bar));
	}

	/* Show everything. */
	pidgin_auto_parent_window(dialog);

	gtk_widget_show(dialog);

	return data;
}

static void
pidgin_request_wait_update(void *ui_handle, gboolean pulse, gfloat fraction)
{
	GtkProgressBar *bar;
	PidginRequestData *data = ui_handle;

	g_return_if_fail(data->type == PURPLE_REQUEST_WAIT);

	bar = data->u.wait.progress_bar;
	if (pulse)
		gtk_progress_bar_pulse(bar);
	else
		gtk_progress_bar_set_fraction(bar, fraction);
}

static void
req_entry_field_changed_cb(GtkWidget *entry, PurpleRequestField *field)
{
	if (purple_request_field_get_field_type(field) == PURPLE_REQUEST_FIELD_INTEGER) {
		int value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(entry));
		purple_request_field_int_set_value(field, value);
		return;
	}

	if (purple_request_field_string_is_multiline(field))
	{
		char *text;
		GtkTextIter start_iter, end_iter;

		gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(entry), &start_iter);
		gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(entry), &end_iter);

		text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(entry), &start_iter, &end_iter, FALSE);
		purple_request_field_string_set_value(field, (!text || !*text) ? NULL : text);
		g_free(text);
	}
	else
	{
		const char *text = NULL;
		text = gtk_editable_get_text(GTK_EDITABLE(entry));
		purple_request_field_string_set_value(field, (*text == '\0') ? NULL : text);
	}
}

static void
req_field_changed_cb(GtkWidget *widget, PurpleRequestField *field)
{
	PurpleRequestFieldGroup *group;
	PurpleRequestFields *fields;
	PidginRequestData *req_data;
	const GList *it;

	group = purple_request_field_get_group(field);
	fields = purple_request_field_group_get_fields_list(group);
	req_data = purple_request_fields_get_ui_data(fields);

	gtk_widget_set_sensitive(req_data->ok_button,
		purple_request_fields_all_required_filled(fields) &&
		purple_request_fields_all_valid(fields));

	it = purple_request_fields_get_autosensitive(fields);
	for (; it != NULL; it = g_list_next(it)) {
		PurpleRequestField *field = it->data;
		GtkWidget *widget = purple_request_field_get_ui_data(field);
		gboolean sensitive;

		sensitive = purple_request_field_is_sensitive(field);
		gtk_widget_set_sensitive(widget, sensitive);

		/* XXX: and what about multiline? */
		if (GTK_IS_EDITABLE(widget))
			gtk_editable_set_editable(GTK_EDITABLE(widget), sensitive);
	}
}

static void
setup_entry_field(GtkWidget *entry, PurpleRequestField *field)
{
	const char *type_hint;

	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);

	g_signal_connect(G_OBJECT(entry), "changed",
		G_CALLBACK(req_entry_field_changed_cb), field);
	g_signal_connect(G_OBJECT(entry), "changed",
		G_CALLBACK(req_field_changed_cb), field);

	if ((type_hint = purple_request_field_get_field_type_hint(field)) != NULL)
	{
		if (g_str_has_prefix(type_hint, "screenname")) {
			GtkWidget *optmenu = NULL;
			PurpleRequestFieldGroup *group = purple_request_field_get_group(field);
			GList *fields = purple_request_field_group_get_fields(group);

			/* Ensure the account option menu is created (if the widget hasn't
			 * been initialized already) for username auto-completion. */
			while (fields)
			{
				PurpleRequestField *fld = fields->data;
				fields = fields->next;

				if (purple_request_field_get_field_type(fld) == PURPLE_REQUEST_FIELD_ACCOUNT &&
						purple_request_field_is_visible(fld))
				{
					const char *type_hint = purple_request_field_get_field_type_hint(fld);
					if (purple_strequal(type_hint, "account"))
					{
						optmenu = GTK_WIDGET(purple_request_field_get_ui_data(fld));
						if (optmenu == NULL) {
							optmenu = GTK_WIDGET(create_account_field(fld));
							purple_request_field_set_ui_data(fld, optmenu);
						}
						break;
					}
				}
			}
			pidgin_setup_screenname_autocomplete(entry, optmenu, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(purple_strequal(type_hint, "screenname-all")));
		}
	}
}

static GtkWidget *
create_string_field(PurpleRequestField *field)
{
	const char *value;
	GtkWidget *widget;
	gboolean is_editable;

	value = purple_request_field_string_get_default_value(field);
	is_editable = purple_request_field_is_sensitive(field);

	if (purple_request_field_string_is_multiline(field))
	{
		GtkWidget *textview;
		GtkEventController *controller;

		textview = gtk_text_view_new();
		gtk_text_view_set_editable(GTK_TEXT_VIEW(textview),
								   TRUE);
		gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview),
									GTK_WRAP_WORD_CHAR);

		if (value != NULL)
		{
			GtkTextBuffer *buffer;

			buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));

			gtk_text_buffer_set_text(buffer, value, -1);
		}

		gtk_widget_set_tooltip_text(textview, purple_request_field_get_tooltip(field));

		gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), is_editable);

		controller = gtk_event_controller_focus_new();
		gtk_widget_add_controller(textview, controller);
		g_signal_connect(controller, "leave",
						 G_CALLBACK(field_string_focus_out_cb), field);

	    if (purple_request_field_is_required(field))
	    {
			GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
			g_signal_connect(G_OBJECT(buffer), "changed",
							 G_CALLBACK(req_entry_field_changed_cb), field);
	    }

		widget = pidgin_make_scrollable(textview, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS, -1, 75);
	}
	else
	{
		GtkEventController *controller = NULL;

		widget = gtk_entry_new();

		setup_entry_field(widget, field);

		if(value != NULL) {
			gtk_editable_set_text(GTK_EDITABLE(widget), value);
		}

		gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

		if (purple_request_field_string_is_masked(field))
		{
			gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
		}

		gtk_editable_set_editable(GTK_EDITABLE(widget), is_editable);

		controller = gtk_event_controller_focus_new();
		gtk_widget_add_controller(widget, controller);
		g_signal_connect(controller, "leave",
						 G_CALLBACK(field_string_focus_out_cb), field);
	}

	return widget;
}

static GtkWidget *
create_int_field(PurpleRequestField *field)
{
	int value;
	GtkWidget *widget;

	widget = gtk_spin_button_new_with_range(
		purple_request_field_int_get_lower_bound(field),
		purple_request_field_int_get_upper_bound(field), 1);

	setup_entry_field(widget, field);

	value = purple_request_field_int_get_default_value(field);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), value);

	gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

	return widget;
}

static GtkWidget *
create_bool_field(PurpleRequestField *field,
	PurpleRequestCommonParameters *cpar)
{
	GtkWidget *widget;
	gchar *label;

	label = pidgin_request_escape(cpar,
		purple_request_field_get_label(field));
	widget = gtk_check_button_new_with_label(label);
	g_free(label);

	gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
		purple_request_field_bool_get_default_value(field));

	g_signal_connect(G_OBJECT(widget), "toggled",
					 G_CALLBACK(field_bool_cb), field);
	g_signal_connect(widget, "toggled",
		G_CALLBACK(req_field_changed_cb), field);

	return widget;
}

static GtkWidget *
create_choice_field(PurpleRequestField *field,
	PurpleRequestCommonParameters *cpar)
{
#warning please rewrite me
#if 0
	GtkWidget *widget;
	GList *elements = purple_request_field_choice_get_elements(field);
	guint num_labels = g_list_length(elements);
	gpointer *values = g_new(gpointer, num_labels);
	gpointer default_value;
	gboolean default_found = FALSE;
	int i;

	default_value = purple_request_field_choice_get_value(field);
	if (num_labels > 5 || purple_request_cpar_is_compact(cpar))
	{
		int default_index = 0;
		widget = gtk_combo_box_text_new();

		i = 0;
		for (GList *l = elements; l != NULL; l = g_list_next(l))
		{
			PurpleKeyValuePair *choice = l->data;

			gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), choice->key);
			if (choice->value == default_value) {
				default_index = i;
				default_found = TRUE;
			}
			values[i++] = choice->value;
		}

		gtk_combo_box_set_active(GTK_COMBO_BOX(widget), default_index);

		gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

		g_signal_connect(G_OBJECT(widget), "changed",
						 G_CALLBACK(field_choice_menu_cb), field);
	}
	else
	{
		GtkWidget *box;
		GtkWidget *first_radio = NULL;
		GtkWidget *radio;
		GtkOrientation orientation = GTK_ORIENTATION_HORIZONTAL;

		if(num_labels == 2) {
			orientation = GTK_ORIENTATION_HORIZONTAL;
			box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
		} else {
			orientation = GTK_ORIENTATION_VERTICAL;
			box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
		}

		widget = box;

		gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

		i = 0;
		for (GList *l = elements; l != NULL; l = g_list_next(l))
		{
			PurpleKeyValuePair *choice = l->data;

			radio = gtk_check_button_new_with_label(choice->key);
			g_object_set_data(G_OBJECT(radio), "box", box);

			if(first_radio == NULL) {
				first_radio = radio;
			} else {
				gtk_check_button_set_group(GTK_CHECK_BUTTON(radio),
				                           GTK_CHECK_BUTTON(first_radio));
			}

			if (choice->value == default_value) {
				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
				default_found = TRUE;
			}
			values[i++] = choice->value;

			if(orientation == GTK_ORIENTATION_VERTICAL) {
				gtk_widget_set_valign(radio, GTK_ALIGN_FILL);
				gtk_widget_set_vexpand(radio, TRUE);
			} else if(orientation == GTK_ORIENTATION_HORIZONTAL) {
				gtk_widget_set_halign(radio, GTK_ALIGN_FILL);
				gtk_widget_set_hexpand(radio, TRUE);
			}

			gtk_box_append(GTK_BOX(box), radio);

			g_signal_connect(G_OBJECT(radio), "toggled",
							 G_CALLBACK(field_choice_option_cb), field);
		}
	}

	if (!default_found && i > 0)
		purple_request_field_choice_set_value(field, values[0]);

	g_object_set_data_full(G_OBJECT(widget), "values", values, g_free);

	return widget;
#endif

	return NULL;
}

static GtkWidget *
create_image_field(PurpleRequestField *field)
{
	GtkWidget *widget;
	GdkPixbuf *buf, *scale;

	buf = purple_gdk_pixbuf_from_data(
			(const guchar *)purple_request_field_image_get_buffer(field),
			purple_request_field_image_get_size(field));

	scale = gdk_pixbuf_scale_simple(buf,
			purple_request_field_image_get_scale_x(field) * gdk_pixbuf_get_width(buf),
			purple_request_field_image_get_scale_y(field) * gdk_pixbuf_get_height(buf),
			GDK_INTERP_BILINEAR);
	widget = gtk_image_new_from_pixbuf(scale);
	g_object_unref(G_OBJECT(buf));
	g_object_unref(G_OBJECT(scale));

	gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));

	return widget;
}

static GtkWidget *
create_account_field(PurpleRequestField *field)
{
	GtkWidget *widget;
	PurpleAccount *account;

	widget = pidgin_account_chooser_new();
	account  = purple_request_field_account_get_default_value(field);

	if(purple_request_field_account_get_show_all(field)) {
		GtkListStore *store = pidgin_account_store_new();

		gtk_combo_box_set_model(GTK_COMBO_BOX(widget), GTK_TREE_MODEL(store));

		g_object_unref(G_OBJECT(store));
	} else {
		GtkListStore *store = NULL;
		GtkTreeModel *filter = NULL;

		store = pidgin_account_store_new();
		filter = pidgin_account_filter_connected_new(GTK_TREE_MODEL(store),
		                                             NULL);
		g_object_unref(G_OBJECT(store));

		gtk_combo_box_set_model(GTK_COMBO_BOX(widget), GTK_TREE_MODEL(filter));
		g_object_unref(G_OBJECT(filter));
	}

	pidgin_account_chooser_set_selected(PIDGIN_ACCOUNT_CHOOSER(widget),
	                                    account);
	pidgin_account_chooser_set_filter_func(
	        PIDGIN_ACCOUNT_CHOOSER(widget),
	        purple_request_field_account_get_filter(field));
	g_signal_connect(widget, "changed", G_CALLBACK(field_account_cb),
	                 field);

	gtk_widget_set_tooltip_text(widget, purple_request_field_get_tooltip(field));
	g_signal_connect(widget, "changed",
		G_CALLBACK(req_field_changed_cb), field);

	return widget;
}

static void
select_field_list_item(GtkTreeModel *model, GtkTreePath *path,
					   GtkTreeIter *iter, gpointer data)
{
	PurpleRequestField *field = (PurpleRequestField *)data;
	char *text;

	gtk_tree_model_get(model, iter, 1, &text, -1);

	purple_request_field_list_add_selected(field, text);
	g_free(text);
}

static void
list_field_select_changed_cb(GtkTreeSelection *sel, PurpleRequestField *field)
{
	purple_request_field_list_clear_selected(field);

	gtk_tree_selection_selected_foreach(sel, select_field_list_item, field);
}

static GtkWidget *
create_list_field(PurpleRequestField *field)
{
	GtkWidget *treeview;
	GtkListStore *store;
	GtkCellRenderer *renderer;
	GtkTreeSelection *sel;
	GtkTreeViewColumn *column;
	GtkTreeIter iter;
	GList *l;
	gboolean has_icons;

	has_icons = purple_request_field_list_has_icons(field);


	/* Create the list store */
	if (has_icons) {
		store = gtk_list_store_new(3, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF);
	} else {
		store = gtk_list_store_new(2, G_TYPE_POINTER, G_TYPE_STRING);
	}

	/* Create the tree view */
	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	g_object_unref(G_OBJECT(store));
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);

	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

	if (purple_request_field_list_get_multi_select(field))
		gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);

	column = gtk_tree_view_column_new();
	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);

	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(column, renderer, TRUE);
	gtk_tree_view_column_add_attribute(column, renderer, "text", 1);

	if (has_icons) {
		renderer = gtk_cell_renderer_pixbuf_new();
		gtk_tree_view_column_pack_start(column, renderer, TRUE);
		gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", 2);

		gtk_widget_set_size_request(treeview, 200, 400);
	}

	for (l = purple_request_field_list_get_items(field); l != NULL; l = l->next)
	{
		PurpleKeyValuePair *item = l->data;
		const char *text = (const char *)item->key;

		gtk_list_store_append(store, &iter);

		if (has_icons) {
			const char *icon_path = (const char *)item->value;
			GdkPixbuf* pixbuf = NULL;

			if (icon_path)
				pixbuf = purple_gdk_pixbuf_new_from_file(icon_path);

			gtk_list_store_set(store, &iter,
			                   0, purple_request_field_list_get_data(field, text),
			                   1, text,
			                   2, pixbuf,
			                   -1);
		} else {
			gtk_list_store_set(store, &iter,
			                   0, purple_request_field_list_get_data(field, text),
			                   1, text,
			                   -1);
		}

		if (purple_request_field_list_is_selected(field, text)) {
			gtk_tree_selection_select_iter(sel, &iter);
		}
	}

	/*
	 * We only want to catch changes made by the user, so it's important
	 * that we wait until after the list is created to connect this
	 * handler.  If we connect the handler before the loop above and
	 * there are multiple items selected, then selecting the first iter
	 * in the tree causes list_field_select_changed_cb to be triggered
	 * which clears out the rest of the list of selected items.
	 */
	g_signal_connect(G_OBJECT(sel), "changed",
					 G_CALLBACK(list_field_select_changed_cb), field);

	return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, -1, -1);
}

static GdkPixbuf*
_pidgin_datasheet_stock_icon_get(const gchar *stock_name)
{
	GdkPixbuf *image = NULL;
	gchar *domain, *id;

	if (stock_name == NULL)
		return NULL;

	/* core is quitting */
	if (datasheet_stock == NULL)
		return NULL;

	if (g_hash_table_lookup_extended(datasheet_stock, stock_name,
		NULL, (gpointer*)&image))
	{
		return image;
	}

	domain = g_strdup(stock_name);
	id = strchr(domain, '/');
	if (!id) {
		g_free(domain);
		return NULL;
	}
	id[0] = '\0';
	id++;

	if (purple_strequal(domain, "protocol")) {
		PurpleAccount *account;
		PurpleAccountManager *manager = NULL;
		gchar *protocol_id, *accountname;

		protocol_id = id;
		accountname = strchr(id, ':');

		if (!accountname) {
			g_free(domain);
			return NULL;
		}

		accountname[0] = '\0';
		accountname++;

		manager = purple_account_manager_get_default();
		account = purple_account_manager_find(manager, accountname,
		                                      protocol_id);
		if(account) {
			image = pidgin_create_protocol_icon(account,
				PIDGIN_PROTOCOL_ICON_SMALL);
		}
	} else {
		purple_debug_error("gtkrequest", "Unknown domain: %s", domain);
		g_free(domain);
		return NULL;
	}

	g_hash_table_insert(datasheet_stock, g_strdup(stock_name), image);
	return image;
}

static PurpleRequestDatasheetRecord*
datasheet_get_selected_row(GtkWidget *sheet_widget)
{
	PurpleRequestDatasheet *sheet;
	GtkTreeView *view;
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GList *sel_list;
	gpointer key;

	g_return_val_if_fail(sheet_widget != NULL, NULL);

	view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(sheet_widget), "view"));
	sheet = g_object_get_data(G_OBJECT(sheet_widget), "sheet");

	g_return_val_if_fail(view != NULL, NULL);
	g_return_val_if_fail(sheet != NULL, NULL);

	selection = gtk_tree_view_get_selection(view);
	if (gtk_tree_selection_count_selected_rows(selection) != 1)
		return NULL;

	sel_list = gtk_tree_selection_get_selected_rows(selection, &model);
	gtk_tree_model_get_iter(model, &iter, sel_list->data);
	g_list_free_full(sel_list, (GDestroyNotify)gtk_tree_path_free);

	gtk_tree_model_get(model, &iter, 0, &key, -1);

	return purple_request_datasheet_record_find(sheet, key);
}

static void
datasheet_button_check_sens(GtkWidget *button, gpointer _sheet_widget)
{
	PurpleRequestDatasheetAction *act;
	GtkWidget *sheet_widget = GTK_WIDGET(_sheet_widget);

	g_return_if_fail(sheet_widget != NULL);

	act = g_object_get_data(G_OBJECT(button), "action");

	g_return_if_fail(act != NULL);

	gtk_widget_set_sensitive(button,
		purple_request_datasheet_action_is_sensitive(act,
			datasheet_get_selected_row(sheet_widget)));
}

static void
datasheet_selection_changed(GtkWidget *sheet_widget)
{
#if 0
	gpointer buttons_box;

	g_return_if_fail(sheet_widget != NULL);

	buttons_box = g_object_get_data(G_OBJECT(sheet_widget), "buttons");
	gtk_container_foreach(GTK_CONTAINER(buttons_box),
		datasheet_button_check_sens, sheet_widget);
#endif
}

static void
datasheet_update_rec(PurpleRequestDatasheetRecord *rec, GtkListStore *model,
	GtkTreeIter *iter)
{
	guint i, col_count;
	PurpleRequestDatasheet *sheet;

	g_return_if_fail(rec != NULL);
	g_return_if_fail(model != NULL);
	g_return_if_fail(iter != NULL);

	sheet = purple_request_datasheet_record_get_datasheet(rec);

	g_return_if_fail(sheet != NULL);

	col_count = purple_request_datasheet_get_column_count(sheet);

	for (i = 0; i < col_count; i++) {
		PurpleRequestDatasheetColumnType type;

		type = purple_request_datasheet_get_column_type(
			sheet, i);
		if (type == PURPLE_REQUEST_DATASHEET_COLUMN_STRING) {
			GValue val;

			val.g_type = 0;
			g_value_init(&val, G_TYPE_STRING);
			g_value_set_string(&val,
				purple_request_datasheet_record_get_string_data(
					rec, i));
			gtk_list_store_set_value(model, iter,
				i + 1, &val);
		} else if (type ==
			PURPLE_REQUEST_DATASHEET_COLUMN_IMAGE)
		{
			GdkPixbuf *pixbuf;

			pixbuf = _pidgin_datasheet_stock_icon_get(
				purple_request_datasheet_record_get_image_data(
					rec, i));
			gtk_list_store_set(model, iter, i + 1,
				pixbuf, -1);
		} else
			g_warn_if_reached();
	}
}

static void
datasheet_fill(PurpleRequestDatasheet *sheet, GtkListStore *model)
{
	const GList *it;

	gtk_list_store_clear(model);

	it = purple_request_datasheet_get_records(sheet);
	for (; it != NULL; it = g_list_next(it)) {
		PurpleRequestDatasheetRecord *rec = it->data;
		GtkTreeIter iter;

		gtk_list_store_append(model, &iter);
		gtk_list_store_set(model, &iter, 0,
			purple_request_datasheet_record_get_key(rec), -1);

		datasheet_update_rec(rec, model, &iter);
	}

	datasheet_selection_changed(GTK_WIDGET(g_object_get_data(
		G_OBJECT(model), "sheet-widget")));
}

static void
datasheet_update(PurpleRequestDatasheet *sheet, gpointer key,
	GtkListStore *model)
{
	PurpleRequestDatasheetRecord *rec;
	GtkTreeIter iter;
	GtkTreeModel *tmodel = GTK_TREE_MODEL(model);
	gboolean found = FALSE;

	g_return_if_fail(tmodel != NULL);

	if (key == NULL) {
		datasheet_fill(sheet, model);
		return;
	}

	rec = purple_request_datasheet_record_find(sheet, key);

	if (gtk_tree_model_get_iter_first(tmodel, &iter)) {
		do {
			gpointer ikey;

			gtk_tree_model_get(tmodel, &iter, 0, &ikey, -1);

			if (key == ikey) {
				found = TRUE;
				break;
			}
		} while (gtk_tree_model_iter_next(tmodel, &iter));
	}

	if (rec == NULL && !found)
		return;

	if (rec == NULL) {
		gtk_list_store_remove(model, &iter);
		return;
	}

	if (!found) {
		gtk_list_store_append(model, &iter);
		gtk_list_store_set(model, &iter, 0, key, -1);
	}

	datasheet_update_rec(rec, model, &iter);

	datasheet_selection_changed(GTK_WIDGET(g_object_get_data(
		G_OBJECT(model), "sheet-widget")));
}


static void
datasheet_selection_changed_cb(GtkTreeSelection *sel, gpointer sheet_widget)
{
	datasheet_selection_changed(GTK_WIDGET(sheet_widget));
}

static void
datasheet_action_clicked(GtkButton *btn, PurpleRequestDatasheetAction *act)
{
	GtkWidget *sheet_widget;

	sheet_widget = g_object_get_data(G_OBJECT(btn), "sheet-widget");

	g_return_if_fail(sheet_widget != NULL);

	purple_request_datasheet_action_call(act, datasheet_get_selected_row(
		sheet_widget));
}

static GtkWidget *
create_datasheet_field(PurpleRequestField *field, GtkSizeGroup *buttons_sg)
{
	PurpleRequestDatasheet *sheet;
	guint i, col_count;
	GType *col_types;
	GtkListStore *model;
	GtkTreeView *view;
	GtkTreeSelection *sel;
	GtkWidget *scrollable;
	GtkCellRenderer *renderer_image = NULL, *renderer_text = NULL;
	GtkTreeViewColumn *id_column;
	GtkWidget *main_box;
	GtkWidget *buttons_box;
	const GList *it;

	sheet = purple_request_field_datasheet_get_sheet(field);
	main_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);

	col_count = purple_request_datasheet_get_column_count(sheet);

	col_types = g_new0(GType, col_count + 1);
	col_types[0] = G_TYPE_POINTER;
	for (i = 0; i < col_count; i++) {
		PurpleRequestDatasheetColumnType type;
		type = purple_request_datasheet_get_column_type(sheet, i);
		if (type == PURPLE_REQUEST_DATASHEET_COLUMN_STRING)
			col_types[i + 1] = G_TYPE_STRING;
		else if (type == PURPLE_REQUEST_DATASHEET_COLUMN_IMAGE)
			col_types[i + 1] = GDK_TYPE_PIXBUF;
		else
			g_warn_if_reached();
	}
	model = gtk_list_store_newv(col_count + 1, col_types);
	g_free(col_types);

	view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(
		GTK_TREE_MODEL(model)));
	g_object_set_data(G_OBJECT(model), "sheet-widget", main_box);
	g_object_unref(G_OBJECT(model));

	id_column = gtk_tree_view_column_new();
	gtk_tree_view_column_set_visible(id_column, FALSE);
	gtk_tree_view_append_column(view, id_column);

	for (i = 0; i < col_count; i++) {
		PurpleRequestDatasheetColumnType type;
		const gchar *title;
		GtkCellRenderer *renderer = NULL;
		const gchar *type_str = "";

		type = purple_request_datasheet_get_column_type(sheet, i);
		title = purple_request_datasheet_get_column_title(sheet, i);

		if (type == PURPLE_REQUEST_DATASHEET_COLUMN_STRING) {
			type_str = "text";
			if (!renderer_text)
				renderer_text = gtk_cell_renderer_text_new();
			renderer = renderer_text;
		}
		else if (type == PURPLE_REQUEST_DATASHEET_COLUMN_IMAGE) {
			type_str = "pixbuf";
			if (!renderer_image)
				renderer_image = gtk_cell_renderer_pixbuf_new();
			renderer = renderer_image;
		} else
			g_warn_if_reached();

		if (title == NULL)
			title = "";
		gtk_tree_view_insert_column_with_attributes(
			view, -1, title, renderer, type_str,
			i + 1, NULL);
	}

	gtk_widget_set_size_request(GTK_WIDGET(view), 400, 250);

	scrollable = gtk_scrolled_window_new();
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollable),
	                               GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollable),
	                              GTK_WIDGET(view));
	gtk_widget_set_halign(scrollable, GTK_ALIGN_FILL);
	gtk_widget_set_hexpand(scrollable, TRUE);
	gtk_box_append(GTK_BOX(main_box), scrollable);

	buttons_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_size_group_add_widget(buttons_sg, buttons_box);
	gtk_box_append(GTK_BOX(main_box), buttons_box);

	it = purple_request_datasheet_get_actions(sheet);
	for (; it != NULL; it = g_list_next(it)) {
		PurpleRequestDatasheetAction *act = it->data;
		GtkButton *btn;
		const gchar *label;

		label = purple_request_datasheet_action_get_label(act);

		btn = GTK_BUTTON(gtk_button_new_with_label(label ? label : ""));

		g_object_set_data(G_OBJECT(btn), "action", act);
		g_object_set_data(G_OBJECT(btn), "sheet-widget", main_box);
		g_signal_connect(G_OBJECT(btn), "clicked",
			G_CALLBACK(datasheet_action_clicked), act);

		gtk_box_append(GTK_BOX(buttons_box), GTK_WIDGET(btn));
	}

	g_object_set_data(G_OBJECT(main_box), "view", view);
	g_object_set_data(G_OBJECT(main_box), "buttons", buttons_box);
	g_object_set_data(G_OBJECT(main_box), "sheet", sheet);

	datasheet_fill(sheet, model);
	purple_signal_connect(sheet, "record-changed",
		pidgin_request_get_handle(),
		G_CALLBACK(datasheet_update), model);

	sel = gtk_tree_view_get_selection(view);
	g_signal_connect(G_OBJECT(sel), "changed",
		G_CALLBACK(datasheet_selection_changed_cb), main_box);

	return main_box;
}

static void *
pidgin_request_fields(const char *title, const char *primary,
	const char *secondary, PurpleRequestFields *fields, const char *ok_text,
	GCallback ok_cb, const char *cancel_text, GCallback cancel_cb,
	PurpleRequestCommonParameters *cpar, void *user_data)
{
	PidginRequestData *data;
	GtkWidget *win;
	GtkWidget *hbox, *vbox;
	GtkWidget *frame;
	GtkWidget *label;
	GtkWidget *grid;
	GtkWidget *button;
	GtkWidget *img;
	GtkWidget *content;
	GtkSizeGroup *sg, *datasheet_buttons_sg;
	GList *gl, *fl;
	PurpleRequestFieldGroup *group;
	PurpleRequestField *field;
	char *label_text;
	char *primary_esc, *secondary_esc;
	const gboolean compact = purple_request_cpar_is_compact(cpar);
	GSList *extra_actions;
	size_t i;
	gboolean ok_btn = (ok_text != NULL);

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_FIELDS;
	data->user_data = user_data;
	data->u.multifield.fields = fields;

	purple_request_fields_set_ui_data(fields, data);

	extra_actions = purple_request_cpar_get_extra_actions(cpar);

	data->cb_count = 2;
	data->cbs = g_new0(GCallback, 2);

	data->cbs[0] = ok_cb;
	data->cbs[1] = cancel_cb;

	data->dialog = win = pidgin_dialog_new(title, 12, "multifield", TRUE) ;

	g_signal_connect(G_OBJECT(win), "delete_event",
					 G_CALLBACK(destroy_multifield_cb), data);

	/* Setup the main horizontal box */
	content = gtk_dialog_get_content_area(GTK_DIALOG(win));
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_append(GTK_BOX(content), hbox);

	/* Dialog icon. */
	img = pidgin_request_dialog_icon(PURPLE_REQUEST_FIELDS, cpar);
	gtk_widget_set_halign(img, GTK_ALIGN_START);
	gtk_widget_set_valign(img, GTK_ALIGN_START);
	gtk_box_append(GTK_BOX(hbox), img);

	pidgin_request_add_help(GTK_DIALOG(win), cpar);

	i = 0;
	for (GSList *it = extra_actions; it != NULL; it = it->next) {
		PurpleKeyValuePair *extra_action = it->data;

		button = gtk_dialog_add_button(GTK_DIALOG(win), extra_action->key, i++);
		g_signal_connect(G_OBJECT(button), "clicked",
				G_CALLBACK(multifield_extra_cb), data);
		g_object_set_data(G_OBJECT(button), "extra-cb", extra_action->value);
	}

	/* Cancel button */
	button = gtk_dialog_add_button(GTK_DIALOG(win), cancel_text, GTK_RESPONSE_CANCEL);
	g_signal_connect(G_OBJECT(button), "clicked",
			G_CALLBACK(multifield_cancel_cb), data);

	/* OK button */
	if (!ok_btn) {
		gtk_window_set_default_widget(GTK_WINDOW(win), button);
	} else {
		button = gtk_dialog_add_button(GTK_DIALOG(win), ok_text, GTK_RESPONSE_OK);
		g_signal_connect(G_OBJECT(button), "clicked",
				G_CALLBACK(multifield_ok_cb), data);
		data->ok_button = button;
		gtk_window_set_default_widget(GTK_WINDOW(win), button);
	}

	pidgin_widget_decorate_account(hbox,
		purple_request_cpar_get_account(cpar));

	/* Setup the vbox */
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
	gtk_widget_set_halign(vbox, GTK_ALIGN_FILL);
	gtk_widget_set_hexpand(vbox, TRUE);
	gtk_box_append(GTK_BOX(hbox), vbox);

	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
	datasheet_buttons_sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);

	if(primary) {
		primary_esc = pidgin_request_escape(cpar, primary);
		label_text = g_strdup_printf(
				"<span weight=\"bold\" size=\"larger\">%s</span>", primary_esc);
		g_free(primary_esc);
		label = gtk_label_new(NULL);

		gtk_label_set_markup(GTK_LABEL(label), label_text);
		gtk_label_set_wrap(GTK_LABEL(label), TRUE);
		gtk_label_set_xalign(GTK_LABEL(label), 0);
		gtk_label_set_yalign(GTK_LABEL(label), 0);
		gtk_box_append(GTK_BOX(vbox), label);
		g_free(label_text);
	}

	if (secondary) {
		secondary_esc = pidgin_request_escape(cpar, secondary);
		label = gtk_label_new(NULL);

		gtk_label_set_markup(GTK_LABEL(label), secondary_esc);
		g_free(secondary_esc);
		gtk_label_set_wrap(GTK_LABEL(label), TRUE);
		gtk_label_set_xalign(GTK_LABEL(label), 0);
		gtk_label_set_yalign(GTK_LABEL(label), 0);
		gtk_box_append(GTK_BOX(vbox), label);
	}

	for (gl = purple_request_fields_get_groups(fields);
		 gl != NULL;
		 gl = gl->next)
	{
		GList *field_list;
		size_t field_count = 0;
		size_t cols = 1;
		size_t rows;
		size_t row_num = 0;
		gboolean contains_resizable = FALSE;

		group      = gl->data;
		field_list = purple_request_field_group_get_fields(group);

		if(purple_request_field_group_get_title(group) != NULL) {
			frame = pidgin_make_frame(vbox,
				purple_request_field_group_get_title(group));
		} else {
			frame = vbox;
		}

		field_count = g_list_length(field_list);
		rows = field_count;

		for (fl = field_list; fl != NULL; fl = fl->next)
		{
			PurpleRequestFieldType type;

			field = (PurpleRequestField *)fl->data;

			type = purple_request_field_get_field_type(field);

			if (type == PURPLE_REQUEST_FIELD_DATASHEET)
				contains_resizable = TRUE;

			if (type == PURPLE_REQUEST_FIELD_LABEL)
			{
				rows++;
			}
			else if ((type == PURPLE_REQUEST_FIELD_LIST) ||
				 (type == PURPLE_REQUEST_FIELD_STRING &&
				  purple_request_field_string_is_multiline(field)))
			{
				rows += 2;
			} else if (compact && type != PURPLE_REQUEST_FIELD_BOOLEAN)
				rows++;
		}

		grid = gtk_grid_new();
		gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
		gtk_grid_set_column_spacing(GTK_GRID(grid), 6);

		/* This box could be setup in a number of ways, so just set all of the
		 * fill and expand properties instead of only setting the
		 * minimums.
		 */
		g_object_set(G_OBJECT(grid),
		             "hexpand", contains_resizable,
		             "vexpand", contains_resizable,
		             NULL);

		if(contains_resizable) {
			g_object_set(G_OBJECT(grid),
			             "halign", GTK_ALIGN_FILL,
			             "valign", GTK_ALIGN_FILL,
			             NULL);
		}
		gtk_box_append(GTK_BOX(frame), grid);

		for (row_num = 0, fl = field_list;
			 row_num < rows && fl != NULL;
			 row_num++)
		{
			gboolean dummy_counter = TRUE;
			/* it's the same as loop above */
			for (; dummy_counter && fl != NULL; dummy_counter = FALSE, fl = fl->next)
			{
				size_t col_offset = 0;
				PurpleRequestFieldType type;
				GtkWidget *widget = NULL;
				gchar *field_label;

				label = NULL;
				field = fl->data;

				if (!purple_request_field_is_visible(field)) {
					continue;
				}

				type = purple_request_field_get_field_type(field);
				field_label = pidgin_request_escape(cpar,
					purple_request_field_get_label(field));

				if (type != PURPLE_REQUEST_FIELD_BOOLEAN && field_label)
				{
					char *text = NULL;

					if (field_label[strlen(field_label) - 1] != ':' &&
						field_label[strlen(field_label) - 1] != '?' &&
						type != PURPLE_REQUEST_FIELD_LABEL)
					{
						text = g_strdup_printf("%s:", field_label);
					}

					label = gtk_label_new(NULL);
					gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), text ? text : field_label);
					g_free(text);

					gtk_widget_set_hexpand(label, TRUE);
					gtk_widget_set_vexpand(label, TRUE);
					gtk_label_set_xalign(GTK_LABEL(label), 0);

					gtk_size_group_add_widget(sg, label);

					if (type == PURPLE_REQUEST_FIELD_LABEL ||
					    type == PURPLE_REQUEST_FIELD_LIST ||
						(type == PURPLE_REQUEST_FIELD_STRING &&
						 purple_request_field_string_is_multiline(field)))
					{
						gtk_grid_attach(GTK_GRID(grid), label,
							0, row_num, 2 * cols, 1);

						row_num++;
					}
					else
					{
						gtk_grid_attach(GTK_GRID(grid), label,
							col_offset, row_num, 1, 1);
					}

					g_free(field_label);
				}

				widget = GTK_WIDGET(purple_request_field_get_ui_data(field));
				if (widget == NULL)
				{
					if (type == PURPLE_REQUEST_FIELD_STRING)
						widget = create_string_field(field);
					else if (type == PURPLE_REQUEST_FIELD_INTEGER)
						widget = create_int_field(field);
					else if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
						widget = create_bool_field(field, cpar);
					else if (type == PURPLE_REQUEST_FIELD_CHOICE)
						widget = create_choice_field(field, cpar);
					else if (type == PURPLE_REQUEST_FIELD_LIST)
						widget = create_list_field(field);
					else if (type == PURPLE_REQUEST_FIELD_IMAGE)
						widget = create_image_field(field);
					else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
						widget = create_account_field(field);
					else if (type == PURPLE_REQUEST_FIELD_DATASHEET)
						widget = create_datasheet_field(field, datasheet_buttons_sg);
					else
						continue;
				}

				gtk_widget_set_sensitive(widget,
					purple_request_field_is_sensitive(field));

				if (label)
					gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);

				gtk_widget_set_hexpand(widget, TRUE);
				gtk_widget_set_vexpand(widget, TRUE);
				gtk_widget_set_margin_start(widget, 5);
				gtk_widget_set_margin_end(widget, 5);

				if (type == PURPLE_REQUEST_FIELD_STRING &&
					purple_request_field_string_is_multiline(field))
				{
					gtk_grid_attach(GTK_GRID(grid), widget,
						0, row_num, 2 * cols, 1);
				}
				else if (type == PURPLE_REQUEST_FIELD_LIST)
				{
					gtk_grid_attach(GTK_GRID(grid), widget,
						0, row_num, 2 * cols, 1);
				}
				else if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
				{
					gtk_grid_attach(GTK_GRID(grid), widget,
						col_offset, row_num, 1, 1);
				}
				else if (compact) {
					row_num++;
					gtk_grid_attach(GTK_GRID(grid), widget,
						0, row_num, 2 * cols, 1);
				} else {
					gtk_grid_attach(GTK_GRID(grid), widget,
						1, row_num, 2 * cols - 1, 1);
				}

				purple_request_field_set_ui_data(field, widget);
			}
		}
	}

	g_object_unref(sg);
	g_object_unref(datasheet_buttons_sg);

	if (!purple_request_fields_all_required_filled(fields))
		gtk_widget_set_sensitive(data->ok_button, FALSE);

	if (!purple_request_fields_all_valid(fields))
		gtk_widget_set_sensitive(data->ok_button, FALSE);

	pidgin_auto_parent_window(win);

	gtk_widget_show(win);

	return data;
}

static void
file_ok_check_if_exists_cb(GtkWidget *widget, gint response, PidginRequestData *data)
{
	GFile *current_path;

	if (response != GTK_RESPONSE_ACCEPT) {
		if (data->cbs[0] != NULL)
			((PurpleRequestFileCb)data->cbs[0])(data->user_data, NULL);
		purple_request_close(data->type, data);
		return;
	}

	data->u.file.name = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(data->dialog));
	current_path = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(data->dialog));
	if (current_path != NULL) {
		gchar *current_folder = g_file_get_path(current_path);
		if (data->u.file.savedialog) {
			purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_save_folder", current_folder);
		} else {
			purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_open_folder", current_folder);
		}
		g_free(current_folder);
	}
	if (data->cbs[1] != NULL) {
		((PurpleRequestFileCb)data->cbs[1])(data->user_data, data->u.file.name);
	}
	purple_request_close(data->type, data);

	g_clear_object(&current_path);
}

static void *
pidgin_request_file(const char *title, const char *filename,
	gboolean savedialog, GCallback ok_cb, GCallback cancel_cb,
	PurpleRequestCommonParameters *cpar, void *user_data)
{
	PidginRequestData *data;
	GtkFileChooserNative *filesel;
#ifdef _WIN32
	const gchar *current_folder;
	gboolean folder_set = FALSE;
#endif

	data = g_new0(PidginRequestData, 1);
	data->type = PURPLE_REQUEST_FILE;
	data->user_data = user_data;
	data->cb_count = 2;
	data->cbs = g_new0(GCallback, 2);
	data->cbs[0] = cancel_cb;
	data->cbs[1] = ok_cb;
	data->u.file.savedialog = savedialog;

	filesel = gtk_file_chooser_native_new(
	        title ? title
	              : (savedialog ? _("Save File...") : _("Open File...")),
	        NULL,
	        savedialog ? GTK_FILE_CHOOSER_ACTION_SAVE
	                   : GTK_FILE_CHOOSER_ACTION_OPEN,
	        savedialog ? _("_Save") : _("_Open"), _("_Cancel"));

	if ((filename != NULL) && (*filename != '\0')) {
		GFile *path = g_file_new_for_path(filename);

		if(savedialog) {
			gtk_file_chooser_set_file(GTK_FILE_CHOOSER(filesel), path, NULL);
		} else if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
			gtk_file_chooser_set_file(GTK_FILE_CHOOSER(filesel), path, NULL);
		}

		g_object_unref(path);
	}

#ifdef _WIN32
	if (savedialog) {
		current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_save_folder");
	} else {
		current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_open_folder");
	}

	if ((filename == NULL || *filename == '\0' || !g_file_test(filename, G_FILE_TEST_EXISTS)) &&
				(current_folder != NULL) && (*current_folder != '\0')) {
		folder_set = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel), current_folder);
	}

	if (!folder_set && (filename == NULL || *filename == '\0' || !g_file_test(filename, G_FILE_TEST_EXISTS))) {
		char *my_documents = wpurple_get_special_folder(CSIDL_PERSONAL);

		if (my_documents != NULL) {
			gtk_file_chooser_set_current_folder(
					GTK_FILE_CHOOSER(filesel), my_documents);

			g_free(my_documents);
		}
	}
#endif

	g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(filesel)), "response",
					 G_CALLBACK(file_ok_check_if_exists_cb), data);

#if 0
	/* FIXME: Not implemented for native dialogs. */
	pidgin_auto_parent_window(filesel);
#endif

	data->dialog = filesel;
	gtk_native_dialog_show(GTK_NATIVE_DIALOG(filesel));

	return (void *)data;
}

static void *
pidgin_request_folder(const char *title, const char *dirname, GCallback ok_cb,
	GCallback cancel_cb, PurpleRequestCommonParameters *cpar,
	void *user_data)
{
	PidginRequestData *data;
	GtkFileChooserNative *dirsel;

	data = g_new0(PidginRequestData, 1);
	data->type = PURPLE_REQUEST_FOLDER;
	data->user_data = user_data;
	data->cb_count = 2;
	data->cbs = g_new0(GCallback, 2);
	data->cbs[0] = cancel_cb;
	data->cbs[1] = ok_cb;
	data->u.file.savedialog = FALSE;

	dirsel = gtk_file_chooser_native_new(
	        title ? title : _("Select Folder..."), NULL,
	        GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("_OK"), _("_Cancel"));

	if ((dirname != NULL) && (*dirname != '\0')) {
		GFile *path = g_file_new_for_path(dirname);

		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirsel), path,
		                                    NULL);

		g_object_unref(path);
	}

	g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(dirsel)), "response",
						G_CALLBACK(file_ok_check_if_exists_cb), data);

	data->dialog = dirsel;
#if 0
	/* FIXME: Not implemented for native dialogs. */
	pidgin_auto_parent_window(dirsel);
#endif

	gtk_native_dialog_show(GTK_NATIVE_DIALOG(dirsel));

	return (void *)data;
}

/* if request callback issues another request, it should be attached to the
 * primary request parent */
static void
pidgin_window_detach_children(GtkWindow* win)
{
	GList *it;
	GtkWindow *par;

	g_return_if_fail(win != NULL);

	par = gtk_window_get_transient_for(win);
	it = gtk_window_list_toplevels();
	for (it = g_list_first(it); it != NULL; it = g_list_delete_link(it, it)) {
		GtkWindow *child = GTK_WINDOW(it->data);
		if (gtk_window_get_transient_for(child) != win)
			continue;
		if (gtk_window_get_destroy_with_parent(child)) {
#ifdef _WIN32
			/* XXX test/verify it: Win32 gtk ignores
			 * gtk_window_set_destroy_with_parent(..., FALSE). */
			gtk_window_set_transient_for(child, NULL);
#endif
			continue;
		}
		gtk_window_set_transient_for(child, par);
	}
}

static void
pidgin_close_request(PurpleRequestType type, void *ui_handle)
{
	PidginRequestData *data = (PidginRequestData *)ui_handle;

	g_free(data->cbs);

	if (type == PURPLE_REQUEST_FILE || type == PURPLE_REQUEST_FOLDER) {
		/* Will be a GtkNativeDialog, not GtkDialog. */
		g_object_unref(data->dialog);
	} else {
		pidgin_window_detach_children(GTK_WINDOW(data->dialog));

		gtk_window_destroy(GTK_WINDOW(data->dialog));
	}

	if (type == PURPLE_REQUEST_FIELDS)
		purple_request_fields_destroy(data->u.multifield.fields);
	else if (type == PURPLE_REQUEST_FILE)
		g_free(data->u.file.name);

	g_free(data);
}

GtkWindow *
pidgin_request_get_dialog_window(void *ui_handle)
{
	PidginRequestData *data = ui_handle;

	g_return_val_if_fail(
		purple_request_is_valid_ui_handle(data, NULL), NULL);

	if (data->type == PURPLE_REQUEST_FILE ||
	    data->type == PURPLE_REQUEST_FOLDER) {
		/* Not a GtkWidget, but a GtkFileChooserNative. Eventually this function
		 * should not be needed, once we don't need to auto-parent. */
		return NULL;
	}

	return GTK_WINDOW(data->dialog);
}

static PurpleRequestUiOps ops =
{
	PURPLE_REQUEST_FEATURE_HTML,
	pidgin_request_input,
	pidgin_request_choice,
	pidgin_request_action,
	pidgin_request_wait,
	pidgin_request_wait_update,
	pidgin_request_fields,
	pidgin_request_file,
	pidgin_request_folder,
	pidgin_close_request,
	NULL,
	NULL,
	NULL,
	NULL
};

PurpleRequestUiOps *
pidgin_request_get_ui_ops(void)
{
	return &ops;
}

void *
pidgin_request_get_handle(void)
{
	static int handle;

	return &handle;
}

static void
pidgin_request_datasheet_stock_remove(gpointer obj)
{
	if (obj == NULL)
		return;
	g_object_unref(obj);
}

void
pidgin_request_init(void)
{
	datasheet_stock = g_hash_table_new_full(g_str_hash, g_str_equal,
		g_free, pidgin_request_datasheet_stock_remove);
}

void
pidgin_request_uninit(void)
{
	purple_signals_disconnect_by_handle(pidgin_request_get_handle());
	g_hash_table_destroy(datasheet_stock);
	datasheet_stock = NULL;
}

mercurial