changeset 26254:c619bef09bec

Make the prpl be in charge of creating the PurpleDiscoList; UI data is allocated via ops->create. When the prpl sets protocol data, it also passed in a close_cb which is called when destroying the struct to clean up the protocol data. This still works, but I've introduced some bugs with cancelling the disco or closing the GtkDisco dialog
author Paul Aurich <paul@darkrain42.org>
date Tue, 31 Mar 2009 05:18:09 +0000
parents 290ea90869c0
children de05bdd931ed
files libpurple/disco.c libpurple/disco.h libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/disco.h libpurple/prpl.h pidgin/gtkdisco.c
diffstat 6 files changed, 223 insertions(+), 142 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/disco.c	Mon Mar 30 02:36:02 2009 +0000
+++ b/libpurple/disco.c	Tue Mar 31 05:18:09 2009 +0000
@@ -43,7 +43,8 @@
 	gboolean in_progress;
 
 	gpointer ui_data; /**< UI private data. */
-	gpointer proto_data; /** Prpl private data. */
+	gpointer proto_data; /**< Prpl private data. */
+	PurpleDiscoCloseCallback close_cb; /**< Callback to free the prpl data */
 	guint ref; /**< The reference count. */
 };
 
@@ -62,7 +63,7 @@
 
 static PurpleDiscoUiOps *ops = NULL;
 
-PurpleDiscoList *purple_disco_list_new(PurpleAccount *account, void *ui_data)
+PurpleDiscoList *purple_disco_list_new(PurpleAccount *account)
 {
 	PurpleDiscoList *list;
 
@@ -71,7 +72,6 @@
 	list = g_new0(PurpleDiscoList, 1);
 	list->account = account;
 	list->ref = 1;
-	list->ui_data = ui_data;
 
 	if (ops && ops->create)
 		ops->create(list);
@@ -105,6 +105,9 @@
 	if (ops && ops->destroy)
 		ops->destroy(list);
 
+	if (list->close_cb)
+		list->close_cb(list);
+
 	for (l = list->services; l; l = l->next) {
 		PurpleDiscoService *s = l->data;
 		purple_disco_list_service_destroy(list, s);
@@ -156,25 +159,21 @@
 	return s;
 }
 
-void purple_disco_get_list(PurpleDiscoList *list)
+PurpleDiscoList *purple_disco_get_list(PurpleConnection *pc)
 {
-	PurpleConnection *pc = NULL;
 	PurplePlugin *prpl = NULL;
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
-	g_return_if_fail(list != NULL);
-
-	pc = purple_account_get_connection(list->account);
-
-	g_return_if_fail(pc != NULL);
+	g_return_val_if_fail(pc != NULL, NULL);
 
 	prpl = purple_connection_get_prpl(pc);
-
 	if (prpl != NULL)
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 
 	if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, disco_get_list))
-		prpl_info->disco_get_list(pc, list);
+		return prpl_info->disco_get_list(pc);
+
+	return NULL;
 }
 
 void purple_disco_cancel_get_list(PurpleDiscoList *list)
@@ -305,11 +304,13 @@
 }
 
 void purple_disco_list_set_protocol_data(PurpleDiscoList *list,
-                                         gpointer proto_data)
+                                         gpointer proto_data,
+                                         PurpleDiscoCloseCallback cb)
 {
 	g_return_if_fail(list != NULL);
 
 	list->proto_data = proto_data;
+	list->close_cb   = cb;
 }
 
 gpointer purple_disco_list_get_protocol_data(PurpleDiscoList *list)
--- a/libpurple/disco.h	Mon Mar 30 02:36:02 2009 +0000
+++ b/libpurple/disco.h	Tue Mar 31 05:18:09 2009 +0000
@@ -35,6 +35,11 @@
 #include "account.h"
 
 /**
+ *
+ */
+typedef void  (*PurpleDiscoCloseCallback) (PurpleDiscoList *list);
+
+/**
  * The categories of services.
  */
 typedef enum
@@ -104,7 +109,7 @@
  * @param account The account that's listing rooms.
  * @return The new service discovery list handle.
  */
-PurpleDiscoList *purple_disco_list_new(PurpleAccount *account, void *ui_data);
+PurpleDiscoList *purple_disco_list_new(PurpleAccount *account);
 
 /**
  * Increases the reference count on the service discovery list.
@@ -127,7 +132,7 @@
  * Instructs the prpl to start fetching the list.
  *
  */
