Fri, 21 Mar 2025 00:42:55 -0500
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/
--- 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; }