# HG changeset patch
# Node ID 4d2520bad2e89dd9c36d59d754db51c14b31d757
# Parent  787ad14ad73af9bb86fed46f30f91a4a820ca22f
diff --git a/libpurple/purpleconversationmanager.c b/libpurple/purpleconversationmanager.c
--- a/libpurple/purpleconversationmanager.c
+++ b/libpurple/purpleconversationmanager.c
@@ -31,6 +31,8 @@
 enum {
 	PROP_0,
 	PROP_FILENAME,
+	PROP_ITEM_TYPE,
+	PROP_N_ITEMS,
 	N_PROPERTIES,
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -49,14 +51,11 @@
 
 	char *filename;
 
-	GHashTable *conversations;
+	GPtrArray *conversations;
 };
 
 static PurpleConversationManager *default_manager = NULL;
 
-G_DEFINE_FINAL_TYPE(PurpleConversationManager, purple_conversation_manager,
-                    G_TYPE_OBJECT)
-
 typedef gboolean (*PurpleConversationManagerCompareFunc)(PurpleConversation *conversation, gpointer userdata);
 
 /******************************************************************************
@@ -90,21 +89,19 @@
 static PurpleConversation *
 purple_conversation_manager_find_internal(PurpleConversationManager *manager,
                                           PurpleAccount *account,
-                                          const gchar *name,
+                                          const char *name,
                                           PurpleConversationManagerCompareFunc func,
                                           gpointer userdata)
 {
-	GHashTableIter iter;
-	gpointer key;
-
 	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
 
-	g_hash_table_iter_init(&iter, manager->conversations);
-	while(g_hash_table_iter_next(&iter, &key, NULL)) {
-		PurpleConversation *conversation = PURPLE_CONVERSATION(key);
+	for(guint i = 0; i < manager->conversations->len; i++) {
+		PurpleConversation *conversation = NULL;
 
-		if(name != NULL) {
-			const gchar *conv_name = purple_conversation_get_name(conversation);
+		conversation = g_ptr_array_index(manager->conversations, i);
+
+		if(!purple_strempty(name)) {
+			const char *conv_name = purple_conversation_get_name(conversation);
 
 			if(!purple_strequal(conv_name, name)) {
 				continue;
@@ -146,20 +143,62 @@
 }
 
 /******************************************************************************
+ * GListModel Implementation
+ *****************************************************************************/
+static GType
+purple_conversation_manager_get_item_type(G_GNUC_UNUSED GListModel *list) {
+	return PURPLE_TYPE_CONVERSATION;
+}
+
+static guint
+purple_conversation_manager_get_n_items(GListModel *list) {
+	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(list);
+
+	return manager->conversations->len;
+}
+
+static gpointer
+purple_conversation_manager_get_item(GListModel *list, guint position) {
+	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(list);
+	PurpleConversation *conversation = NULL;
+
+	if(position < manager->conversations->len) {
+		conversation = g_object_ref(g_ptr_array_index(manager->conversations,
+		                                              position));
+	}
+
+	return conversation;
+}
+
+static void
+pidgin_conversation_manager_list_model_iface_init(GListModelInterface *iface) {
+	iface->get_item_type = purple_conversation_manager_get_item_type;
+	iface->get_n_items = purple_conversation_manager_get_n_items;
+	iface->get_item = purple_conversation_manager_get_item;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_FINAL_TYPE_WITH_CODE(PurpleConversationManager,
+                              purple_conversation_manager,
+                              G_TYPE_OBJECT,
+                              G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
+                                                    pidgin_conversation_manager_list_model_iface_init))
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
 static void
 purple_conversation_manager_init(PurpleConversationManager *manager) {
-	manager->conversations = g_hash_table_new_full(g_direct_hash,
-	                                               g_direct_equal,
-	                                               g_object_unref, NULL);
+	manager->conversations = g_ptr_array_new_full(10, g_object_unref);
 }
 
 static void
 purple_conversation_manager_finalize(GObject *obj) {
 	PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj);
 
-	g_hash_table_destroy(manager->conversations);
+	g_clear_pointer(&manager->conversations, g_ptr_array_unref);
 	g_clear_pointer(&manager->filename, g_free);
 
 	G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj);
