Make PurpleNotificationManager implement GListModel

Thu, 24 Nov 2022 00:23:08 -0600

author
Gary Kramlich <grim@reaperworld.com>
date
Thu, 24 Nov 2022 00:23:08 -0600
changeset 41936
c4a96b5eecba
parent 41935
14beb80abb20
child 41937
f34ff5a47b2e

Make PurpleNotificationManager implement GListModel

Also a few other clean ups, including making the user of the manager responsible
for sorting the list.

Testing Done:
Ran the unit tests and verified the notification list in pidgin was updating correctly.

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

finch/finchnotifications.c file | annotate | diff | comparison | revisions
libpurple/purplenotificationmanager.c file | annotate | diff | comparison | revisions
libpurple/purplenotificationmanager.h file | annotate | diff | comparison | revisions
libpurple/tests/test_notification_manager.c file | annotate | diff | comparison | revisions
pidgin/pidginnotificationlist.c file | annotate | diff | comparison | revisions
--- a/finch/finchnotifications.c	Wed Nov 23 23:03:30 2022 -0600
+++ b/finch/finchnotifications.c	Thu Nov 24 00:23:08 2022 -0600
@@ -330,7 +330,6 @@
  ******************************************************************************/
 void
 finch_notifications_window_show(void) {
-	PurpleNotificationManager *manager = NULL;
 	GntWidget *wid, *box;
 	GListModel *model = NULL;
 
@@ -358,8 +357,7 @@
 	                 G_CALLBACK(finch_notifications_activate_cb), NULL);
 
 	/* Get the notification manager to get the model and populate the list. */
-	manager = purple_notification_manager_get_default();
-	model = purple_notification_manager_get_model(manager);
+	model = purple_notification_manager_get_default_as_model();
 	finch_notifications_update(GNT_TREE(notifications.list), model);
 	g_signal_connect_object(model, "items-changed",
 	                        G_CALLBACK(finch_notifications_changed_cb),
--- a/libpurple/purplenotificationmanager.c	Wed Nov 23 23:03:30 2022 -0600
+++ b/libpurple/purplenotificationmanager.c	Thu Nov 24 00:23:08 2022 -0600
@@ -41,14 +41,11 @@
 struct _PurpleNotificationManager {
 	GObject parent;
 
-	GListStore *notifications;
+	GPtrArray *notifications;
 
 	guint unread_count;
 };
 
-G_DEFINE_TYPE(PurpleNotificationManager, purple_notification_manager,
-              G_TYPE_OBJECT);
-
 static PurpleNotificationManager *default_manager = NULL;
 
 /******************************************************************************
@@ -114,8 +111,48 @@
 }
 
 /******************************************************************************
+ * GListModel Implementation
+ *****************************************************************************/
+static GType
+purple_notification_manager_get_item_type(G_GNUC_UNUSED GListModel *list) {
+	return PURPLE_TYPE_NOTIFICATION;
+}
+
+static guint
+purple_notification_manager_get_n_items(GListModel *list) {
+	PurpleNotificationManager *manager = PURPLE_NOTIFICATION_MANAGER(list);
+
+	return manager->notifications->len;
+}
+
+static gpointer
+purple_notification_manager_get_item(GListModel *list, guint position) {
+	PurpleNotificationManager *manager = PURPLE_NOTIFICATION_MANAGER(list);
+	PurpleNotification *notification = NULL;
+
+	if(position < manager->notifications->len) {
+		notification = g_ptr_array_index(manager->notifications, position);
+		g_object_ref(notification);
+	}
+
+	return notification;
+}
+
+static void
+purple_notification_manager_list_model_init(GListModelInterface *iface) {
+	iface->get_item_type = purple_notification_manager_get_item_type;
+	iface->get_n_items = purple_notification_manager_get_n_items;
+	iface->get_item = purple_notification_manager_get_item;
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
+G_DEFINE_TYPE_EXTENDED(PurpleNotificationManager, purple_notification_manager,
+                       G_TYPE_OBJECT, G_TYPE_FLAG_FINAL,
+                       G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, purple_notification_manager_list_model_init));
+
+
 static void
 purple_notification_manager_get_property(GObject *obj, guint param_id,
                                          GValue *value, GParamSpec *pspec)
@@ -139,14 +176,16 @@
 
 	manager = PURPLE_NOTIFICATION_MANAGER(obj);
 
-	g_clear_object(&manager->notifications);
+	g_ptr_array_free(manager->notifications, TRUE);
+	manager->notifications = NULL;
 
 	G_OBJECT_CLASS(purple_notification_manager_parent_class)->finalize(obj);
 }
 
 static void
 purple_notification_manager_init(PurpleNotificationManager *manager) {
-	manager->notifications = g_list_store_new(PURPLE_TYPE_NOTIFICATION);
+	manager->notifications = g_ptr_array_new_full(0,
+	                                              (GDestroyNotify)g_object_unref);
 }
 
 static void
