pidgin/plugins/disco/gtkdisco.c

Mon, 23 Jan 2023 20:51:42 -0600

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Mon, 23 Jan 2023 20:51:42 -0600
changeset 42034
f05baca349b3
parent 41962
f802660eaef2
child 42037
af20db93f2c2
permissions
-rw-r--r--

Use icon-name to set up XMPP Discovery icons instead

This will make it easier to move away from `GtkTreeView`.

Testing Done:
Fetched discovery results from `pidgin.im` and the entries had icons (though I think only chats exist on there.)

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

/*
 * 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/>.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <glib/gi18n-lib.h>

#include <purple.h>

#include <pidgin.h>

#include "gtkdisco.h"
#include "xmppdisco.h"

GList *dialogs = NULL;

enum {
	ICON_NAME_COLUMN = 0,
	NAME_COLUMN,
	DESCRIPTION_COLUMN,
	SERVICE_COLUMN,
	NUM_OF_COLUMNS
};

static PidginDiscoList *
pidgin_disco_list_new(void) {
	return g_rc_box_new0(PidginDiscoList);
}

static void
pidgin_disco_list_destroy(PidginDiscoList *list)
{
	g_hash_table_destroy(list->services);
	if (list->dialog && list->dialog->discolist == list) {
		list->dialog->discolist = NULL;
	}

	g_free((gchar*)list->server);
}

PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list)
{
	g_return_val_if_fail(list != NULL, NULL);

	purple_debug_misc("xmppdisco", "reffing list");
	return g_rc_box_acquire(list);
}

void pidgin_disco_list_unref(PidginDiscoList *list)
{
	g_return_if_fail(list != NULL);

	purple_debug_misc("xmppdisco", "unreffing list");
	g_rc_box_release_full(list, (GDestroyNotify)pidgin_disco_list_destroy);
}

void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress)
{
	PidginDiscoDialog *dialog = list->dialog;

	if (!dialog) {
		return;
	}

	list->in_progress = in_progress;

	if (in_progress) {
		gtk_widget_set_sensitive(dialog->account_chooser, FALSE);
		g_simple_action_set_enabled(dialog->stop_action, TRUE);
		g_simple_action_set_enabled(dialog->browse_action, FALSE);
	} else {
		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0);

		gtk_widget_set_sensitive(dialog->account_chooser, TRUE);

		g_simple_action_set_enabled(dialog->stop_action, FALSE);
		g_simple_action_set_enabled(dialog->browse_action, TRUE);
/*
		g_simple_action_set_enabled(dialog->register_action, FALSE);
		g_simple_action_set_enabled(dialog->add_action, FALSE);
*/
	}
}

static char *
pidgin_disco_get_icon_name(XmppDiscoService *service)
{
	char *icon_name = NULL;

	g_return_val_if_fail(service != NULL, NULL);

	if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) {
		icon_name = g_strconcat("im-", service->gateway_type, NULL);
#if 0
	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) {
		icon_name = g_strdup("person");
#endif
	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) {
		icon_name = g_strdup("chat");
	}

	return icon_name;
}

static void
dialog_select_account_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec,
                         gpointer data)
{
	PidginDiscoDialog *dialog = data;
	PurpleAccount *account = pidgin_account_chooser_get_selected(PIDGIN_ACCOUNT_CHOOSER(obj));
	gboolean change = (account != dialog->account);
	dialog->account = account;
	g_simple_action_set_enabled(dialog->browse_action, account != NULL);

	if (change) {
		g_clear_pointer(&dialog->discolist, pidgin_disco_list_unref);
	}
}

static void
activate_register(G_GNUC_UNUSED GSimpleAction *action,
                  G_GNUC_UNUSED GVariant *parameter,
                  gpointer data)
{
	PidginDiscoDialog *dialog = data;
	XmppDiscoService *service = dialog->selected;

	g_return_if_fail(service != NULL);

	xmpp_disco_service_register(service);
}

static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
{
	pdl->dialog->prompt_handle = NULL;

	pidgin_disco_list_set_in_progress(pdl, FALSE);
	pidgin_disco_list_unref(pdl);
}