-void purple_disco_get_list(PurpleDiscoList *list);
+PurpleDiscoList *purple_disco_get_list(PurpleConnection *gc);
 
 /**
  * Tells the prpl to stop fetching the list.
@@ -280,13 +285,17 @@
  *
  * This should only be called from the associated prpl.
  *
- * @param list The disco list.
- * @param data The protocol data.
+ * @param list     The disco list.
+ * @param data     The protocol data.
+ * @param close_cb The function to be called when destroying the disco list to
+ *                 free the protocol data. May be NULL if the data does not need
+ *                 to be freed.
  *
  * @see purple_disco_list_get_protocol_data()
  * @since TODO
  */
-void purple_disco_list_set_protocol_data(PurpleDiscoList *list, gpointer data);
+void purple_disco_list_set_protocol_data(PurpleDiscoList *list, gpointer data,
+                                         PurpleDiscoCloseCallback cb);
 
 /**
  * Returns the disco list's protocol-specific data.
--- a/libpurple/protocols/jabber/disco.c	Mon Mar 30 02:36:02 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Tue Mar 31 05:18:09 2009 +0000
@@ -48,6 +48,8 @@
 }
 
 struct jabber_disco_list_data {
+	JabberStream *js;
+	PurpleDiscoList *list;
 	char *server;
 	int fetch_count;
 };
@@ -408,7 +410,7 @@
 }
 
 struct _disco_data {
-	PurpleDiscoList *list;
+	struct jabber_disco_list_data *list_data;
 	PurpleDiscoService *parent;
 	char *node;
 };
@@ -578,12 +580,19 @@
 static void
 jabber_disco_list_data_destroy(struct jabber_disco_list_data *data)
 {
-	g_return_if_fail(data != NULL);
-
 	g_free(data->server);
 	g_free(data);
 }
 
+static void
+disco_proto_data_destroy_cb(PurpleDiscoList *list)
+{
+	struct jabber_disco_list_data *data;
+	
+	data = purple_disco_list_get_protocol_data(list);
+	jabber_disco_list_data_destroy(data);
+}
+
 static PurpleDiscoServiceCategory
 jabber_disco_category_from_string(const gchar *str)
 {
@@ -632,7 +641,7 @@
 {
 	struct _disco_data *disco_data = data;
 	struct jabber_disco_list_data *list_data;
-	PurpleDiscoList *list = disco_data->list;
+	PurpleDiscoList *list;	
 	PurpleDiscoService *parent = disco_data->parent;
 	const char *parent_node = disco_data->node;
 	xmlnode *query = xmlnode_get_child(packet, "query");
@@ -641,16 +650,14 @@
 	xmlnode *child;
 	gboolean has_items = FALSE;
 
-	list_data = purple_disco_list_get_protocol_data(list);
+	list_data = disco_data->list_data;
+	list = list_data->list;
+
 	--list_data->fetch_count;
 
 	if (!from || !result || !query || strcmp(result, "result") != 0) {
-		if (list_data->fetch_count == 0) {
-			jabber_disco_list_data_destroy(list_data);
-			purple_disco_list_set_protocol_data(list, NULL);
-
+		if (list_data->fetch_count == 0)
 			purple_disco_list_set_in_progress(list, FALSE);
-		}
 
 		purple_disco_list_unref(list);
 		return;
@@ -685,7 +692,7 @@
 		}
 
 		disco_data = g_new0(struct _disco_data, 1);
-		disco_data->list = list;
+		disco_data->list_data = list_data;
 		disco_data->parent = parent;
 		disco_data->node = full_node;
 
@@ -701,12 +708,8 @@
 		jabber_iq_send(iq);
 	}
 
-	if (list_data->fetch_count == 0) {
-		jabber_disco_list_data_destroy(list_data);
-		purple_disco_list_set_protocol_data(list, NULL);
-
+	if (list_data->fetch_count == 0)
 		purple_disco_list_set_in_progress(list, FALSE);
-	}
 
 	purple_disco_list_unref(list);
 
@@ -719,7 +722,7 @@
 {
 	struct _disco_data *disco_data = data;
 	struct jabber_disco_list_data *list_data;
-	PurpleDiscoList *list = disco_data->list;
+	PurpleDiscoList *list;
 	PurpleDiscoService *parent = disco_data->parent;
 	char *node = g_strdup(disco_data->node);
 	xmlnode *query, *ident, *child;
@@ -732,21 +735,19 @@
 	PurpleDiscoServiceType type;
 	PurpleDiscoServiceFlags flags = PURPLE_DISCO_ADD;
 
+	list_data = disco_data->list_data;
+	list = list_data->list;
+
 	g_free(disco_data->node);
 	g_free(disco_data);
 
-	list_data = purple_disco_list_get_protocol_data(list);
 	--list_data->fetch_count;
 
 	if (!from || !result || strcmp(result, "result") != 0
 			|| (!(query = xmlnode_get_child(packet, "query")))
 			|| (!(ident = xmlnode_get_child(query, "identity")))) {
-		if (list_data->fetch_count == 0) {
-			jabber_disco_list_data_destroy(list_data);
-			purple_disco_list_set_protocol_data(list, NULL);
-
+		if (list_data->fetch_count == 0)
 			purple_disco_list_set_in_progress(list, FALSE);
-		}
 
 		purple_disco_list_unref(list);
 		return;
@@ -802,7 +803,7 @@
 		++list_data->fetch_count;
 		purple_disco_list_ref(list);
 		disco_data = g_new0(struct _disco_data, 1);
-		disco_data->list = list;
+		disco_data->list_data = list_data;
 		disco_data->parent = s;
 
 		xmlnode_set_attrib(iq->node, "to", from);
@@ -812,12 +813,8 @@
 		jabber_iq_send(iq);
 	}
 
-	if (list_data->fetch_count == 0) {
-		jabber_disco_list_data_destroy(list_data);
-		purple_disco_list_set_protocol_data(list, NULL);
-
+	if (list_data->fetch_count == 0)
 		purple_disco_list_set_in_progress(list, FALSE);
-	}
 
 	purple_disco_list_unref(list);
 
@@ -828,7 +825,6 @@
 static void
 jabber_disco_server_items_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
-	PurpleDiscoList *list = data;
 	struct jabber_disco_list_data *list_data;
 	xmlnode *query, *child;
 	const char *from = xmlnode_get_attrib(packet, "from");
@@ -841,7 +837,15 @@
 	if (strcmp(type, "result"))
 		return;
 
-	list_data = purple_disco_list_get_protocol_data(list);
+	list_data = data;
+	--list_data->fetch_count;
+
+	if (list_data->list == NULL) {
+		if (list_data->fetch_count == 0)
+			jabber_disco_list_data_destroy(list_data);
+
+		return;
+	}
 
 	query = xmlnode_get_child(packet, "query");
 
@@ -851,15 +855,15 @@
 		const char *jid;
 		struct _disco_data *disco_data;
 
-		if(!(jid = xmlnode_get_attrib(child, "jid")) || !purple_disco_list_get_protocol_data(list))
+		if(!(jid = xmlnode_get_attrib(child, "jid")))
 			continue;
 
 		disco_data = g_new0(struct _disco_data, 1);
-		disco_data->list = list;
+		disco_data->list_data = list_data;
 
 		has_items = TRUE;
 		++list_data->fetch_count;
-		purple_disco_list_ref(list);
+		purple_disco_list_ref(list_data->list);
 		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
 		xmlnode_set_attrib(iq->node, "to", jid);
 		jabber_iq_set_callback(iq, jabber_disco_service_info_cb, disco_data);
@@ -868,89 +872,120 @@
 	}
 
 	if (!has_items)
-		purple_disco_list_set_in_progress(list, FALSE);
+		purple_disco_list_set_in_progress(list_data->list, FALSE);
 
-	purple_disco_list_unref(list);
+	purple_disco_list_unref(list_data->list);
 }
 
 static void
 jabber_disco_server_info_cb(JabberStream *js, const char *who, JabberCapabilities caps, gpointer data)
 {
-	PurpleDiscoList *list = data;
-	JabberIq *iq;
+	struct jabber_disco_list_data *list_data;
+
+	list_data = data;
+	--list_data->fetch_count;
+
+	if (!list_data->list) {
+		purple_disco_list_unref(list_data->list);
+
+		if (list_data->fetch_count == 0)
+			jabber_disco_list_data_destroy(list_data);
+
+		return;
+	}
 
 	if (caps & JABBER_CAP_ITEMS) {
-		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+		JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
 		xmlnode_set_attrib(iq->node, "to", who);
-		jabber_iq_set_callback(iq, jabber_disco_server_items_cb, list);
-	
-		if (purple_disco_list_get_protocol_data(list))
-			jabber_iq_send(iq);
-		else
-			purple_disco_list_unref(list);
-	
+		jabber_iq_set_callback(iq, jabber_disco_server_items_cb, list_data);
+
+		++list_data->fetch_count;
+		purple_disco_list_ref(list_data->list);
+
+		jabber_iq_send(iq);
 	} else {
 		purple_notify_error(NULL, _("Error"), _("Server doesn't support service discovery"), NULL); 
-		purple_disco_list_set_in_progress(list, FALSE);
-		purple_disco_list_unref(list);
+		purple_disco_list_set_in_progress(list_data->list, FALSE);
 	}
+
+	purple_disco_list_unref(list_data->list);
+}
+
+static void discolist_cancel_cb(struct jabber_disco_list_data *list_data, const char *server)
+{
+	purple_disco_list_set_in_progress(list_data->list, FALSE);
+	purple_disco_list_unref(list_data->list);
 }
 
-static void
-jabber_disco_server_cb(PurpleDiscoList *list, PurpleRequestFields *fields)
+static void discolist_ok_cb(struct jabber_disco_list_data *list_data, const char *server)
+{
+	JabberStream *js;
+
+	js = list_data->js;
+
+	if (!server || !*server) {
+		purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL);
+
+		purple_disco_list_set_in_progress(list_data->list, FALSE);
+		purple_disco_list_unref(list_data->list);
+		return;
+	}
+
+	list_data->server = g_strdup(server);
+	if (js->last_disco_server)
+		g_free(js->last_disco_server);
+	js->last_disco_server = g_strdup(server);
+
+	purple_disco_list_set_in_progress(list_data->list, TRUE);
+
+	++list_data->fetch_count;
+	jabber_disco_info_do(js, list_data->server, jabber_disco_server_info_cb, list_data);
+}
+
+PurpleDiscoList *
+jabber_disco_get_list(PurpleConnection *gc)
 {
 	PurpleAccount *account;
-	PurpleConnection *gc;
-	JabberStream *js;
-	const char *server_name;
-
-	server_name = purple_request_fields_get_string(fields, "server");
-
-	account = purple_disco_list_get_account(list);
-	gc = purple_account_get_connection(account);
-	if (!gc)
-		return;
-	js = purple_connection_get_protocol_data(gc);
-
-	purple_disco_list_set_in_progress(list, TRUE);
-	purple_debug_misc("jabber", "Service discovery for %s\n", server_name);
-	if (js->last_disco_server)
-		g_free(js->last_disco_server);
-	js->last_disco_server = g_strdup(server_name);
-
-	jabber_disco_info_do(js, server_name, jabber_disco_server_info_cb, list);
-}
-
-void
-jabber_disco_get_list(PurpleConnection *gc, PurpleDiscoList *list)
-{
-	PurpleAccount *account;
+	PurpleDiscoList *list;
 	JabberStream *js;
 	struct jabber_disco_list_data *disco_list_data;
 
 	account = purple_connection_get_account(gc);
 	js = purple_connection_get_protocol_data(gc);
 
+	/* We start with a ref */
+	list = purple_disco_list_new(account);
+
 	disco_list_data = g_new0(struct jabber_disco_list_data, 1);
