From 5701ff4637926e7f04d9b1022082d887883dd9ca Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 16 Mar 2023 22:50:08 -0500 Subject: Add PurpleRequest{Group,Page}:valid properties This collects the validity of the fields/groups within the group/page into a single value. Testing Done: Compiled and ran `ninja test`. Reviewed at https://reviews.imfreedom.org/r/2348/ --- finch/gntrequest.c | 2 +- libpurple/purplerequestgroup.c | 58 ++++++++++- libpurple/purplerequestgroup.h | 12 +++ libpurple/purplerequestpage.c | 84 +++++++++++++-- libpurple/purplerequestpage.h | 6 +- libpurple/tests/meson.build | 2 + libpurple/tests/test_request_group.c | 103 +++++++++++++++++++ libpurple/tests/test_request_page.c | 194 +++++++++++++++++++++++++++++++++++ pidgin/gtkrequest.c | 4 +- 9 files changed, 447 insertions(+), 18 deletions(-) create mode 100644 libpurple/tests/test_request_group.c create mode 100644 libpurple/tests/test_request_page.c diff --git a/finch/gntrequest.c b/finch/gntrequest.c index 6c06435066..66d1579659 100644 --- a/finch/gntrequest.c +++ b/finch/gntrequest.c @@ -379,7 +379,7 @@ request_fields_cb(GntWidget *button, PurpleRequestPage *page) { if (!g_object_get_data(G_OBJECT(button), "cancellation-function") && (!purple_request_page_all_required_filled(page) || - !purple_request_page_all_valid(page))) { + !purple_request_page_is_valid(page))) { purple_notify_error(button, _("Error"), _("You must properly fill all the required fields."), _("The required fields are underlined."), NULL); diff --git a/libpurple/purplerequestgroup.c b/libpurple/purplerequestgroup.c index 15c6294259..c0d4d1120d 100644 --- a/libpurple/purplerequestgroup.c +++ b/libpurple/purplerequestgroup.c @@ -32,11 +32,13 @@ struct _PurpleRequestGroup { char *title; GList *fields; + GHashTable *invalid_fields; }; enum { PROP_0, PROP_TITLE, + PROP_VALID, N_PROPERTIES, }; static GParamSpec *properties[N_PROPERTIES] = {NULL, }; @@ -52,6 +54,31 @@ purple_request_group_set_title(PurpleRequestGroup *group, const char *title) { g_object_notify_by_pspec(G_OBJECT(group), properties[PROP_TITLE]); } +/****************************************************************************** + * Callbacks + *****************************************************************************/ +static void +purple_request_group_notify_field_cb(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + PurpleRequestGroup *group = PURPLE_REQUEST_GROUP(data); + PurpleRequestField *field = PURPLE_REQUEST_FIELD(obj); + gboolean before, after; + + before = purple_request_group_is_valid(group); + if(purple_request_field_is_valid(field, NULL)) { + g_hash_table_remove(group->invalid_fields, field); + } else { + g_hash_table_add(group->invalid_fields, field); + } + after = purple_request_group_is_valid(group); + + if(before != after) { + g_object_notify_by_pspec(G_OBJECT(group), properties[PROP_VALID]); + } +} + /****************************************************************************** * GListModel Implementation *****************************************************************************/ @@ -104,6 +131,9 @@ purple_request_group_get_property(GObject *obj, guint param_id, GValue *value, case PROP_TITLE: g_value_set_string(value, purple_request_group_get_title(group)); break; + case PROP_VALID: + g_value_set_boolean(value, purple_request_group_is_valid(group)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; @@ -133,12 +163,14 @@ purple_request_group_finalize(GObject *obj) { g_free(group->title); g_list_free_full(group->fields, g_object_unref); + g_clear_pointer(&group->invalid_fields, g_hash_table_destroy); G_OBJECT_CLASS(purple_request_group_parent_class)->finalize(obj); } static void -purple_request_group_init(G_GNUC_UNUSED PurpleRequestGroup *group) { +purple_request_group_init(PurpleRequestGroup *group) { + group->invalid_fields = g_hash_table_new(g_direct_hash, g_direct_equal); } static void @@ -162,6 +194,19 @@ purple_request_group_class_init(PurpleRequestGroupClass *klass) { NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + /** + * PurpleRequestGroup:valid: + * + * Whether all fields in a group are valid. + * + * Since: 3.0.0 + */ + properties[PROP_VALID] = g_param_spec_boolean( + "valid", "valid", + "Whether all fields in a group are valid.", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); } @@ -216,6 +261,10 @@ purple_request_group_add_field(PurpleRequestGroup *group, position = g_list_length(group->fields); group->fields = g_list_append(group->fields, field); + purple_request_group_notify_field_cb(G_OBJECT(field), NULL, group); + g_signal_connect(field, "notify::valid", + G_CALLBACK(purple_request_group_notify_field_cb), group); + if(PURPLE_IS_REQUEST_PAGE(group->page)) { _purple_request_page_add_field(group->page, field); } @@ -248,3 +297,10 @@ purple_request_group_get_page(PurpleRequestGroup *group) return group->page; } + +gboolean +purple_request_group_is_valid(PurpleRequestGroup *group) { + g_return_val_if_fail(PURPLE_IS_REQUEST_GROUP(group), FALSE); + + return g_hash_table_size(group->invalid_fields) == 0; +} diff --git a/libpurple/purplerequestgroup.h b/libpurple/purplerequestgroup.h index 4d62e6bbfa..2f9aade7db 100644 --- a/libpurple/purplerequestgroup.h +++ b/libpurple/purplerequestgroup.h @@ -107,6 +107,18 @@ GList *purple_request_group_get_fields(PurpleRequestGroup *group); */ PurpleRequestPage *purple_request_group_get_page(PurpleRequestGroup *group); +/** + * purple_request_group_is_valid: + * @group: The field. + * + * Returns whether or not all fields are valid. + * + * Returns: %TRUE if all fields in the group are valid, %FALSE otherwise. + * + * Since: 3.0.0 + */ +gboolean purple_request_group_is_valid(PurpleRequestGroup *group); + G_END_DECLS #endif /* PURPLE_REQUEST_GROUP_H */ diff --git a/libpurple/purplerequestpage.c b/libpurple/purplerequestpage.c index 62442772c8..ab909697f0 100644 --- a/libpurple/purplerequestpage.c +++ b/libpurple/purplerequestpage.c @@ -34,6 +34,7 @@ struct _PurpleRequestPage { GObject parent; GList *groups; + GHashTable *invalid_groups; GHashTable *fields; @@ -42,6 +43,38 @@ struct _PurpleRequestPage { GList *validated_fields; }; +enum { + PROP_0, + PROP_VALID, + N_PROPERTIES, +}; +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; + +/****************************************************************************** + * Callbacks + *****************************************************************************/ +static void +purple_request_page_notify_group_cb(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + PurpleRequestPage *page = PURPLE_REQUEST_PAGE(data); + PurpleRequestGroup *group = PURPLE_REQUEST_GROUP(obj); + gboolean before, after; + + before = purple_request_page_is_valid(page); + if(purple_request_group_is_valid(group)) { + g_hash_table_remove(page->invalid_groups, group); + } else { + g_hash_table_add(page->invalid_groups, group); + } + after = purple_request_page_is_valid(page); + + if(before != after) { + g_object_notify_by_pspec(G_OBJECT(page), properties[PROP_VALID]); + } +} + /****************************************************************************** * GListModel Implementation *****************************************************************************/ @@ -84,11 +117,28 @@ G_DEFINE_TYPE_WITH_CODE(PurpleRequestPage, purple_request_page, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, purple_request_page_list_model_init)) +static void +purple_request_page_get_property(GObject *obj, guint param_id, GValue *value, + GParamSpec *pspec) +{ + PurpleRequestPage *page = PURPLE_REQUEST_PAGE(obj); + + switch(param_id) { + case PROP_VALID: + g_value_set_boolean(value, purple_request_page_is_valid(page)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); + break; + } +} + static void purple_request_page_finalize(GObject *obj) { PurpleRequestPage *page = PURPLE_REQUEST_PAGE(obj); g_list_free_full(page->groups, g_object_unref); + g_clear_pointer(&page->invalid_groups, g_hash_table_destroy); g_list_free(page->required_fields); g_list_free(page->validated_fields); g_hash_table_destroy(page->fields); @@ -99,6 +149,7 @@ purple_request_page_finalize(GObject *obj) { static void purple_request_page_init(PurpleRequestPage *page) { page->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + page->invalid_groups = g_hash_table_new(g_direct_hash, g_direct_equal); } static void @@ -106,6 +157,22 @@ purple_request_page_class_init(PurpleRequestPageClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = purple_request_page_finalize; + obj_class->get_property = purple_request_page_get_property; + + /** + * PurpleRequestPage:valid: + * + * Whether all fields in a page are valid. + * + * Since: 3.0.0 + */ + properties[PROP_VALID] = g_param_spec_boolean( + "valid", "valid", + "Whether all fields in a page are valid.", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); } /****************************************************************************** @@ -177,6 +244,10 @@ purple_request_page_add_group(PurpleRequestPage *page, _purple_request_group_set_page(group, page); + purple_request_page_notify_group_cb(G_OBJECT(group), NULL, page); + g_signal_connect(group, "notify::valid", + G_CALLBACK(purple_request_page_notify_group_cb), page); + for (l = purple_request_group_get_fields(group); l != NULL; l = l->next) { @@ -248,19 +319,10 @@ purple_request_page_all_required_filled(PurpleRequestPage *page) { } gboolean -purple_request_page_all_valid(PurpleRequestPage *page) { - GList *l; - +purple_request_page_is_valid(PurpleRequestPage *page) { g_return_val_if_fail(PURPLE_IS_REQUEST_PAGE(page), FALSE); - for(l = page->validated_fields; l != NULL; l = l->next) { - PurpleRequestField *field = PURPLE_REQUEST_FIELD(l->data); - - if (!purple_request_field_is_valid(field, NULL)) - return FALSE; - } - - return TRUE; + return g_hash_table_size(page->invalid_groups) == 0; } PurpleRequestField * diff --git a/libpurple/purplerequestpage.h b/libpurple/purplerequestpage.h index b9dc4ebd30..a6dd247691 100644 --- a/libpurple/purplerequestpage.h +++ b/libpurple/purplerequestpage.h @@ -122,16 +122,16 @@ gboolean purple_request_page_is_field_required(PurpleRequestPage *page, const ch gboolean purple_request_page_all_required_filled(PurpleRequestPage *page); /** - * purple_request_page_all_valid: + * purple_request_page_is_valid: * @page: The fields page. * * Returns whether or not all fields are valid. * - * Returns: TRUE if all fields are valid, or FALSE. + * Returns: %TRUE if all fields in the page are valid, %FALSE otherwise. * * Since: 3.0.0 */ -gboolean purple_request_page_all_valid(PurpleRequestPage *page); +gboolean purple_request_page_is_valid(PurpleRequestPage *page); /** * purple_request_page_get_field: diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build index c922b80c3c..146ae25fd0 100644 --- a/libpurple/tests/meson.build +++ b/libpurple/tests/meson.build @@ -27,6 +27,8 @@ PROGS = [ 'purplepath', 'queued_output_stream', 'request_field', + 'request_group', + 'request_page', 'str', 'tags', 'util', diff --git a/libpurple/tests/test_request_group.c b/libpurple/tests/test_request_group.c new file mode 100644 index 0000000000..7752ede709 --- /dev/null +++ b/libpurple/tests/test_request_group.c @@ -0,0 +1,103 @@ +/* + * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include + +#include + +/****************************************************************************** + * Tests + *****************************************************************************/ +static void +test_request_group_valid_changed_cb(G_GNUC_UNUSED GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + gint *called = data; + + *called += 1; +} + +static void +test_request_group_valid(void) { + PurpleRequestGroup *group = NULL; + PurpleRequestField *field1 = NULL, *field2 = NULL, *field3 = NULL; + gint called = 0; + + group = purple_request_group_new("test-group"); + g_signal_connect(group, "notify::valid", + G_CALLBACK(test_request_group_valid_changed_cb), &called); + + /* Empty groups are always valid. */ + g_assert_true(purple_request_group_is_valid(group)); + + /* An added valid field keeps the group valid. */ + called = 0; + field1 = purple_request_field_int_new("test-int", "Test int", 50, 0, 100); + purple_request_group_add_field(group, field1); + g_assert_true(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 0); + + /* Making the field invalid makes the group invalid. */ + called = 0; + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field1), -42); + g_assert_false(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 1); + + /* Adding an invalid field keeps the group invalid. */ + called = 0; + field2 = purple_request_field_int_new("invalid", "Invalid", -42, 0, 100); + purple_request_group_add_field(group, field2); + g_assert_false(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 0); + + /* Adding a valid field to an already invalid group does not change it to + * valid accidentally. */ + called = 0; + field3 = purple_request_field_int_new("valid", "Valid", 42, 0, 100); + purple_request_group_add_field(group, field3); + g_assert_false(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 0); + + /* Making one field valid while others are still invalid keeps the group + * invalid. */ + called = 0; + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field1), 42); + g_assert_false(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 0); + + /* Making last invalid field valid makes the group valid again. */ + called = 0; + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field2), 42); + g_assert_true(purple_request_group_is_valid(group)); + g_assert_cmpint(called, ==, 1); + + g_object_unref(group); +} + +/****************************************************************************** + * Main + *****************************************************************************/ +gint +main(gint argc, gchar *argv[]) { + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/request-group/valid", test_request_group_valid); + + return g_test_run(); +} diff --git a/libpurple/tests/test_request_page.c b/libpurple/tests/test_request_page.c new file mode 100644 index 0000000000..aea7f52e63 --- /dev/null +++ b/libpurple/tests/test_request_page.c @@ -0,0 +1,194 @@ +/* + * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include + +#include + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static gboolean +test_request_page_string_validator(PurpleRequestField *field, char **errmsg, + G_GNUC_UNUSED gpointer data) +{ + /* Validator for a string field that is valid if the value is "valid". */ + PurpleRequestFieldString *strfield = PURPLE_REQUEST_FIELD_STRING(field); + const char *value = NULL; + gboolean result = TRUE; + + value = purple_request_field_string_get_value(strfield); + if(!purple_strequal(value, "valid")) { + if(errmsg != NULL) { + *errmsg = g_strdup_printf("String value is not valid: %s", value); + } + result = FALSE; + } + + return result; +} + +static PurpleRequestGroup * +test_request_page_new_valid_group(const char *name) { + PurpleRequestGroup *group = NULL; + PurpleRequestField *field = NULL; + char *field_name = NULL; + + group = purple_request_group_new(name); + + /* Field is valid, making the group valid. */ + field_name = g_strdup_printf("%s-string", name); + field = purple_request_field_string_new(field_name, field_name, "valid", + FALSE); + purple_request_field_set_validator(field, + test_request_page_string_validator, + NULL, NULL); + g_free(field_name); + purple_request_group_add_field(group, field); + + return group; +} + +static PurpleRequestGroup * +test_request_page_new_invalid_group(const char *name) { + PurpleRequestGroup *group = NULL; + PurpleRequestField *field = NULL; + char *field_name = NULL; + + group = purple_request_group_new(name); + + /* Field is invalid, making the group invalid. */ + field_name = g_strdup_printf("%s-string", name); + field = purple_request_field_string_new(field_name, field_name, "invalid", + FALSE); + purple_request_field_set_validator(field, + test_request_page_string_validator, + NULL, NULL); + g_free(field_name); + purple_request_group_add_field(group, field); + + return group; +} + +static void +test_request_page_make_group_valid(PurpleRequestGroup *group) { + GListModel *model = G_LIST_MODEL(group); + guint n_items; + + n_items = g_list_model_get_n_items(model); + for(guint index = 0; index < n_items; index++) { + PurpleRequestFieldString *field = g_list_model_get_item(model, index); + purple_request_field_string_set_value(field, "valid"); + g_object_unref(field); + } +} + +static void +test_request_page_make_group_invalid(PurpleRequestGroup *group) { + GListModel *model = G_LIST_MODEL(group); + guint n_items; + + n_items = g_list_model_get_n_items(model); + for(guint index = 0; index < n_items; index++) { + PurpleRequestFieldString *field = g_list_model_get_item(model, index); + purple_request_field_string_set_value(field, "invalid"); + g_object_unref(field); + } +} + +/****************************************************************************** + * Tests + *****************************************************************************/ +static void +test_request_page_valid_changed_cb(G_GNUC_UNUSED GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + gint *called = data; + + *called += 1; +} + +static void +test_request_page_valid(void) { + PurpleRequestPage *page = NULL; + PurpleRequestGroup *group1 = NULL, *group2 = NULL, *group3 = NULL; + gint called = FALSE; + + page = purple_request_page_new(); + g_signal_connect(page, "notify::valid", + G_CALLBACK(test_request_page_valid_changed_cb), &called); + + /* Empty pages are always valid. */ + g_assert_true(purple_request_page_is_valid(page)); + + /* An added valid group keeps the page valid. */ + called = 0; + group1 = test_request_page_new_valid_group("group1"); + purple_request_page_add_group(page, group1); + g_assert_true(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 0); + + /* Making the group invalid makes the page invalid. */ + called = 0; + test_request_page_make_group_invalid(group1); + g_assert_false(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 1); + + /* Adding an invalid group keeps the page invalid. */ + called = 0; + group2 = test_request_page_new_invalid_group("group2"); + purple_request_page_add_group(page, group2); + g_assert_false(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 0); + + /* Adding a valid group to an already invalid page does not change it to + * valid accidentally. */ + called = 0; + group3 = test_request_page_new_valid_group("group3"); + purple_request_page_add_group(page, group3); + g_assert_false(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 0); + + /* Making one group valid while others are still invalid keeps the group + * invalid. */ + called = 0; + test_request_page_make_group_valid(group1); + g_assert_false(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 0); + + /* Making last invalid group valid makes the page valid again. */ + called = 0; + test_request_page_make_group_valid(group2); + g_assert_true(purple_request_page_is_valid(page)); + g_assert_cmpint(called, ==, 1); + + g_object_unref(page); +} + +/****************************************************************************** + * Main + *****************************************************************************/ +gint +main(gint argc, gchar *argv[]) { + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/request-page/valid", test_request_page_valid); + + return g_test_run(); +} diff --git a/pidgin/gtkrequest.c b/pidgin/gtkrequest.c index 58f8355929..1ac25084da 100644 --- a/pidgin/gtkrequest.c +++ b/pidgin/gtkrequest.c @@ -235,7 +235,7 @@ req_field_changed_common(G_GNUC_UNUSED GtkWidget *widget, gtk_widget_set_sensitive(req_data->ok_button, purple_request_page_all_required_filled(page) && - purple_request_page_all_valid(page)); + purple_request_page_is_valid(page)); } static void @@ -2208,7 +2208,7 @@ pidgin_request_fields(const char *title, const char *primary, gtk_widget_set_sensitive(data->ok_button, FALSE); } - if(!purple_request_page_all_valid(page)) { + if(!purple_request_page_is_valid(page)) { gtk_widget_set_sensitive(data->ok_button, FALSE); } -- cgit v1.2.1