# HG changeset patch # User Gary Kramlich # Date 1669270988 21600 # Node ID c4a96b5eecba28b99d478490344c8839b217cb42 # Parent 14beb80abb209ecc90591ee879f6ea0bf8732501 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/ diff -r 14beb80abb20 -r c4a96b5eecba finch/finchnotifications.c --- 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), diff -r 14beb80abb20 -r c4a96b5eecba libpurple/purplenotificationmanager.c --- 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(¬ification); } /* 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); } diff -r 14beb80abb20 -r c4a96b5eecba libpurple/purplenotificationmanager.h --- 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 diff -r 14beb80abb20 -r c4a96b5eecba libpurple/tests/test_notification_manager.c --- 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(¬ification); /* 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(¬ification); 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(¬ification); - /* 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(¬ification); @@ -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); diff -r 14beb80abb20 -r c4a96b5eecba pidgin/pidginnotificationlist.c --- 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); }