@@ -267,6 +306,8 @@
 purple_notification_manager_startup(void) {
 	if(default_manager == NULL) {
 		default_manager = g_object_new(PURPLE_TYPE_NOTIFICATION_MANAGER, NULL);
+		g_object_add_weak_pointer(G_OBJECT(default_manager),
+		                          (gpointer *)&default_manager);
 	}
 }
 
@@ -283,6 +324,15 @@
 	return default_manager;
 }
 
+GListModel *
+purple_notification_manager_get_default_as_model(void) {
+	if(PURPLE_IS_NOTIFICATION_MANAGER(default_manager)) {
+		return G_LIST_MODEL(default_manager);
+	}
+
+	return NULL;
+}
+
 void
 purple_notification_manager_add(PurpleNotificationManager *manager,
                                 PurpleNotification *notification)
@@ -290,7 +340,7 @@
 	g_return_if_fail(PURPLE_IS_NOTIFICATION_MANAGER(manager));
 	g_return_if_fail(PURPLE_IS_NOTIFICATION(notification));
 
-	if(g_list_store_find(manager->notifications, notification, NULL)) {
+	if(g_ptr_array_find(manager->notifications, notification, NULL)) {
 		const gchar *id = purple_notification_get_id(notification);
 
 		g_warning("double add detected for notification %s", id);
@@ -298,9 +348,7 @@
 		return;
 	}
 
-	g_list_store_insert_sorted(manager->notifications, notification,
-	                           (GCompareDataFunc)purple_notification_compare,
-	                           NULL);
+	g_ptr_array_insert(manager->notifications, 0, g_object_ref(notification));
 
 	/* Connect to the notify signal for the read property only so we can
 	 * propagate out changes for any notification.
@@ -316,23 +364,25 @@
 	}
 
 	g_signal_emit(G_OBJECT(manager), signals[SIG_ADDED], 0, notification);
+	g_list_model_items_changed(G_LIST_MODEL(manager), 0, 0, 1);
 }
 
 void
 purple_notification_manager_remove(PurpleNotificationManager *manager,
                                    PurpleNotification *notification)
 {
-	guint position;
+	guint index = 0;
 
 	g_return_if_fail(PURPLE_IS_NOTIFICATION_MANAGER(manager));
 	g_return_if_fail(PURPLE_IS_NOTIFICATION(notification));
 
-	if(g_list_store_find(manager->notifications, notification, &position)) {
+	if(g_ptr_array_find(manager->notifications, notification, &index)) {
 		/* Reference the notification so we can emit the signal after it's been
 		 * removed from the hash table.
 		 */
 		g_object_ref(notification);
 
+		g_ptr_array_remove_index(manager->notifications, index);
 		/* Remove the notify signal handler for the read state incase someone
 		 * else added a reference to the notification which would then mess
 		 * with our unread count accounting.
@@ -348,18 +398,17 @@
 			purple_notification_manager_decrement_unread_count(manager);
 		}
 
-		g_list_store_remove(manager->notifications, position);
-
 		g_signal_emit(G_OBJECT(manager), signals[SIG_REMOVED], 0, notification);
+		g_list_model_items_changed(G_LIST_MODEL(manager), index, 1, 0);
 
 		g_object_unref(notification);
-
 	}
 }
 
 /*
 This function uses the following algorithm to optimally remove items from the
-GListStore. See the psuedo code below for an easier to follow version.
+g_ptr_array to minimize the number of calls to g_list_model_items_changed. See
+the pseudo code below for an easier to follow version.
 
 A
 A B C
@@ -397,6 +446,7 @@
                                                 PurpleAccount *account,
                                                 gboolean all)
 {
+	GListModel *model = NULL;
 	guint pos = 0, len = 0;
 	guint start = 0, count = 0;
 	gboolean have_same = FALSE;
@@ -404,15 +454,15 @@
 	g_return_if_fail(PURPLE_IS_NOTIFICATION_MANAGER(manager));
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 
-	len = g_list_model_get_n_items(G_LIST_MODEL(manager->notifications));
-	for(pos = 0; pos < len; pos++) {
+	model = G_LIST_MODEL(manager);
+
+	for(pos = 0; pos < manager->notifications->len; pos++) {
 		PurpleAccount *account2 = NULL;
 		PurpleNotification *notification = NULL;
 		PurpleNotificationType type;
 		gboolean can_remove = TRUE;
 
-		notification = g_list_model_get_item(G_LIST_MODEL(manager->notifications),
-		                                     pos);
+		notification = g_ptr_array_index(manager->notifications, pos);
 
 		/* If the notification's type is connection error, set can_remove to
 		 * the value of the all parameter.
@@ -438,8 +488,8 @@
 		} else {
 			if(have_same) {
 				/* Remove the run of items from the list. */
-				g_list_store_splice(manager->notifications, start, count, NULL,
-				                    0);
+				g_ptr_array_remove_range(manager->notifications, start, count);
+				g_list_model_items_changed(model, start, count, 0);
 
 				/* Adjust pos and len for the items that we removed. */
 				pos = pos - count;
@@ -448,13 +498,12 @@
 				have_same = FALSE;
 			}
 		}