static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
{
	pdl->dialog->prompt_handle = NULL;
	g_simple_action_set_enabled(pdl->dialog->browse_action, TRUE);

	if (!server || !*server) {
		purple_notify_error(my_plugin, _("Invalid Server"), _("Invalid Server"),
			NULL, purple_request_cpar_from_connection(pdl->pc));

		pidgin_disco_list_set_in_progress(pdl, FALSE);
		pidgin_disco_list_unref(pdl);
		return;
	}

	pdl->server = g_strdup(server);
	pidgin_disco_list_set_in_progress(pdl, TRUE);
	xmpp_disco_start(pdl);
}

static void
activate_browse(G_GNUC_UNUSED GSimpleAction *action,
                G_GNUC_UNUSED GVariant *parameter,
                gpointer data)
{
	PidginDiscoDialog *dialog = data;
	PurpleConnection *pc;
	PurpleContactInfo *info = NULL;
	PidginDiscoList *pdl;
	const char *username;
	const char *at, *slash;
	char *server = NULL;

	pc = purple_account_get_connection(dialog->account);
	if (!pc) {
		return;
	}

	g_simple_action_set_enabled(dialog->browse_action, FALSE);
	g_simple_action_set_enabled(dialog->add_action, FALSE);
	g_simple_action_set_enabled(dialog->register_action, FALSE);

	g_clear_pointer(&dialog->discolist, pidgin_disco_list_unref);
	gtk_tree_store_clear(dialog->model);

	pdl = dialog->discolist = pidgin_disco_list_new();
	pdl->services = g_hash_table_new_full(NULL, NULL, NULL,
			(GDestroyNotify)gtk_tree_row_reference_free);
	pdl->pc = pc;
	/* We keep a copy... */
	pidgin_disco_list_ref(pdl);

	pdl->dialog = dialog;

	gtk_widget_set_sensitive(dialog->account_chooser, FALSE);

	info = PURPLE_CONTACT_INFO(dialog->account);
	username = purple_contact_info_get_username(info);
	at = strchr(username, '@');
	slash = strchr(username, '/');
	if (at && !slash) {
		server = g_strdup(at + 1);
	} else if (at && slash && at + 1 < slash) {
		server = g_strdup_printf("%.*s", (int)(slash - (at + 1)), at + 1);
	}

	if (server == NULL) {
		/* This shouldn't ever happen since the account is connected */
		server = g_strdup("jabber.org");
	}

	/* Translators: The string "Enter an XMPP Server" is asking the user to
	   type the name of an XMPP server which will then be queried */
	dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
			_("Select an XMPP server to query"),
			server, FALSE, FALSE, NULL,
			_("Find Services"), G_CALLBACK(discolist_ok_cb),
			_("Cancel"), G_CALLBACK(discolist_cancel_cb),
			purple_request_cpar_from_connection(pc), pdl);

	g_free(server);
}

static void
activate_add_to_blist(G_GNUC_UNUSED GSimpleAction *action,
                      G_GNUC_UNUSED GVariant *parameter,
                      gpointer data)
{
	PidginDiscoDialog *dialog = data;
	XmppDiscoService *service = dialog->selected;
	PurpleAccount *account;
	const char *jid;

	g_return_if_fail(service != NULL);

	account = purple_connection_get_account(service->list->pc);
	jid = service->jid;

	if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) {
		purple_blist_request_add_chat(account, NULL, NULL, jid);
	} else {
		purple_blist_request_add_buddy(account, jid, NULL, NULL);
	}
}

static gboolean
service_click_cb(G_GNUC_UNUSED GtkGestureClick *click,
                 G_GNUC_UNUSED gint n_press,
                 gdouble x, gdouble y, gpointer data)
{
	PidginDiscoDialog *dialog = data;
	XmppDiscoService *service;

	GtkTreePath *path;
	GtkTreeIter iter;
	GValue val;

	GdkRectangle rect;

	/* Figure out what was clicked */
	if (!gtk_tree_view_get_path_at_pos(dialog->tree, (gint)x, (gint)y, &path,
	                                   NULL, NULL, NULL))
	{
		return FALSE;
	}
	gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
	gtk_tree_path_free(path);
	val.g_type = 0;
	gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
	                         SERVICE_COLUMN, &val);
	service = g_value_get_pointer(&val);

	if (!service) {
		return FALSE;
	}

	gtk_tree_view_convert_bin_window_to_widget_coords(dialog->tree,
	                                                  (gint)x, (gint)y,
	                                                  &rect.x, &rect.y);
	rect.width = rect.height = 1;

	gtk_popover_set_pointing_to(GTK_POPOVER(dialog->popover), &rect);
	gtk_popover_popup(GTK_POPOVER(dialog->popover));

	return FALSE;
}