@@ -176,6 +215,14 @@
 		g_value_set_string(value,
 		                   purple_conversation_manager_get_filename(manager));
 		break;
+	case PROP_ITEM_TYPE:
+		g_value_set_gtype(value,
+		                  purple_conversation_manager_get_item_type(G_LIST_MODEL(manager)));
+		break;
+	case PROP_N_ITEMS:
+		g_value_set_uint(value,
+		                 purple_conversation_manager_get_n_items(G_LIST_MODEL(manager)));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
 		break;
@@ -222,6 +269,32 @@
 		NULL,
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * PurpleConversationManager:item-type:
+	 *
+	 * The type of items. See [vfunc@Gio.ListModel.get_item_type].
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_ITEM_TYPE] = g_param_spec_gtype(
+		"item-type", "item-type",
+		"The type of the contained items.",
+		G_TYPE_OBJECT,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleConversationManager:n-items:
+	 *
+	 * The number of items. See [vfunc@Gio.ListModel.get_n_items].
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_N_ITEMS] = g_param_spec_uint(
+		"n-items", "n-items",
+		"The number of contained items.",
+		0, G_MAXUINT, 0,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 
 	/**
@@ -347,32 +420,27 @@
 purple_conversation_manager_add(PurpleConversationManager *manager,
                                 PurpleConversation *conversation)
 {
-	gboolean added = FALSE;
-
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
 
-	/* g_hash_table_add calls the key_destroy_func if the key already exists
-	 * which means we don't need to worry about the reference we're creating
-	 * during the addition.
-	 */
-	added = g_hash_table_add(manager->conversations,
-	                         g_object_ref(conversation));
-
-	if(added) {
-		/* Register our signals that need to be propagated. */
-		g_signal_connect_object(conversation, "notify",
-		                        G_CALLBACK(purple_conversation_manager_conversation_changed_cb),
-		                        manager, 0);
-		g_signal_connect_object(conversation, "present",
-		                        G_CALLBACK(purple_conversation_manager_present_conversation_cb),
-		                        manager, 0);
-
-		/* Tell everyone about the new conversation. */
-		g_signal_emit(manager, signals[SIG_ADDED], 0, conversation);
+	if(g_ptr_array_find(manager->conversations, conversation, NULL)) {
+		return FALSE;
 	}
 
-	return added;
+	g_ptr_array_add(manager->conversations, g_object_ref(conversation));
+
+	/* Register our signals that need to be propagated. */
+	g_signal_connect_object(conversation, "notify",
+	                        G_CALLBACK(purple_conversation_manager_conversation_changed_cb),
+	                        manager, 0);
+	g_signal_connect_object(conversation, "present",
+	                        G_CALLBACK(purple_conversation_manager_present_conversation_cb),
+	                        manager, 0);
+
+	/* Tell everyone about the new conversation. */
+	g_signal_emit(manager, signals[SIG_ADDED], 0, conversation);
+
+	return TRUE;
 }
 
 PurpleConversationManager *
@@ -388,6 +456,15 @@
 	return default_manager;
 }
 
+GListModel *
+purple_conversation_manager_get_default_as_model(void) {
+	if(PURPLE_IS_CONVERSATION_MANAGER(default_manager)) {
+		return G_LIST_MODEL(default_manager);
+	}
+
+	return NULL;
+}
+
 const char *
 purple_conversation_manager_get_filename(PurpleConversationManager *manager) {
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL);
@@ -397,9 +474,17 @@
 
 GList *
 purple_conversation_manager_get_all(PurpleConversationManager *manager) {
+	GList *result = NULL;
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL);
 
-	return g_hash_table_get_keys(manager->conversations);
+	for(guint i = 0; i < manager->conversations->len; i++) {
+		PurpleConversation *conversation = NULL;
+
+		conversation = g_ptr_array_index(manager->conversations, i);
+		result = g_list_append(result, conversation);
+	}
+
+	return result;
 }
 
 PurpleConversation *
@@ -408,8 +493,6 @@
 {
 	PurpleAccount *contact_account = NULL;
 	PurpleContactInfo *info = NULL;
-	GHashTableIter iter;
-	gpointer key;
 
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL);
 	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
@@ -417,12 +500,12 @@
 	info = PURPLE_CONTACT_INFO(contact);
 	contact_account = purple_contact_get_account(contact);
 