-	purple_disco_list_set_protocol_data(list, disco_list_data);
-
-	purple_disco_list_ref(list);
+	disco_list_data->list = list;
+	disco_list_data->js = js;
+	purple_disco_list_set_protocol_data(list, disco_list_data, disco_proto_data_destroy_cb);
 
 	purple_request_input(gc, _("Server name request"), _("Enter an XMPP Server"),
 			_("Select an XMPP server to query"),
 			js->last_disco_server ? js->last_disco_server : js->user->domain,
 			FALSE, FALSE, NULL,
-			_("Find Services"), PURPLE_CALLBACK(jabber_disco_server_cb),
-			_("Cancel"), PURPLE_CALLBACK(jabber_disco_cancel),
+			_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
+			_("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb),
 			account, NULL, NULL, disco_list_data);
 
+	return list;
 }
 
 void
 jabber_disco_cancel(PurpleDiscoList *list)
 {
-	jabber_disco_list_data_destroy(purple_disco_list_get_protocol_data(list));
-	purple_disco_list_set_protocol_data(list, NULL);
+	struct jabber_disco_list_data *list_data = purple_disco_list_get_protocol_data(list);
+	purple_disco_list_set_protocol_data(list, NULL, NULL);
+
+	if (list_data->fetch_count == 0) {
+		/* Nothing outstanding, just free it now... */
+		jabber_disco_list_data_destroy(list_data);
+	} else {
+		/* We'll free it when the count is 0 */
+		list_data->list = NULL;
+	}
+
 	purple_disco_list_set_in_progress(list, FALSE);	
 }
 