static void
selection_changed_cb(GtkTreeSelection *selection, PidginDiscoDialog *dialog)
{
	GtkTreeIter iter;
	GValue val;
	gboolean allow_add = FALSE, allow_register = FALSE;

	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
		val.g_type = 0;
		gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
		                         SERVICE_COLUMN, &val);
		dialog->selected = g_value_get_pointer(&val);
		if (dialog->selected != NULL) {
			allow_add = (dialog->selected->flags & XMPP_DISCO_ADD);
			allow_register = (dialog->selected->flags & XMPP_DISCO_REGISTER);
		}
	}

	g_simple_action_set_enabled(dialog->add_action, allow_add);
	g_simple_action_set_enabled(dialog->register_action, allow_register);
}

static void
row_expanded_cb(GtkTreeView *tree, GtkTreeIter *arg1, GtkTreePath *rg2,
                gpointer user_data)
{
	PidginDiscoDialog *dialog = user_data;
	XmppDiscoService *service;
	GValue val;

	val.g_type = 0;
	gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), arg1,
	                         SERVICE_COLUMN, &val);
	service = g_value_get_pointer(&val);
	xmpp_disco_service_expand(service);
}

static void
row_activated_cb(GtkTreeView       *tree_view,
                 GtkTreePath       *path,
                 GtkTreeViewColumn *column,
                 gpointer           user_data)
{
	PidginDiscoDialog *dialog = user_data;
	GtkTreeIter iter;
	XmppDiscoService *service;
	GValue val;

	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path)) {
		return;
	}

	val.g_type = 0;
	gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
	                         SERVICE_COLUMN, &val);
	service = g_value_get_pointer(&val);

	if (service->flags & XMPP_DISCO_BROWSE) {
		if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dialog->tree), path)) {
			gtk_tree_view_collapse_row(GTK_TREE_VIEW(dialog->tree), path);
		} else {
			gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog->tree), path, FALSE);
		}
	} else if (service->flags & XMPP_DISCO_REGISTER) {
		g_action_activate(G_ACTION(dialog->register_action), NULL);
	} else if (service->flags & XMPP_DISCO_ADD) {
		g_action_activate(G_ACTION(dialog->add_action), NULL);
	}
}

static void
destroy_win_cb(GtkWidget *window, G_GNUC_UNUSED gpointer data)
{
	PidginDiscoDialog *dialog = PIDGIN_DISCO_DIALOG(window);
	PidginDiscoList *list = dialog->discolist;

	if (dialog->prompt_handle) {
		purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
	}

	if (list) {
		list->dialog = NULL;

		if (list->in_progress) {
			list->in_progress = FALSE;
		}

		pidgin_disco_list_unref(list);
	}

	dialogs = g_list_remove(dialogs, dialog);
}

static void
activate_stop(G_GNUC_UNUSED GSimpleAction *action,
              G_GNUC_UNUSED GVariant *parameter,
              gpointer data)
{
	PidginDiscoDialog *dialog = data;

	pidgin_disco_list_set_in_progress(dialog->discolist, FALSE);
}

