Add a badges-changed signal to Purple.ConversationMember and fix the items changed emissions

Fri, 21 Mar 2025 00:42:55 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Fri, 21 Mar 2025 00:42:55 -0500
changeset 43202
1c2e548285f3
parent 43201
1889c68bc5a0
child 43203
0095fd9f6a30

Add a badges-changed signal to Purple.ConversationMember and fix the items changed emissions

This was added to make sure badge change are notified in the list model.

We now also pass 1 for removed and added to `g_list_model_items_changed` to notify when an item has changed. This feels weird, but `GtkSortListModel` and probably others, early return if removed and added are 0.

Testing Done:
Ran the tests under valgrind and called in the turtles.

Bugs closed: PIDGIN-18045

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

libpurple/purplebadges.c file | annotate | diff | comparison | revisions
libpurple/purpleconversationmember.c file | annotate | diff | comparison | revisions
libpurple/purpleconversationmembers.c file | annotate | diff | comparison | revisions
libpurple/tests/test_conversation_member.c file | annotate | diff | comparison | revisions
libpurple/tests/test_conversation_members.c file | annotate | diff | comparison | revisions
--- a/libpurple/purplebadges.c	Thu Mar 20 23:01:02 2025 -0500
+++ b/libpurple/purplebadges.c	Fri Mar 21 00:42:55 2025 -0500
@@ -41,25 +41,6 @@
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
 
 /******************************************************************************
- * Helpers
- *****************************************************************************/
-static gboolean
-purple_badges_equal(gconstpointer a, gconstpointer b) {
-	PurpleBadge *badge1 = (gpointer)a;
-	PurpleBadge *badge2 = (gpointer)b;
-
-	return purple_badge_equal(badge1, badge2);
-}
-
-static int
-purple_badges_compare_badges(gconstpointer a, gconstpointer b) {
-	PurpleBadge *badge1 = (gpointer)a;
-	PurpleBadge *badge2 = (gpointer)b;
-
-	return purple_badge_compare(badge1, badge2);
-}
-
-/******************************************************************************
  * GListModel Implementation
  *****************************************************************************/
 static GType
@@ -183,7 +164,8 @@
 	g_return_val_if_fail(PURPLE_IS_BADGE(badge), FALSE);
 
 	found = g_ptr_array_find_with_equal_func(badges->badges, badge,
-	                                         purple_badges_equal, NULL);
+	                                         (GEqualFunc)purple_badge_equal,
+	                                         NULL);
 
 	if(found) {
 		return FALSE;
@@ -193,7 +175,7 @@
 	len = badges->badges->len;
 
 	g_ptr_array_add(badges->badges, g_object_ref(badge));
-	g_ptr_array_sort_values(badges->badges, purple_badges_compare_badges);
+	g_ptr_array_sort_values(badges->badges, (GCompareFunc)purple_badge_compare);
 
 	g_list_model_items_changed(G_LIST_MODEL(badges), 0, len, len + 1);
 	g_object_notify_by_pspec(G_OBJECT(badges), properties[PROP_N_ITEMS]);
@@ -267,7 +249,8 @@
 	needle = purple_badge_new(id, 0, "folder-saved-search-symbolic", "🔍");
 
 	found = g_ptr_array_find_with_equal_func(badges->badges, needle,
-	                                         purple_badges_equal, &index);
+	                                         (GEqualFunc)purple_badge_equal,
+	                                         &index);
 	g_clear_object(&needle);
 
 	if(!found) {
--- a/libpurple/purpleconversationmember.c	Thu Mar 20 23:01:02 2025 -0500
+++ b/libpurple/purpleconversationmember.c	Fri Mar 21 00:42:55 2025 -0500
@@ -54,9 +54,15 @@
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
 
-G_DEFINE_FINAL_TYPE(PurpleConversationMember, purple_conversation_member,
-                    G_TYPE_OBJECT)
+enum {
+	SIG_BADGES_CHANGED,
+	N_SIGNALS,
+};
+static guint signals[N_SIGNALS] = {0, };
 
+/******************************************************************************
+ * Prototypes
+ *****************************************************************************/
 static void
 purple_conversation_member_info_changed_cb(GObject *self, GParamSpec *pspec,
                                            gpointer data);
@@ -115,9 +121,22 @@
 	                         properties[PROP_NAME_FOR_DISPLAY]);
 }
 