--- a/libpurple/protocols/jabber/disco.h	Mon Mar 30 02:36:02 2009 +0000
+++ b/libpurple/protocols/jabber/disco.h	Tue Mar 31 05:18:09 2009 +0000
@@ -35,7 +35,7 @@
 void jabber_disco_info_do(JabberStream *js, const char *who,
 		JabberDiscoInfoCallback *callback, gpointer data);
 
-void jabber_disco_get_list(PurpleConnection *gc, PurpleDiscoList* list);
+PurpleDiscoList *jabber_disco_get_list(PurpleConnection *gc);
 void jabber_disco_cancel(PurpleDiscoList *list);
 
 int jabber_disco_service_register(PurpleConnection *gc, PurpleDiscoService *service);
--- a/libpurple/prpl.h	Mon Mar 30 02:36:02 2009 +0000
+++ b/libpurple/prpl.h	Tue Mar 31 05:18:09 2009 +0000
@@ -464,7 +464,7 @@
 	/**
 	 * Service discovery prpl callbacks
 	 */
-	void (*disco_get_list)(PurpleConnection *gc, PurpleDiscoList *list);
+	PurpleDiscoList *(*disco_get_list)(PurpleConnection *gc);
 
 	/**
 	 * Cancel fetching service list
--- a/pidgin/gtkdisco.c	Mon Mar 30 02:36:02 2009 +0000
+++ b/pidgin/gtkdisco.c	Tue Mar 31 05:18:09 2009 +0000
@@ -38,9 +38,6 @@
 	
 	GtkWidget *sw;
 	GtkWidget *progress;
-	GtkTreeStore *model;
-	GtkWidget *tree;
-	GHashTable *cats; /** Meow. */
 
 	GtkWidget *stop_button;
 	GtkWidget *list_button;
