# HG changeset patch
# Node ID f729e2fe4490336cd809fb5646c12bf53ae420e1
# Parent  b6cb116ce0638f834060a4c199538d5796fce495
diff --git a/libpurple/purplehistorymanager.c b/libpurple/purplehistorymanager.c
--- a/libpurple/purplehistorymanager.c
+++ b/libpurple/purplehistorymanager.c
@@ -31,6 +31,14 @@
 #include "util.h"
 
 enum {
+	PROP_0,
+	PROP_ITEM_TYPE,
+	PROP_N_ITEMS,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+enum {
 	SIG_ACTIVE_CHANGED,
 	SIG_ADDED,
 	SIG_REMOVED,
@@ -41,33 +49,118 @@
 struct _PurpleHistoryManager {
 	GObject parent;
 
-	GHashTable *adapters;
+	GPtrArray *adapters;
 	PurpleHistoryAdapter *active_adapter;
 };
 
-G_DEFINE_FINAL_TYPE(PurpleHistoryManager, purple_history_manager,
-                    G_TYPE_OBJECT)
-
 static PurpleHistoryManager *default_manager = NULL;
 
 /******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static PurpleHistoryAdapter *
+purple_history_manager_find_with_id(PurpleHistoryManager *manager,
+                                    const char *id, guint *position)
+{
+	g_return_val_if_fail(PURPLE_IS_HISTORY_MANAGER(manager), NULL);
+
+	for(guint i = 0; i < manager->adapters->len; i++) {
+		PurpleHistoryAdapter *adapter = NULL;
+		const char *candidate_id = NULL;
+
+		adapter = g_ptr_array_index(manager->adapters, i);
+		candidate_id = purple_history_adapter_get_id(adapter);
+
+		if(purple_strequal(candidate_id, id)) {
+			if(position != NULL) {
+				*position = i;
+			}
+
+			return adapter;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************************
+ * GListModel Implementation
+ *****************************************************************************/
+static GType
+purple_history_manager_get_item_type(G_GNUC_UNUSED GListModel *list) {
+	return PURPLE_TYPE_HISTORY_ADAPTER;
+}
+
+static guint
+purple_history_manager_get_n_items(GListModel *list) {
+	PurpleHistoryManager *manager = PURPLE_HISTORY_MANAGER(list);
+
+	return manager->adapters->len;
+}
+
+static gpointer
+purple_history_manager_get_item(GListModel *list, guint position) {
+	PurpleHistoryManager *manager = PURPLE_HISTORY_MANAGER(list);
+	PurpleHistoryAdapter *adapter = NULL;
+
+	if(position < manager->adapters->len) {
+		adapter = g_object_ref(g_ptr_array_index(manager->adapters,
+		                                         position));
+	}
+
+	return adapter;
+}
+
+static void
+pidgin_history_manager_list_model_iface_init(GListModelInterface *iface) {
+	iface->get_item_type = purple_history_manager_get_item_type;
+	iface->get_n_items = purple_history_manager_get_n_items;
+	iface->get_item = purple_history_manager_get_item;
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
+G_DEFINE_FINAL_TYPE_WITH_CODE(PurpleHistoryManager, purple_history_manager,
+                              G_TYPE_OBJECT,
+                              G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
+                                                    pidgin_history_manager_list_model_iface_init))
+
 static void
 purple_history_manager_finalize(GObject *obj) {
 	PurpleHistoryManager *manager = NULL;
 
 	manager = PURPLE_HISTORY_MANAGER(obj);
 
-	g_clear_pointer(&manager->adapters, g_hash_table_destroy);
+	g_clear_pointer(&manager->adapters, g_ptr_array_unref);
 
 	G_OBJECT_CLASS(purple_history_manager_parent_class)->finalize(obj);
 }
 
 static void
+purple_history_manager_get_property(GObject *obj, guint param_id,
+                                    GValue *value, GParamSpec *pspec)
+{
+	GListModel *model = G_LIST_MODEL(obj);
+
+	switch(param_id) {
+	case PROP_ITEM_TYPE:
+		g_value_set_gtype(value,
+		                  purple_history_manager_get_item_type(model));
+		break;
+	case PROP_N_ITEMS:
+		g_value_set_uint(value,
+		                 purple_history_manager_get_n_items(model));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
 purple_history_manager_init(PurpleHistoryManager *manager) {
-	manager->adapters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                          g_object_unref);
+	manager->adapters = g_ptr_array_new_full(1, g_object_unref);
 }
 
 static void
@@ -75,6 +168,35 @@
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
 	obj_class->finalize = purple_history_manager_finalize;
+	obj_class->get_property = purple_history_manager_get_property;
+
+	/**
+	 * PurpleHistoryManager: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);
+
+	/**
+	 * PurpleHistoryManager: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);
 
 	/**
 	* PurpleHistoryManager::active-changed:
@@ -150,27 +272,41 @@
 	return default_manager;
 }
 
+GListModel *
+purple_history_manager_get_default_as_model(void) {
+	if(PURPLE_IS_HISTORY_MANAGER(default_manager)) {
+		return G_LIST_MODEL(default_manager);
+	}
+
+	return NULL;
+}
+
 gboolean
 purple_history_manager_add(PurpleHistoryManager *manager,
                            PurpleHistoryAdapter *adapter, GError **error)
 {
+	PurpleHistoryAdapter *existing = NULL;
 	const gchar *id = NULL;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_HISTORY_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_HISTORY_ADAPTER(adapter), FALSE);
 
 	id = purple_history_adapter_get_id(adapter);
-	if(g_hash_table_lookup(manager->adapters, id) != NULL) {
+	existing = purple_history_manager_find_with_id(manager, id, NULL);
+	if(PURPLE_IS_HISTORY_ADAPTER(existing)) {
 		g_set_error(error, PURPLE_HISTORY_MANAGER_DOMAIN, 0,
 		            _("adapter %s has already been added"), id);
 
 		return FALSE;
 	}
 
-	g_hash_table_insert(manager->adapters, g_strdup(id),
-	                    g_object_ref(adapter));
+	/* Set position to the length before we add the new item to avoid math. */
+	position = manager->adapters->len;
+	g_ptr_array_add(manager->adapters, g_object_ref(adapter));
 
 	g_signal_emit(G_OBJECT(manager), signals[SIG_ADDED], 0, adapter);
+	g_list_model_items_changed(G_LIST_MODEL(manager), position, 0, 1);
 
 	return TRUE;
 }
@@ -179,8 +315,10 @@
 purple_history_manager_remove(PurpleHistoryManager *manager,
                               PurpleHistoryAdapter *adapter, GError **error)
 {
+	PurpleHistoryAdapter *existing = NULL;
 	const gchar *id = NULL;
 	gboolean ret = FALSE;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_HISTORY_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_HISTORY_ADAPTER(adapter), FALSE);
@@ -196,8 +334,11 @@
 
 	id = purple_history_adapter_get_id(adapter);
 
-	if(g_hash_table_remove(manager->adapters, id)) {
+	existing = purple_history_manager_find_with_id(manager, id, &position);
+	if(PURPLE_IS_HISTORY_ADAPTER(existing)) {
+		g_ptr_array_remove_index(manager->adapters, position);
 		g_signal_emit(G_OBJECT(manager), signals[SIG_REMOVED], 0, adapter);
+		g_list_model_items_changed(G_LIST_MODEL(manager), position, 1, 0);
 
 		ret = TRUE;
 	} else {
@@ -230,7 +371,7 @@
 
 	/* First look up the new adapter if we're given one. */
 	if(id != NULL) {
-		adapter = g_hash_table_lookup(manager->adapters, id);
+		adapter = purple_history_manager_find_with_id(manager, id, NULL);
 		if(!PURPLE_IS_HISTORY_ADAPTER(adapter)) {
 			g_set_error(error, PURPLE_HISTORY_MANAGER_DOMAIN, 0,
 			            "no history adapter found with id %s", id);
diff --git a/libpurple/purplehistorymanager.h b/libpurple/purplehistorymanager.h
--- a/libpurple/purplehistorymanager.h
+++ b/libpurple/purplehistorymanager.h
@@ -100,6 +100,20 @@
 PurpleHistoryManager *purple_history_manager_get_default(void);
 
 /**
+ * purple_history_manager_get_default_as_model:
+ *
+ * Gets the default #PurpleHistoryManager instance case to a
+ * [iface@Gio.ListModel].
+ *
+ * Returns: (transfer none): The default #PurpleHistoryManager instance cast to
+ *          a model.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+GListModel *purple_history_manager_get_default_as_model(void);
+
+/**
  * purple_history_manager_get_active:
  * @manager: The #PurpleHistoryManager instance.
  *
diff --git a/libpurple/tests/test_history_manager.c b/libpurple/tests/test_history_manager.c
--- a/libpurple/tests/test_history_manager.c
+++ b/libpurple/tests/test_history_manager.c
@@ -136,57 +136,145 @@
 }
 
 /******************************************************************************
- * Tests
+ * Callbacks
  *****************************************************************************/
 static void
-test_purple_history_manager_get_default(void) {
-	PurpleHistoryManager *manager1 = NULL, *manager2 = NULL;
+test_purple_history_manager_items_changed_cb(GListModel *model,
+                                             G_GNUC_UNUSED guint position,
+                                             G_GNUC_UNUSED guint removed,
+                                             G_GNUC_UNUSED guint added,
+                                             gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(PURPLE_IS_HISTORY_MANAGER(model));
+	g_assert_true(G_IS_LIST_MODEL(model));
 
-	manager1 = purple_history_manager_get_default();
-	g_assert_true(PURPLE_IS_HISTORY_MANAGER(manager1));
+	*counter = *counter + 1;
+}
 
-	manager2 = purple_history_manager_get_default();
-	g_assert_true(PURPLE_IS_HISTORY_MANAGER(manager2));
+static void
+test_purple_history_manager_added_removed_cb(PurpleHistoryManager *manager,
+                                             PurpleHistoryAdapter *adapter,
+                                             gpointer data)
+{
+	guint *counter = data;
 
-	g_assert_true(manager1 == manager2);
+	g_assert_true(PURPLE_IS_HISTORY_MANAGER(manager));
+	g_assert_true(PURPLE_IS_HISTORY_ADAPTER(adapter));
+
+	*counter = *counter + 1;
 }
 
 /******************************************************************************
- * Registration Tests
+ * Add/Remove Tests
  *****************************************************************************/
 static void
-test_purple_history_manager_registration(void) {
+test_purple_history_manager_new(void) {
+	PurpleHistoryManager *manager = NULL;
+
+	manager = g_object_new(PURPLE_TYPE_HISTORY_MANAGER, NULL);
+
+	g_assert_true(PURPLE_IS_HISTORY_MANAGER(manager));
+	g_assert_true(G_IS_LIST_MODEL(manager));
+
+	g_assert_finalize_object(manager);
+}
+
+static void
+test_purple_history_manager_properties(void) {
+	PurpleHistoryManager *manager = NULL;
+	GType item_type = G_TYPE_INVALID;
+	guint n_items = 0;
+
+	manager = g_object_new(PURPLE_TYPE_HISTORY_MANAGER, NULL);
+
+	g_object_get(
+		G_OBJECT(manager),
+		"item-type", &item_type,
+		"n-items", &n_items,
+		NULL);
+
+	g_assert_true(item_type == PURPLE_TYPE_HISTORY_ADAPTER);
+
+	/* While there aren't any items in the manager, this is still testing that
+	 * the property is readable.
+	 */
+	g_assert_cmpuint(n_items, ==, 0);
+
+	g_assert_finalize_object(manager);
+}
+
+static void
+test_purple_history_manager_add_remove(void) {
 	PurpleHistoryManager *manager = NULL;
 	PurpleHistoryAdapter *adapter = NULL;
 	GError *error = NULL;
 	gboolean r = FALSE;
+	guint added = 0;
+	guint removed = 0;
+	guint changed = 0;
 
 	manager = g_object_new(PURPLE_TYPE_HISTORY_MANAGER, NULL);
 	g_assert_true(PURPLE_IS_HISTORY_MANAGER(manager));
 
+	g_signal_connect(manager, "added",
+	                 G_CALLBACK(test_purple_history_manager_added_removed_cb),
+	                 &added);
+	g_signal_connect(manager, "removed",
+	                 G_CALLBACK(test_purple_history_manager_added_removed_cb),
+	                 &removed);
+	g_signal_connect(manager, "items-changed",
+	                 G_CALLBACK(test_purple_history_manager_items_changed_cb),
+	                 &changed);
+
 	adapter = test_purple_history_adapter_new();
 
 	/* Add the first time cleanly. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	r = purple_history_manager_add(manager, adapter, &error);
 	g_assert_no_error(error);
 	g_assert_true(r);
+	g_assert_cmpuint(added, ==, 1);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 1);
 
 	/* Add again and verify the error. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	r = purple_history_manager_add(manager, adapter, &error);
 	g_assert_false(r);
 	g_assert_error(error, PURPLE_HISTORY_MANAGER_DOMAIN, 0);
 	g_clear_error(&error);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 0);
 
 	/* Add the adapter. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	r = purple_history_manager_remove(manager, adapter, &error);
 	g_assert_no_error(error);
 	g_assert_true(r);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 1);
+	g_assert_cmpuint(changed, ==, 1);
 
 	/* Remove the adapter again and verify the error. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	r = purple_history_manager_remove(manager, adapter, &error);
 	g_assert_false(r);
 	g_assert_error(error, PURPLE_HISTORY_MANAGER_DOMAIN, 0);
 	g_clear_error(&error);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 0);
 
 	/* Final clean ups. */
 	g_clear_object(&adapter);
@@ -486,10 +574,12 @@
 
 	test_ui_purple_init();
 
-	g_test_add_func("/history-manager/get-default",
-	                test_purple_history_manager_get_default);
-	g_test_add_func("/history-manager/registration",
-	                test_purple_history_manager_registration);
+	g_test_add_func("/history-manager/new",
+	                test_purple_history_manager_new);
+	g_test_add_func("/history-manager/properties",
+	                test_purple_history_manager_properties);
+	g_test_add_func("/history-manager/add-remove",
+	                test_purple_history_manager_add_remove);
 	g_test_add_func("/history-manager/set-active/null",
 	                test_purple_history_manager_set_active_null);
 	g_test_add_func("/history-manager/set-active/non-existent",