static gboolean
disco_query_tooltip(GtkWidget *widget, int x, int y, gboolean keyboard_mode,
                    GtkTooltip *tooltip, gpointer data)
{
	PidginDiscoDialog *dialog = data;
	GtkTreePath *path = NULL;
	GtkTreeIter iter;
	XmppDiscoService *service;
	GValue val;
	const char *type = NULL;
	char *markup, *jid, *name, *desc = NULL;

	if (keyboard_mode) {
		GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
		if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
			return FALSE;
		}
		path = gtk_tree_model_get_path(GTK_TREE_MODEL(dialog->model), &iter);
	} else {
		gint bx, by;

		gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(widget),
		                                                  x, y, &bx, &by);
		gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bx, by, &path,
		                              NULL, NULL, NULL);
		if (path == NULL) {
			return FALSE;
		}

		if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path)) {
			gtk_tree_path_free(path);
			return FALSE;
		}
	}

	val.g_type = 0;
	gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
	                         SERVICE_COLUMN, &val);
	service = g_value_get_pointer(&val);
	if (!service) {
		gtk_tree_path_free(path);
		return FALSE;
	}

	switch (service->type) {
		case XMPP_DISCO_SERVICE_TYPE_UNSET:
			type = _("Unknown");
			break;

		case XMPP_DISCO_SERVICE_TYPE_GATEWAY:
			type = _("Gateway");
			break;

		case XMPP_DISCO_SERVICE_TYPE_DIRECTORY:
			type = _("Directory");
			break;

		case XMPP_DISCO_SERVICE_TYPE_CHAT:
			type = _("Chat");
			break;

		case XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION:
			type = _("PubSub Collection");
			break;

		case XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF:
			type = _("PubSub Leaf");
			break;

		case XMPP_DISCO_SERVICE_TYPE_OTHER:
			type = _("Other");
			break;
	}

	name = g_markup_escape_text(service->name, -1);
	jid = g_markup_escape_text(service->jid, -1);
	if (service->description != NULL) {
		desc = g_markup_escape_text(service->description, -1);
	}

	markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s%s%s",
	                         name, type, jid,
	                         service->description ? _("\n<b>Description:</b> ") : "",
	                         service->description ? desc : "");

	gtk_tooltip_set_markup(tooltip, markup);
	gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path);

	g_free(markup);
	g_free(jid);
	g_free(name);
	g_free(desc);
	gtk_tree_path_free(path);

	return TRUE;
}

void pidgin_disco_signed_off_cb(PurpleConnection *pc)
{
	GList *node;

	for (node = dialogs; node; node = node->next) {
		PidginDiscoDialog *dialog = node->data;
		PidginDiscoList *list = dialog->discolist;

		if (list && list->pc == pc) {
			PurpleAccount *account = NULL;

			if (list->in_progress) {
				pidgin_disco_list_set_in_progress(list, FALSE);
			}

			gtk_tree_store_clear(dialog->model);

			pidgin_disco_list_unref(list);
			dialog->discolist = NULL;

			account = pidgin_account_chooser_get_selected(
				PIDGIN_ACCOUNT_CHOOSER(dialog->account_chooser));

			g_simple_action_set_enabled(dialog->browse_action, account != NULL);
			g_simple_action_set_enabled(dialog->add_action, FALSE);
			g_simple_action_set_enabled(dialog->register_action, FALSE);
		}
	}
}

void pidgin_disco_dialogs_destroy_all(void)
{
	while (dialogs) {
		GtkWidget *dialog = dialogs->data;

		gtk_window_destroy(GTK_WINDOW(dialog));
		/* destroy_win_cb removes the dialog from the list */
	}
}

/******************************************************************************
 * GObject implementation
 *****************************************************************************/

G_DEFINE_DYNAMIC_TYPE(PidginDiscoDialog, pidgin_disco_dialog, GTK_TYPE_DIALOG)

static void
pidgin_disco_dialog_class_init(PidginDiscoDialogClass *klass)
{
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

	gtk_widget_class_set_template_from_resource(
	        widget_class, "/im/pidgin/Pidgin3/Plugin/XMPPDisco/disco.ui");

	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
	                                     account_chooser);
	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
	                                     progress);
	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
	                                     tree);
	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
	                                     model);
	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
	                                     popover);

	gtk_widget_class_bind_template_callback(widget_class, destroy_win_cb);
	gtk_widget_class_bind_template_callback(widget_class,
	                                        dialog_select_account_cb);
	gtk_widget_class_bind_template_callback(widget_class, row_activated_cb);
	gtk_widget_class_bind_template_callback(widget_class, row_expanded_cb);
	gtk_widget_class_bind_template_callback(widget_class, service_click_cb);
	gtk_widget_class_bind_template_callback(widget_class,
	                                        selection_changed_cb);
}

static void
pidgin_disco_dialog_class_finalize(PidginDiscoDialogClass *klass)
{
}