-
-		g_clear_object(&notification);
 	}
 
 	/* Clean up the last bit if the last item needs to be removed. */
 	if(have_same) {
-		g_list_store_splice(manager->notifications, start, count, NULL, 0);
+		g_ptr_array_remove_range(manager->notifications, start, count);
+		g_list_model_items_changed(model, start, count, 0);
 	}
 }
 
@@ -465,13 +514,23 @@
 	return manager->unread_count;
 }
 
-GListModel *
-purple_notification_manager_get_model(PurpleNotificationManager *manager) {
-	g_return_val_if_fail(PURPLE_IS_NOTIFICATION_MANAGER(manager), NULL);
+void
+purple_notification_manager_clear(PurpleNotificationManager *manager) {
+	guint count = 0;
+
+	g_return_if_fail(PURPLE_IS_NOTIFICATION_MANAGER(manager));
 
-	if(manager->notifications == NULL) {
-		return NULL;
+	count = manager->notifications->len;
+
+	for(guint pos = 0; pos < count; pos++) {
+		PurpleNotification *notification = NULL;
+
+		notification = g_ptr_array_index(manager->notifications, pos);
+
+		g_signal_emit(manager, signals[SIG_REMOVED], 0, notification);
 	}
 
-	return G_LIST_MODEL(g_object_ref(manager->notifications));
+	g_ptr_array_remove_range(manager->notifications, 0, count);
+
+	g_list_model_items_changed(G_LIST_MODEL(manager), 0, count, 0);
 }
--- a/libpurple/purplenotificationmanager.h	Wed Nov 23 23:03:30 2022 -0600
+++ b/libpurple/purplenotificationmanager.h	Thu Nov 24 00:23:08 2022 -0600
@@ -56,6 +56,17 @@
 PurpleNotificationManager *purple_notification_manager_get_default(void);
 
 /**
+ * purple_notification_manager_get_default_as_model:
+ *
+ * Gets the default manager instance type casted to [iface@Gio.ListModel].
+ *
+ * Returns: (transfer none): The model.
+ *
+ * Since: 3.0.0
+ */
+GListModel *purple_notification_manager_get_default_as_model(void);
+
+/**
  * purple_notification_manager_add:
  * @manager: The instance.
  * @notification: (transfer full): The [class@Notification] to add.
@@ -107,17 +118,14 @@
 guint purple_notification_manager_get_unread_count(PurpleNotificationManager *manager);
 
 /**
- * purple_notification_manager_get_model:
+ * purple_notification_manager_clear:
  * @manager: The instance.
  *
- * Gets a [iface@Gio.ListModel] of all the [class@Notification]'s in
- * @manager.
- *
- * Returns: (transfer full): The model.
+ * Removes all notifications from @manager.
  *
  * Since: 3.0.0
  */
-GListModel *purple_notification_manager_get_model(PurpleNotificationManager *manager);
+void purple_notification_manager_clear(PurpleNotificationManager *manager);
 
 G_END_DECLS
 
--- a/libpurple/tests/test_notification_manager.c	Wed Nov 23 23:03:30 2022 -0600
+++ b/libpurple/tests/test_notification_manager.c	Thu Nov 24 00:23:08 2022 -0600
@@ -169,7 +169,7 @@
 	GListModel *model = NULL;
 
 	manager = g_object_new(PURPLE_TYPE_NOTIFICATION_MANAGER, NULL);
-	model = purple_notification_manager_get_model(manager);
+	model = G_LIST_MODEL(manager);
 	account = purple_account_new("test", "test");
 
 	/* Make sure that nothing happens on an empty list. */
@@ -182,7 +182,7 @@
 	purple_notification_manager_add(manager, notification);
 	purple_notification_manager_remove_with_account(manager, account, TRUE);
 	g_assert_cmpuint(1, ==, g_list_model_get_n_items(model));
-	g_list_store_remove_all(G_LIST_STORE(model));
+	purple_notification_manager_clear(manager);
 	g_clear_object(&notification);
 
 	/* Add a single notification with the account */
@@ -191,7 +191,7 @@
 	purple_notification_manager_add(manager, notification);
 	purple_notification_manager_remove_with_account(manager, account, TRUE);
 	g_assert_cmpuint(0, ==, g_list_model_get_n_items(model));
-	g_list_store_remove_all(G_LIST_STORE(model));
+	purple_notification_manager_clear(manager);
 	g_clear_object(&notification);
 
 	g_clear_object(&manager);
@@ -208,7 +208,7 @@
 	gint i = 0;
 
 	manager = g_object_new(PURPLE_TYPE_NOTIFICATION_MANAGER, NULL);
-	model = purple_notification_manager_get_model(manager);
+	model = G_LIST_MODEL(manager);
 
 	accounts[0] = purple_account_new("account1", "account1");
 	accounts[1] = purple_account_new("account2", "account2");
@@ -255,20 +255,24 @@
 	 * and generic. We will also add a generic notification with no account, to
 	 * make sure notifications don't accidentally get removed.
 	 *
+	 * Since the notification manager prepends items and leaves sorting up to
+	 * the user interface, the order of the notifications will be the opposite
+	 * of the order they were added.
+	 *
 	 * This ordering is specifically done because we batch remove items from
 	 * the list. So by calling purple_notification_manager_remove_with_account
-	 * with `all` set to FALSE, we should remove the first and third items,
-	 * but leave the second and fourth items.
+	 * with `all` set to FALSE, we should remove the second and fourth items,
+	 * but leave the first and third items.
 	 *
 	 * We then call purple_notification_manager_remove_with_account with `all`
 	 * set to TRUE, which will remove the connection error notification which
-	 * is now the first item.
+	 * is now the second item.
 	 *
 	 * Finally, we empty the manager and verify that its count is at 0.
 	 */
 
 	manager = g_object_new(PURPLE_TYPE_NOTIFICATION_MANAGER, NULL);
-	model = purple_notification_manager_get_model(manager);
+	model = G_LIST_MODEL(manager);
 	account = purple_account_new("test", "test");
 
 	/* Make sure that nothing happens on an empty list. */
@@ -299,7 +303,6 @@
 	purple_notification_manager_add(manager, notification);
 	g_clear_object(&notification);
 
-
 	/* Verify that we have all of the notifications in the manager. */
 	g_assert_cmpuint(4, ==, g_list_model_get_n_items(model));
 
@@ -307,8 +310,8 @@
 	purple_notification_manager_remove_with_account(manager, account, FALSE);
 	g_assert_cmpuint(2, ==, g_list_model_get_n_items(model));
 
-	/* Make sure that the first item is the connection error. */
-	notification = g_list_model_get_item(G_LIST_MODEL(model), 0);
+	/* Make sure that the second item is the connection error. */
+	notification = g_list_model_get_item(G_LIST_MODEL(model), 1);
 	g_assert_cmpint(purple_notification_get_notification_type(notification),
 	                ==, PURPLE_NOTIFICATION_TYPE_CONNECTION_ERROR);
 	g_clear_object(&notification);
@@ -318,7 +321,7 @@
 	g_assert_cmpuint(1, ==, g_list_model_get_n_items(model));
 
 	/* Remove the generic notification that's not tied to an account. */
-	g_list_store_remove_all(G_LIST_STORE(model));
+	purple_notification_manager_clear(manager);
 	g_assert_cmpuint(0, ==, g_list_model_get_n_items(model));
 
 	g_clear_object(&manager);
--- a/pidgin/pidginnotificationlist.c	Wed Nov 23 23:03:30 2022 -0600
+++ b/pidgin/pidginnotificationlist.c	Thu Nov 24 00:23:08 2022 -0600
@@ -96,15 +96,10 @@
  *****************************************************************************/
 static void
 pidgin_notification_list_init(PidginNotificationList *list) {
-	PurpleNotificationManager *manager = NULL;
-	GListModel *model = NULL;
-
 	gtk_widget_init_template(GTK_WIDGET(list));
 
-	manager = purple_notification_manager_get_default();
-	model = purple_notification_manager_get_model(manager);
-
-	gtk_list_box_bind_model(GTK_LIST_BOX(list->list_box), model,
+	gtk_list_box_bind_model(GTK_LIST_BOX(list->list_box),
+	                        purple_notification_manager_get_default_as_model(),
 	                        pidgin_notification_list_create_widget_func,
 	                        list, NULL);
 }

mercurial