-	g_hash_table_iter_init(&iter, manager->conversations);
-	while(g_hash_table_iter_next(&iter, &key, NULL)) {
+	for(guint i = 0; i < manager->conversations->len; i++) {
 		PurpleAccount *conversation_account = NULL;
-		PurpleConversation *conversation = key;
+		PurpleConversation *conversation = NULL;
 		PurpleConversationMembers *members = NULL;
 
+		conversation = g_ptr_array_index(manager->conversations, i);
 		conversation_account = purple_conversation_get_account(conversation);
 		if(conversation_account != contact_account) {
 			continue;
@@ -458,29 +541,32 @@
 purple_conversation_manager_remove(PurpleConversationManager *manager,
                                    PurpleConversation *conversation)
 {
-	gboolean removed = FALSE;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
 
+	if(!g_ptr_array_find(manager->conversations, conversation, &position)) {
+		return FALSE;
+	}
+
 	/* Make sure we have a reference in case we need to emit signals. */
 	g_object_ref(conversation);
 
-	removed = g_hash_table_remove(manager->conversations, conversation);
-	if(removed) {
-		/* Disconnect all the signals we added for propagation. */
-		g_signal_handlers_disconnect_by_func(conversation,
-		                                     purple_conversation_manager_conversation_changed_cb,
-		                                     manager);
-		g_signal_handlers_disconnect_by_func(conversation,
-		                                     purple_conversation_manager_present_conversation_cb,
-		                                     manager);
+	g_ptr_array_remove_index(manager->conversations, position);
 
-		/* Tell everyone about the removed conversation. */
-		g_signal_emit(manager, signals[SIG_REMOVED], 0, conversation);
-	}
+	/* Disconnect all the signals we added for propagation. */
+	g_signal_handlers_disconnect_by_func(conversation,
+	                                     purple_conversation_manager_conversation_changed_cb,
+	                                     manager);
+	g_signal_handlers_disconnect_by_func(conversation,
+	                                     purple_conversation_manager_present_conversation_cb,
+	                                     manager);
+
+	/* Tell everyone about the removed conversation. */
+	g_signal_emit(manager, signals[SIG_REMOVED], 0, conversation);
 
 	g_object_unref(conversation);
 
-	return removed;
+	return TRUE;
 }
diff --git a/libpurple/purpleconversationmanager.h b/libpurple/purpleconversationmanager.h
--- a/libpurple/purpleconversationmanager.h
+++ b/libpurple/purpleconversationmanager.h
@@ -85,7 +85,7 @@
 /**
  * purple_conversation_manager_get_default:
  *
- * Gets the default instance of #PurpleConversationManager.  This instance
+ * Gets the default instance of #PurpleConversationManager. This instance
  * can be used for any of the API including connecting to signals.
  *
  * Returns: (transfer none): The default #PurpleConversationManager instance.
@@ -96,6 +96,19 @@
 PurpleConversationManager *purple_conversation_manager_get_default(void);
 
 /**
+ * purple_conversation_manager_get_default_as_model:
+ *
+ * Gets the default instance of #PurpleConversationManager but cast to a
+ * [iface@Gio.ListModel].
+ *
+ * Returns: (transfer none): The instance cast to a list model.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+GListModel *purple_conversation_manager_get_default_as_model(void);
+
+/**
  * purple_conversation_manager_get_filename:
  * @manager: The instance.
  *
diff --git a/libpurple/tests/test_conversation_manager.c b/libpurple/tests/test_conversation_manager.c
--- a/libpurple/tests/test_conversation_manager.c
+++ b/libpurple/tests/test_conversation_manager.c
@@ -47,6 +47,7 @@
 	manager = purple_conversation_manager_new(NULL);
 
 	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
+	g_assert_true(G_IS_LIST_MODEL(manager));
 
 	filename = purple_conversation_manager_get_filename(manager);
 	g_assert_null(filename);
@@ -62,6 +63,7 @@
 	manager = purple_conversation_manager_new("convos.db");
 
 	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
+	g_assert_true(G_IS_LIST_MODEL(manager));
 
 	filename = purple_conversation_manager_get_filename(manager);
 	g_assert_cmpstr(filename, ==, "convos.db");