static void
pidgin_disco_dialog_init(PidginDiscoDialog *dialog)
{
	GActionEntry entries[] = {
		{ .name = "stop", .activate = activate_stop },
		{ .name = "browse", .activate = activate_browse },
		{ .name = "register", .activate = activate_register },
		{ .name = "add", .activate = activate_add_to_blist },
	};
	GSimpleActionGroup *action_group = NULL;
	GActionMap *action_map = NULL;

	dialogs = g_list_prepend(dialogs, dialog);

	gtk_widget_init_template(GTK_WIDGET(dialog));

	/* accounts dropdown list */
	dialog->account = pidgin_account_chooser_get_selected(
		PIDGIN_ACCOUNT_CHOOSER(dialog->account_chooser));

	gtk_widget_set_has_tooltip(GTK_WIDGET(dialog->tree), TRUE);
	g_signal_connect(G_OBJECT(dialog->tree), "query-tooltip",
	                 G_CALLBACK(disco_query_tooltip), dialog);

	action_group = g_simple_action_group_new();
	action_map = G_ACTION_MAP(action_group);
	g_action_map_add_action_entries(action_map, entries, G_N_ELEMENTS(entries),
	                                dialog);

	dialog->stop_action = G_SIMPLE_ACTION(
			g_action_map_lookup_action(action_map, "stop"));
	g_simple_action_set_enabled(dialog->stop_action, FALSE);

	dialog->browse_action = G_SIMPLE_ACTION(
			g_action_map_lookup_action(action_map, "browse"));
	g_simple_action_set_enabled(dialog->browse_action, dialog->account != NULL);

	dialog->register_action = G_SIMPLE_ACTION(
			g_action_map_lookup_action(action_map, "register"));
	g_simple_action_set_enabled(dialog->register_action, FALSE);

	dialog->add_action = G_SIMPLE_ACTION(
			g_action_map_lookup_action(action_map, "add"));
	g_simple_action_set_enabled(dialog->add_action, FALSE);

	gtk_widget_insert_action_group(GTK_WIDGET(dialog), "disco",
	                               G_ACTION_GROUP(action_group));
}

/******************************************************************************
 * Public API
 *****************************************************************************/

void
pidgin_disco_dialog_register(PurplePlugin *plugin)
{
	pidgin_disco_dialog_register_type(G_TYPE_MODULE(plugin));
}

PidginDiscoDialog *
pidgin_disco_dialog_new(void)
{
	PidginDiscoDialog *dialog = g_object_new(PIDGIN_TYPE_DISCO_DIALOG, NULL);
	gtk_widget_show(GTK_WIDGET(dialog));
	return dialog;
}

void pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, XmppDiscoService *parent)
{
	PidginDiscoDialog *dialog;
	GtkTreeIter iter, parent_iter, child;
	char *icon_name = NULL;
	gboolean append = TRUE;

	dialog = pdl->dialog;
	g_return_if_fail(dialog != NULL);

	if (service != NULL) {
		purple_debug_info("xmppdisco", "Adding service \"%s\"", service->name);
	} else {
		purple_debug_info("xmppdisco", "Service \"%s\" has no children",
		                  parent->name);
	}

	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress));

	if (parent) {
		GtkTreeRowReference *rr;
		GtkTreePath *path;

		rr = g_hash_table_lookup(pdl->services, parent);
		path = gtk_tree_row_reference_get_path(rr);
		if (path) {
			gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model),
			                        &parent_iter, path);
			gtk_tree_path_free(path);

			if (gtk_tree_model_iter_children(
			            GTK_TREE_MODEL(dialog->model), &child,
			            &parent_iter)) {
				PidginDiscoList *tmp;
				gtk_tree_model_get(
				        GTK_TREE_MODEL(dialog->model), &child,
				        SERVICE_COLUMN, &tmp, -1);
				if (!tmp) {
					append = FALSE;
				}
			}
		}
	}

	if (service == NULL) {
		if (parent != NULL && !append) {
			gtk_tree_store_remove(dialog->model, &child);
		}
		return;
	}

	if (append) {
		gtk_tree_store_append(dialog->model, &iter,
		                      (parent ? &parent_iter : NULL));
	} else {
		iter = child;
	}

	if (service->flags & XMPP_DISCO_BROWSE) {
		GtkTreeRowReference *rr;
		GtkTreePath *path;

		gtk_tree_store_append(dialog->model, &child, &iter);

		path = gtk_tree_model_get_path(GTK_TREE_MODEL(dialog->model),
		                               &iter);
		rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(dialog->model),
		                                path);
		g_hash_table_insert(pdl->services, service, rr);
		gtk_tree_path_free(path);
	}

	icon_name = pidgin_disco_get_icon_name(service);

	gtk_tree_store_set(dialog->model, &iter, ICON_NAME_COLUMN, icon_name,
	                   NAME_COLUMN, service->name, DESCRIPTION_COLUMN,
	                   service->description, SERVICE_COLUMN, service, -1);

	g_free(icon_name);
}

mercurial