diff options
author | Elliott Sales de Andrade <quantum.analyst@gmail.com> | 2023-03-16 20:43:41 -0500 |
---|---|---|
committer | Elliott Sales de Andrade <quantum.analyst@gmail.com> | 2023-03-16 20:43:41 -0500 |
commit | 85e11fc7f835ef573fae937e5c7956725b81d1ff (patch) | |
tree | 088d22dd152389d75bc01a2bab39311514055db7 /libpurple | |
parent | 6ace7e4ef5e70584ecebe53c200d6c5732e48ed6 (diff) | |
download | pidgin-85e11fc7f835ef573fae937e5c7956725b81d1ff.tar.gz |
Add a PurpleRequestField:valid property and is_valid vfunc
* Add a `PurpleRequestField:valid` property and `is_valid` vfunc.
* Add a `GDestroyNotify` parameter for the validator data, and document the callback.
* Correct the property notification in `purple_request_field_bool_set_value`.
Testing Done:
Compiled and ran `ninja test`
Reviewed at https://reviews.imfreedom.org/r/2346/
Diffstat (limited to 'libpurple')
-rw-r--r-- | libpurple/protocols/demo/purpledemoprotocolactions.c | 4 | ||||
-rw-r--r-- | libpurple/purplerequestfield.c | 79 | ||||
-rw-r--r-- | libpurple/purplerequestfield.h | 29 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldaccount.c | 3 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldbool.c | 5 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldchoice.c | 3 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldint.c | 44 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldlist.c | 5 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldstring.c | 1 | ||||
-rw-r--r-- | libpurple/request/purplerequestfieldstring.h | 4 | ||||
-rw-r--r-- | libpurple/tests/test_request_field.c | 94 |
11 files changed, 237 insertions, 34 deletions
diff --git a/libpurple/protocols/demo/purpledemoprotocolactions.c b/libpurple/protocols/demo/purpledemoprotocolactions.c index 7ab29b5014..097e3539f7 100644 --- a/libpurple/protocols/demo/purpledemoprotocolactions.c +++ b/libpurple/protocols/demo/purpledemoprotocolactions.c @@ -502,13 +502,13 @@ purple_demo_protocol_request_fields_activate(G_GNUC_UNUSED GSimpleAction *action _("default"), FALSE); purple_request_field_set_validator(field, purple_request_field_alphanumeric_validator, - NULL); + NULL, NULL); purple_request_group_add_field(group, field); field = purple_request_field_string_new("email", _("An email"), _("me@example.com"), FALSE); purple_request_field_set_validator(field, purple_request_field_email_validator, - NULL); + NULL, NULL); purple_request_group_add_field(group, field); field = purple_request_field_int_new("int", _("An integer"), 123, -42, 1337); purple_request_group_add_field(group, field); diff --git a/libpurple/purplerequestfield.c b/libpurple/purplerequestfield.c index 3b9bd233d8..ac87c88506 100644 --- a/libpurple/purplerequestfield.c +++ b/libpurple/purplerequestfield.c @@ -36,8 +36,7 @@ typedef struct { char *tooltip; - PurpleRequestFieldValidator validator; - void *validator_data; + GClosure *validator; } PurpleRequestFieldPrivate; enum { @@ -50,6 +49,7 @@ enum { PROP_TOOLTIP, PROP_REQUIRED, PROP_FILLED, + PROP_VALID, PROP_IS_VALIDATABLE, N_PROPERTIES, }; @@ -111,6 +111,10 @@ purple_request_field_get_property(GObject *obj, guint param_id, GValue *value, g_value_set_boolean(value, purple_request_field_is_filled(field)); break; + case PROP_VALID: + g_value_set_boolean(value, + purple_request_field_is_valid(field, NULL)); + break; case PROP_IS_VALIDATABLE: g_value_set_boolean(value, purple_request_field_is_validatable(field)); @@ -170,6 +174,7 @@ purple_request_field_finalize(GObject *obj) { g_free(priv->label); g_free(priv->type_hint); g_free(priv->tooltip); + g_clear_pointer(&priv->validator, g_closure_unref); G_OBJECT_CLASS(purple_request_field_parent_class)->finalize(obj); } @@ -297,6 +302,19 @@ purple_request_field_class_init(PurpleRequestFieldClass *klass) { G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** + * PurpleRequestField:valid: + * + * Whether the field has a valid value. + * + * Since: 3.0.0 + */ + properties[PROP_VALID] = g_param_spec_boolean( + "valid", "valid", + "Whether the field has a valid value.", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** * PurpleRequestField:is-validatable: * * Whether the field can be validated by the requestor. @@ -505,7 +523,9 @@ purple_request_field_is_filled(PurpleRequestField *field) { void purple_request_field_set_validator(PurpleRequestField *field, - PurpleRequestFieldValidator validator, void *user_data) + PurpleRequestFieldValidator validator, + gpointer user_data, + GDestroyNotify destroy_data) { PurpleRequestFieldPrivate *priv = NULL; @@ -513,8 +533,14 @@ purple_request_field_set_validator(PurpleRequestField *field, priv = purple_request_field_get_instance_private(field); - priv->validator = validator; - priv->validator_data = validator ? user_data : NULL; + g_clear_pointer(&priv->validator, g_closure_unref); + if(validator != NULL) { + priv->validator = g_cclosure_new(G_CALLBACK(validator), user_data, + (GClosureNotify)G_CALLBACK(destroy_data)); + g_closure_ref(priv->validator); + g_closure_sink(priv->validator); + g_closure_set_marshal(priv->validator, g_cclosure_marshal_generic); + } if(PURPLE_IS_REQUEST_GROUP(priv->group)) { _purple_request_group_set_field_validator(priv->group, field, @@ -522,6 +548,7 @@ purple_request_field_set_validator(PurpleRequestField *field, } g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_IS_VALIDATABLE]); + g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALID]); } gboolean @@ -539,25 +566,49 @@ purple_request_field_is_validatable(PurpleRequestField *field) gboolean purple_request_field_is_valid(PurpleRequestField *field, gchar **errmsg) { + PurpleRequestFieldClass *klass = NULL; PurpleRequestFieldPrivate *priv = NULL; - gboolean valid; + gboolean valid = TRUE; g_return_val_if_fail(PURPLE_IS_REQUEST_FIELD(field), FALSE); - priv = purple_request_field_get_instance_private(field); + if(errmsg != NULL) { + *errmsg = NULL; + } - if(!priv->validator) { + if(!purple_request_field_is_required(field) && + !purple_request_field_is_filled(field)) + { return TRUE; } - if (!purple_request_field_is_required(field) && - !purple_request_field_is_filled(field)) - return TRUE; + klass = PURPLE_REQUEST_FIELD_GET_CLASS(field); + if(klass != NULL && klass->is_valid != NULL) { + valid = klass->is_valid(field, errmsg); + } - valid = priv->validator(field, errmsg, priv->validator_data); + priv = purple_request_field_get_instance_private(field); + if(valid && priv->validator != NULL) { + GValue result = G_VALUE_INIT; + GValue params[] = {G_VALUE_INIT, G_VALUE_INIT}; + g_value_init(&result, G_TYPE_BOOLEAN); + g_value_set_instance(g_value_init(¶ms[0], + PURPLE_TYPE_REQUEST_FIELD), + field); + g_value_set_pointer(g_value_init(¶ms[1], G_TYPE_POINTER), errmsg); + g_closure_invoke(priv->validator, &result, + G_N_ELEMENTS(params), params, NULL); + valid = g_value_get_boolean(&result); + g_value_unset(&result); + for(gsize i = 0; i < G_N_ELEMENTS(params); i++) { + g_value_unset(¶ms[i]); + } + } - if (valid && errmsg) - *errmsg = NULL; + if(!valid && errmsg != NULL && *errmsg == NULL) { + *errmsg = g_strdup(_("Validation failed without setting an error " + "message.")); + } return valid; } diff --git a/libpurple/purplerequestfield.h b/libpurple/purplerequestfield.h index b81aa6e642..2b0f22e678 100644 --- a/libpurple/purplerequestfield.h +++ b/libpurple/purplerequestfield.h @@ -54,13 +54,23 @@ struct _PurpleRequestFieldClass { /*< public >*/ gboolean (*is_filled)(PurpleRequestField *field); + gboolean (*is_valid)(PurpleRequestField *field, char **errmsg); /*< private >*/ gpointer reserved[4]; }; -typedef gboolean (*PurpleRequestFieldValidator)(PurpleRequestField *field, - gchar **errmsg, gpointer user_data); +/** + * PurpleRequestFieldValidator: + * @field: The field. + * @errmsg: (nullable) (optional) (out): A location to store an error message + * if the field is invalid. + * @user_data: (closure): The data passed to + * [method@Purple.RequestField.set_validator]. + * + * A callback to check whether a field is valid. + */ +typedef gboolean (*PurpleRequestFieldValidator)(PurpleRequestField *field, char **errmsg, gpointer user_data); G_BEGIN_DECLS @@ -207,13 +217,14 @@ gboolean purple_request_field_is_filled(PurpleRequestField *field); /** * purple_request_field_set_validator: * @field: The field. - * @validator: (scope notified): The validator callback, NULL to disable validation. - * @user_data: The data to pass to the callback. + * @validator: (scope notified) (closure user_data): The validator callback, or + * %NULL to disable additional validation. + * @user_data: The data to pass to the validator callback. + * @destroy_data: A cleanup function for @user_data. * - * Sets validator for a single field. + * Set an additional validator for a field. */ -void purple_request_field_set_validator(PurpleRequestField *field, - PurpleRequestFieldValidator validator, void *user_data); +void purple_request_field_set_validator(PurpleRequestField *field, PurpleRequestFieldValidator validator, gpointer user_data, GDestroyNotify destroy_data); /** * purple_request_field_is_validatable: @@ -228,8 +239,8 @@ gboolean purple_request_field_is_validatable(PurpleRequestField *field); /** * purple_request_field_is_valid: * @field: The field. - * @errmsg: If non-NULL, the memory area, where the pointer to validation - * failure message will be set. + * @errmsg: (nullable) (optional) (out): If non-%NULL, the memory area, where + * the validation failure message will be returned. * * Checks, if specified field is valid. * diff --git a/libpurple/request/purplerequestfieldaccount.c b/libpurple/request/purplerequestfieldaccount.c index f4d936d62d..2c7bed194b 100644 --- a/libpurple/request/purplerequestfieldaccount.c +++ b/libpurple/request/purplerequestfieldaccount.c @@ -217,7 +217,10 @@ purple_request_field_account_set_value(PurpleRequestFieldAccount *field, g_return_if_fail(PURPLE_IS_REQUEST_FIELD_ACCOUNT(field)); if(g_set_object(&field->account, value)) { + g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALUE]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } } diff --git a/libpurple/request/purplerequestfieldbool.c b/libpurple/request/purplerequestfieldbool.c index 14da4bb064..51edf444d6 100644 --- a/libpurple/request/purplerequestfieldbool.c +++ b/libpurple/request/purplerequestfieldbool.c @@ -173,7 +173,10 @@ purple_request_field_bool_set_value(PurpleRequestFieldBool *field, field->value = value; - g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_DEFAULT_VALUE]); + g_object_freeze_notify(G_OBJECT(field)); + g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALUE]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } gboolean diff --git a/libpurple/request/purplerequestfieldchoice.c b/libpurple/request/purplerequestfieldchoice.c index c2d315c1de..b1d0c55412 100644 --- a/libpurple/request/purplerequestfieldchoice.c +++ b/libpurple/request/purplerequestfieldchoice.c @@ -207,7 +207,10 @@ purple_request_field_choice_set_value(PurpleRequestFieldChoice *field, field->value = value; + g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALUE]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } gpointer diff --git a/libpurple/request/purplerequestfieldint.c b/libpurple/request/purplerequestfieldint.c index e973030aab..f4b0aeeb66 100644 --- a/libpurple/request/purplerequestfieldint.c +++ b/libpurple/request/purplerequestfieldint.c @@ -45,6 +45,32 @@ enum { static GParamSpec *properties[N_PROPERTIES] = {NULL, }; /****************************************************************************** + * PurpleRequestField Implementation + *****************************************************************************/ +static gboolean +purple_request_field_int_is_valid(PurpleRequestField *field, char **errmsg) { + PurpleRequestFieldInt *intfield = PURPLE_REQUEST_FIELD_INT(field); + + if(intfield->value < intfield->lower_bound) { + if(errmsg != NULL) { + *errmsg = g_strdup_printf(_("Int value %d exceeds lower bound %d"), + intfield->value, intfield->lower_bound); + } + return FALSE; + } + + if(intfield->value > intfield->upper_bound) { + if(errmsg != NULL) { + *errmsg = g_strdup_printf(_("Int value %d exceeds upper bound %d"), + intfield->value, intfield->upper_bound); + } + return FALSE; + } + + return TRUE; +} + +/****************************************************************************** * GObject Implementation *****************************************************************************/ G_DEFINE_TYPE(PurpleRequestFieldInt, purple_request_field_int, @@ -112,8 +138,11 @@ purple_request_field_int_init(G_GNUC_UNUSED PurpleRequestFieldInt *field) { static void purple_request_field_int_class_init(PurpleRequestFieldIntClass *klass) { + PurpleRequestFieldClass *request_class = PURPLE_REQUEST_FIELD_CLASS(klass); GObjectClass *obj_class = G_OBJECT_CLASS(klass); + request_class->is_valid = purple_request_field_int_is_valid; + obj_class->get_property = purple_request_field_int_get_property; obj_class->set_property = purple_request_field_int_set_property; @@ -220,7 +249,10 @@ purple_request_field_int_set_lower_bound(PurpleRequestFieldInt *field, field->lower_bound = lower_bound; + g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_LOWER_BOUND]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } void @@ -235,26 +267,26 @@ purple_request_field_int_set_upper_bound(PurpleRequestFieldInt *field, field->upper_bound = upper_bound; + g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_UPPER_BOUND]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } void purple_request_field_int_set_value(PurpleRequestFieldInt *field, int value) { g_return_if_fail(PURPLE_IS_REQUEST_FIELD_INT(field)); - if(value < field->lower_bound || value > field->upper_bound) { - g_warning("Int value %d out of bounds (%d, %d)", value, - field->lower_bound, field->upper_bound); - return; - } - if(field->value == value) { return; } field->value = value; + g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALUE]); + g_object_notify(G_OBJECT(field), "valid"); + g_object_thaw_notify(G_OBJECT(field)); } int diff --git a/libpurple/request/purplerequestfieldlist.c b/libpurple/request/purplerequestfieldlist.c index 127449f445..aee6039a35 100644 --- a/libpurple/request/purplerequestfieldlist.c +++ b/libpurple/request/purplerequestfieldlist.c @@ -190,6 +190,7 @@ purple_request_field_list_add_icon(PurpleRequestFieldList *field, kvp = purple_key_value_pair_new_full(item, g_strdup(icon_path), g_free); field->items = g_list_append(field->items, kvp); g_hash_table_insert(field->item_data, g_strdup(item), data); + g_object_notify(G_OBJECT(field), "valid"); } void @@ -208,6 +209,7 @@ purple_request_field_list_add_selected(PurpleRequestFieldList *field, field->selected = g_list_append(field->selected, g_strdup(item)); g_hash_table_add(field->selected_table, g_strdup(item)); + g_object_notify(G_OBJECT(field), "valid"); } void @@ -218,6 +220,7 @@ purple_request_field_list_clear_selected(PurpleRequestFieldList *field) { field->selected = NULL; g_hash_table_remove_all(field->selected_table); + g_object_notify(G_OBJECT(field), "valid"); } void @@ -242,6 +245,8 @@ purple_request_field_list_set_selected(PurpleRequestFieldList *field, field->selected = g_list_append(field->selected, g_strdup(selected)); g_hash_table_add(field->selected_table, g_strdup(selected)); } + + g_object_notify(G_OBJECT(field), "valid"); } gboolean diff --git a/libpurple/request/purplerequestfieldstring.c b/libpurple/request/purplerequestfieldstring.c index 5f41ec3726..eca4c1b984 100644 --- a/libpurple/request/purplerequestfieldstring.c +++ b/libpurple/request/purplerequestfieldstring.c @@ -265,6 +265,7 @@ purple_request_field_string_set_value(PurpleRequestFieldString *field, g_object_freeze_notify(G_OBJECT(field)); g_object_notify_by_pspec(G_OBJECT(field), properties[PROP_VALUE]); + g_object_notify(G_OBJECT(field), "valid"); if(before != after) { g_object_notify(G_OBJECT(field), "filled"); } diff --git a/libpurple/request/purplerequestfieldstring.h b/libpurple/request/purplerequestfieldstring.h index 6fd2b4ed10..b5daff5278 100644 --- a/libpurple/request/purplerequestfieldstring.h +++ b/libpurple/request/purplerequestfieldstring.h @@ -134,7 +134,7 @@ gboolean purple_request_field_string_is_masked(PurpleRequestFieldString *field); * * Validates a field which should contain an email address. * - * See purple_request_field_set_validator(). + * See [method@Purple.RequestField.set_validator]. * * Returns: TRUE, if field contains valid email address. */ @@ -149,7 +149,7 @@ gboolean purple_request_field_email_validator(PurpleRequestField *field, char ** * * Validates a field which should contain alphanumeric content. * - * See purple_request_field_set_validator(). + * See [method@Purple.RequestField.set_validator]. * * Returns: TRUE, if field contains only alphanumeric characters. */ diff --git a/libpurple/tests/test_request_field.c b/libpurple/tests/test_request_field.c index a15d3e12b6..27c1de1841 100644 --- a/libpurple/tests/test_request_field.c +++ b/libpurple/tests/test_request_field.c @@ -113,6 +113,96 @@ test_request_field_filled_nonstring(void) { g_object_unref(field); } +static void +test_request_field_valid_int(void) { + PurpleRequestField *field = NULL; + char *errmsg = NULL; + gboolean result; + + field = purple_request_field_int_new("test-int", "Test int", 50, 0, 100); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_null(errmsg); + g_assert_true(result); + + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field), -42); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_cmpstr(errmsg, ==, "Int value -42 exceeds lower bound 0"); + g_assert_false(result); + g_free(errmsg); + + /* Don't crash if no error message is requested. */ + result = purple_request_field_is_valid(field, NULL); + g_assert_false(result); + + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field), 1337); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_cmpstr(errmsg, ==, "Int value 1337 exceeds upper bound 100"); + g_assert_false(result); + g_free(errmsg); + + /* Don't crash if no error message is requested. */ + result = purple_request_field_is_valid(field, NULL); + g_assert_false(result); + + g_object_unref(field); +} + +static gboolean +test_request_field_validator_is_even(PurpleRequestField *field, char **errmsg, + G_GNUC_UNUSED gpointer data) +{ + gboolean *called = data; + gint value; + + *called = TRUE; + g_return_val_if_fail(PURPLE_IS_REQUEST_FIELD_INT(field), FALSE); + + value = purple_request_field_int_get_value(PURPLE_REQUEST_FIELD_INT(field)); + + if(value % 2 != 0) { + *errmsg = g_strdup_printf("Value %d is not even.", value); + return FALSE; + } + + return TRUE; +} + +static void +test_request_field_valid_custom(void) { + PurpleRequestField *field = NULL; + char *errmsg = NULL; + gboolean result; + gboolean called = FALSE; + + field = purple_request_field_int_new("test-int", "Test int", 50, 0, 100); + purple_request_field_set_validator(field, + test_request_field_validator_is_even, + &called, NULL); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_cmpstr(errmsg, ==, NULL); + g_assert_true(result); + + /* Default validator (i.e., the bounds) is checked first. */ + called = FALSE; + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field), -42); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_cmpstr(errmsg, ==, "Int value -42 exceeds lower bound 0"); + g_assert_false(result); + g_assert_false(called); + g_free(errmsg); + + /* But if default validator passes, then the custom one is checked. */ + called = FALSE; + purple_request_field_int_set_value(PURPLE_REQUEST_FIELD_INT(field), 23); + result = purple_request_field_is_valid(field, &errmsg); + g_assert_cmpstr(errmsg, ==, "Value 23 is not even."); + g_assert_false(result); + g_assert_true(called); + g_free(errmsg); + + g_object_unref(field); +} + /****************************************************************************** * Main *****************************************************************************/ @@ -125,5 +215,9 @@ main(gint argc, gchar *argv[]) { g_test_add_func("/request-field/filled-nonstring", test_request_field_filled_nonstring); + g_test_add_func("/request-field/valid-int", test_request_field_valid_int); + g_test_add_func("/request-field/valid-custom", + test_request_field_valid_custom); + return g_test_run(); } |