@@ -52,6 +49,13 @@
 	PurpleDiscoList *discolist;
 } PidginDiscoDialog;
 
+typedef struct _PidginDiscoList {
+	PidginDiscoDialog *dialog;
+	GtkTreeStore *model;
+	GtkWidget *tree;
+	GHashTable *cats; /** Meow. */
+} PidginDiscoList;
+
 struct _menu_cb_info {
 	PurpleDiscoList *list;
 	PurpleDiscoService *service;
@@ -65,6 +69,8 @@
 	NUM_OF_COLUMNS
 };
 
+static void pidgin_disco_create_tree(PidginDiscoList *pdl);
+
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
 				     PidginDiscoDialog *dialog)
 {
@@ -86,17 +92,31 @@
 static void list_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
 {
 	PurpleConnection *gc;
+	PidginDiscoList *pdl;
 
 	gc = purple_account_get_connection(dialog->account);
 	if (!gc)
 		return;
 
-	if (dialog->discolist != NULL)
+	if (dialog->discolist != NULL) {
+		pdl = purple_disco_list_get_ui_data(dialog->discolist);
+		gtk_widget_destroy(pdl->tree);
 		purple_disco_list_unref(dialog->discolist);
-	
-	dialog->discolist = purple_disco_list_new(dialog->account, (void*) dialog);
+	}
+
+	dialog->discolist = purple_disco_get_list(gc);
+	if (!dialog->discolist)
+		return;
+	/* We keep a copy... */
+	purple_disco_list_ref(dialog->discolist);
 
-	purple_disco_get_list(dialog->discolist);
+	pdl = purple_disco_list_get_ui_data(dialog->discolist);
+	pdl->dialog = dialog;
+
+	pidgin_disco_create_tree(pdl);
+
+	if (dialog->account_widget)
+		gtk_widget_set_sensitive(dialog->account_widget, FALSE);
 }
 
 static void add_room_to_blist_cb(GtkButton *button, PidginDiscoDialog *dialog)
@@ -117,17 +137,18 @@
 }
 
 static void