+static void
+purple_conversation_member_badges_changed_cb(G_GNUC_UNUSED GListModel *model,
+                                             G_GNUC_UNUSED guint position,
+                                             G_GNUC_UNUSED guint removed,
+                                             G_GNUC_UNUSED guint added,
+                                             gpointer data)
+{
+	g_signal_emit(data, signals[SIG_BADGES_CHANGED], 0);
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
+G_DEFINE_FINAL_TYPE(PurpleConversationMember, purple_conversation_member,
+                    G_TYPE_OBJECT)
+
 static void
 purple_conversation_member_get_property(GObject *obj, guint param_id,
                                         GValue *value, GParamSpec *pspec)
@@ -224,6 +243,10 @@
 static void
 purple_conversation_member_init(PurpleConversationMember *member) {
 	member->badges = purple_badges_new();
+	g_signal_connect_object(member->badges, "items-changed",
+	                        G_CALLBACK(purple_conversation_member_badges_changed_cb),
+	                        member, G_CONNECT_DEFAULT);
+
 	member->tags = purple_tags_new();
 }
 
@@ -346,6 +369,24 @@
 		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+	/**
+	 * PurpleConversationMember::badges-changed:
+	 *
+	 * Emitted when the badges for the member have changed.
+	 *
+	 * Since: 3.0
+	 */
+	signals[SIG_BADGES_CHANGED] = g_signal_new_class_handler(
+		"badges-changed",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		0);
 }
 
 /******************************************************************************
--- a/libpurple/purpleconversationmembers.c	Thu Mar 20 23:01:02 2025 -0500
+++ b/libpurple/purpleconversationmembers.c	Fri Mar 21 00:42:55 2025 -0500
@@ -81,7 +81,7 @@
 	                                         &position);
 
 	if(found) {
-		g_list_model_items_changed(G_LIST_MODEL(members), position, 0, 0);
+		g_list_model_items_changed(G_LIST_MODEL(members), position, 1, 1);
 	}
 }
 
@@ -108,6 +108,23 @@
 	}
 }
 
+static void
+purple_conversation_members_member_badges_changed_cb(PurpleConversationMember *member,
+                                                     gpointer data)
+{
+	PurpleConversationMembers *members = data;
+	gboolean found = FALSE;
+	guint position = 0;
+
+	found = g_ptr_array_find_with_equal_func(members->members, member,
+	                                         purple_conversation_members_check_member_equal,
+	                                         &position);
+
+	if(found) {
+		g_list_model_items_changed(G_LIST_MODEL(members), position, 1, 1);
+	}
+}
+
 /******************************************************************************
  * GListModel Implementation
  *****************************************************************************/
@@ -313,13 +330,16 @@
 	member = purple_conversation_member_new(info);
 	g_ptr_array_add(members->members, member);
 
-	/* Add a callback for notify::name-for-display and typing-state on info. */
+	/* Add callbacks for the member. */
 	g_signal_connect_object(member, "notify::name-for-display",
 	                        G_CALLBACK(purple_conversation_members_member_changed_cb),
 	                        members, G_CONNECT_DEFAULT);
 	g_signal_connect_object(member, "notify::typing-state",
 	                        G_CALLBACK(purple_conversation_members_typing_changed_cb),
 	                        members, G_CONNECT_DEFAULT);
+	g_signal_connect_object(member, "badges-changed",
+	                        G_CALLBACK(purple_conversation_members_member_badges_changed_cb),
+	                        members, G_CONNECT_DEFAULT);
 
 	g_signal_emit(members, signals[SIG_MEMBER_ADDED], 0, member, announce,
 	              message);
--- a/libpurple/tests/test_conversation_member.c	Thu Mar 20 23:01:02 2025 -0500
+++ b/libpurple/tests/test_conversation_member.c	Fri Mar 21 00:42:55 2025 -0500
@@ -35,6 +35,17 @@
 	*counter = *counter + 1;
 }
 
+static void
+test_purple_conversation_member_badges_changed_counter(PurpleConversationMember *member,
+                                                       gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(PURPLE_IS_CONVERSATION_MEMBER(member));
+
+	*counter = *counter + 1;
+}
+
 /******************************************************************************
  * Tests
  *****************************************************************************/
@@ -265,6 +276,42 @@
 	g_clear_object(&member);
 }
 
+static void
+test_purple_conversation_member_badges_changed_signal(void) {
+	PurpleBadge *badge = NULL;
+	PurpleBadges *badges = NULL;
+	PurpleContactInfo *info = NULL;
+	PurpleConversationMember *member = NULL;
+	guint counter = 0;
+
+	/* Create the member and connect to the badges-changed signal for verify
+	 * it was emitted when expected.
+	 */
+	info = purple_contact_info_new(NULL);
+	member = purple_conversation_member_new(info);
+	g_signal_connect(member, "badges-changed",
+	                 G_CALLBACK(test_purple_conversation_member_badges_changed_counter),
+	                 &counter);
+
+	badges = purple_conversation_member_get_badges(member);
+
+	/* Add a badge and verify badges-changed only got emitted once. */
+	counter = 0;
+	badge = purple_badge_new("test", 0, "test-icon", "t");
+	purple_badges_add_badge(badges, badge);
+	g_assert_cmpuint(counter, ==, 1);
+	g_clear_object(&badge);
+
+	/* Remove the badge and verify badges-changed only got emitted once. */
+	counter = 0;
+	purple_badges_remove_badge(badges, "test");
+	g_assert_cmpuint(counter, ==, 1);
+
+	/* Clean everything up. */
+	g_clear_object(&info);
+	g_clear_object(&member);
+}
+
 /******************************************************************************
  * Matches Tests
  *****************************************************************************/
@@ -488,6 +535,9 @@
 	g_test_add_func("/conversation-member/typing-state/timeout",
 	                test_purple_conversation_member_typing_state_timeout);
 
+	g_test_add_func("/conversation-member/badges-changed-signal",
+	                test_purple_conversation_member_badges_changed_signal);
+
 	g_test_add_func("/conversation-member/matches/accepts_null",
 	                test_purple_conversation_member_matches_accepts_null);
 	g_test_add_func("/conversation-member/matches/empty_string",
--- a/libpurple/tests/test_conversation_members.c	Thu Mar 20 23:01:02 2025 -0500
+++ b/libpurple/tests/test_conversation_members.c	Fri Mar 21 00:42:55 2025 -0500
@@ -143,8 +143,8 @@
 	g_assert_true(PURPLE_IS_CONVERSATION_MEMBERS(model));
 
 	g_assert_cmpuint(position, ==, 1);
-	g_assert_cmpuint(removed, ==, 0);
-	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 1);
+	g_assert_cmpuint(added, ==, 1);
 
 	*counter = *counter + 1;
 }

mercurial