-selection_changed_cb(GtkTreeSelection *selection, PidginDiscoDialog *dialog) 
+selection_changed_cb(GtkTreeSelection *selection, PidginDiscoList *pdl)
 {
 	PurpleDiscoService *service;
 	PurpleDiscoServiceFlags flags;
 	GtkTreeIter iter;
 	GValue val;
 	static struct _menu_cb_info *info;
+	PidginDiscoDialog *dialog = pdl->dialog;
 
 	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);
+		gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN, &val);
 		service = g_value_get_pointer(&val);
 		if (!service) {
 			gtk_widget_set_sensitive(dialog->add_button, FALSE);
@@ -157,8 +178,16 @@
 {
 	PidginDiscoDialog *dialog = d;
 
-	if (dialog->discolist)
+	if (dialog->discolist && purple_disco_list_get_in_progress(dialog->discolist))
+		purple_disco_cancel_get_list(dialog->discolist);
+
+	if (dialog->discolist) {
+		PidginDiscoList *pdl = purple_disco_list_get_ui_data(dialog->discolist);
+
+		if (pdl)
+			pdl->dialog = NULL;
 		purple_disco_list_unref(dialog->discolist);
+	}
 
 	g_free(dialog);
 
@@ -212,30 +241,30 @@
 	return FALSE;
 }
 
-static void pidgin_disco_create_tree(PidginDiscoDialog *dialog)
+static void pidgin_disco_create_tree(PidginDiscoList *pdl)
 {
 	GtkCellRenderer *text_renderer, *pixbuf_renderer;
 	GtkTreeViewColumn *column;
 	GtkTreeSelection *selection;
 
-	dialog->model = gtk_tree_store_new(NUM_OF_COLUMNS,
+	pdl->model = gtk_tree_store_new(NUM_OF_COLUMNS,
 			GDK_TYPE_PIXBUF,	/* PIXBUF_COLUMN */
 			G_TYPE_STRING,		/* NAME_COLUMN */
 			G_TYPE_STRING,		/* DESCRIPTION_COLUMN */
 			G_TYPE_POINTER		/* SERVICE_COLUMN */
 	);
 
-	dialog->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
-	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(dialog->tree), TRUE);
+	pdl->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pdl->model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pdl->tree), TRUE);
 
-	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->tree));
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pdl->tree));
 	g_signal_connect(G_OBJECT(selection), "changed",
-					 G_CALLBACK(selection_changed_cb), dialog);
+					 G_CALLBACK(selection_changed_cb), pdl);
 
-	g_object_unref(dialog->model);
+	g_object_unref(pdl->model);
 
-	gtk_container_add(GTK_CONTAINER(dialog->sw), dialog->tree);
-	gtk_widget_show(dialog->tree);
+	gtk_container_add(GTK_CONTAINER(pdl->dialog->sw), pdl->tree);
+	gtk_widget_show(pdl->tree);
 
 	text_renderer = gtk_cell_renderer_text_new();
 	pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
@@ -256,7 +285,7 @@
 	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
 	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN);
 	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
-	gtk_tree_view_append_column(GTK_TREE_VIEW(dialog->tree), column);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column);
 
 	column = gtk_tree_view_column_new_with_attributes(_("Description"), text_renderer,
 				"text", DESCRIPTION_COLUMN, NULL);
@@ -265,7 +294,7 @@
 	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
 	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), DESCRIPTION_COLUMN);
 	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
-	gtk_tree_view_append_column(GTK_TREE_VIEW(dialog->tree), column);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column);
 }
 
 static PidginDiscoDialog*
@@ -351,8 +380,6 @@
 	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
 					 G_CALLBACK(close_button_cb), dialog);
 
-	pidgin_disco_create_tree(dialog);
-
 	/* show the dialog window and return the dialog */
 	gtk_widget_show(dialog->window);
 
@@ -379,35 +406,42 @@
 static void
 pidgin_disco_create(PurpleDiscoList *list)
 {
-	PidginDiscoDialog *dialog;
+	PidginDiscoList *pdl = g_new0(PidginDiscoList, 1);
 
-	dialog = purple_disco_list_get_ui_data(list);
+	purple_disco_list_set_ui_data(list, pdl);
 
-	dialog->cats = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)gtk_tree_row_reference_free);
+	pdl->cats = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)gtk_tree_row_reference_free);
 }
 
 
 static void
 pidgin_disco_destroy(PurpleDiscoList *list)
 {
-	PidginDiscoDialog *dialog;
-	
-	dialog = purple_disco_list_get_ui_data(list);
+	PidginDiscoList *pdl;
+
+	pdl = purple_disco_list_get_ui_data(list);
 
-	g_hash_table_destroy(dialog->cats);
+	g_hash_table_destroy(pdl->cats);
+	g_free(pdl);
+
+	purple_disco_list_set_ui_data(list, NULL);
 }
 
 static void pidgin_disco_in_progress(PurpleDiscoList *list, gboolean in_progress)
 {
+	PidginDiscoList *pdl;
 	PidginDiscoDialog *dialog;
 	
-	dialog = purple_disco_list_get_ui_data(list);
+	pdl = purple_disco_list_get_ui_data(list);
+	if (!pdl)
+		return;
 
+	dialog = pdl->dialog;
 	if (!dialog)
 		return;
 
 	if (in_progress) {
-		gtk_tree_store_clear(dialog->model);
+		gtk_tree_store_clear(pdl->model);
 		if (dialog->account_widget)
 			gtk_widget_set_sensitive(dialog->account_widget, FALSE);
 		gtk_widget_set_sensitive(dialog->stop_button, TRUE);
@@ -423,6 +457,7 @@
 
 static void pidgin_disco_add_service(PurpleDiscoList *list, PurpleDiscoService *service, PurpleDiscoService *parent)
 {
+	PidginDiscoList *pdl;
 	PidginDiscoDialog *dialog;
 	PurpleDiscoServiceCategory category;
 	PurpleDiscoServiceType type;
@@ -432,22 +467,23 @@
 	char *filename = NULL;
 	GdkPixbuf *pixbuf = NULL;
 
-	dialog = purple_disco_list_get_ui_data(list);
+	pdl = purple_disco_list_get_ui_data(list);
+	dialog = pdl->dialog;
 
 	purple_debug_info("disco", "Add_service \"%s\"\n", purple_disco_service_get_name(service));
 
 	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress));
 
 	if (parent) {
-		rr = g_hash_table_lookup(dialog->cats, parent);
+		rr = g_hash_table_lookup(pdl->cats, 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_model_get_iter(GTK_TREE_MODEL(pdl->model), &parent_iter, path);
 			gtk_tree_path_free(path);
 		}
 	}
 
-	gtk_tree_store_append(dialog->model, &iter, (parent ? &parent_iter : NULL));
+	gtk_tree_store_append(pdl->model, &iter, (parent ? &parent_iter : NULL));
 
 	category = purple_disco_service_get_category(service);
 	type = purple_disco_service_get_type(service);
@@ -480,17 +516,17 @@
 		g_free(filename);
 	}
 
-	gtk_tree_store_set(dialog->model, &iter,
+	gtk_tree_store_set(pdl->model, &iter,
 			PIXBUF_COLUMN, pixbuf,
 			NAME_COLUMN, purple_disco_service_get_name(service),
 			DESCRIPTION_COLUMN, purple_disco_service_get_description(service),
 			SERVICE_COLUMN, service,
 			-1);
 
-	path = gtk_tree_model_get_path(GTK_TREE_MODEL(dialog->model), &iter);
+	path = gtk_tree_model_get_path(GTK_TREE_MODEL(pdl->model), &iter);
 
-	rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(dialog->model), path);
-	g_hash_table_insert(dialog->cats, service, rr);
+	rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(pdl->model), path);
+	g_hash_table_insert(pdl->cats, service, rr);
 
 	gtk_tree_path_free(path);