pidgin/pidgin

Add a PurpleRequestField:valid property and is_valid vfunc

14 months ago, Elliott Sales de Andrade
4de5a514b8ba
Parents 4c7b2771461a
Children a90f1381c9a8
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/
--- a/libpurple/protocols/demo/purpledemoprotocolactions.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/protocols/demo/purpledemoprotocolactions.c Thu Mar 16 20:43:41 2023 -0500
@@ -502,13 +502,13 @@
_("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);
--- a/libpurple/purplerequestfield.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/purplerequestfield.c Thu Mar 16 20:43:41 2023 -0500
@@ -36,8 +36,7 @@
char *tooltip;
- PurpleRequestFieldValidator validator;
- void *validator_data;
+ GClosure *validator;
} PurpleRequestFieldPrivate;
enum {
@@ -50,6 +49,7 @@
PROP_TOOLTIP,
PROP_REQUIRED,
PROP_FILLED,
+ PROP_VALID,
PROP_IS_VALIDATABLE,
N_PROPERTIES,
};
@@ -111,6 +111,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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(&params[0],
+ PURPLE_TYPE_REQUEST_FIELD),
+ field);
+ g_value_set_pointer(g_value_init(&params[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(&params[i]);
+ }
+ }
- if (valid && errmsg)
- *errmsg = NULL;
+ if(!valid && errmsg != NULL && *errmsg == NULL) {
+ *errmsg = g_strdup(_("Validation failed without setting an error "
+ "message."));
+ }
return valid;
}
--- a/libpurple/purplerequestfield.h Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/purplerequestfield.h Thu Mar 16 20:43:41 2023 -0500
@@ -54,13 +54,23 @@
/*< 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 @@
/**
* 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 @@
/**
* 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.
*
--- a/libpurple/request/purplerequestfieldaccount.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldaccount.c Thu Mar 16 20:43:41 2023 -0500
@@ -217,7 +217,10 @@
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));
}
}
--- a/libpurple/request/purplerequestfieldbool.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldbool.c Thu Mar 16 20:43:41 2023 -0500
@@ -173,7 +173,10 @@
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
--- a/libpurple/request/purplerequestfieldchoice.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldchoice.c Thu Mar 16 20:43:41 2023 -0500
@@ -207,7 +207,10 @@
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
--- a/libpurple/request/purplerequestfieldint.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldint.c Thu Mar 16 20:43:41 2023 -0500
@@ -45,6 +45,32 @@
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 @@
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 @@
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 @@
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
--- a/libpurple/request/purplerequestfieldlist.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldlist.c Thu Mar 16 20:43:41 2023 -0500
@@ -190,6 +190,7 @@
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 @@
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 @@
field->selected = NULL;
g_hash_table_remove_all(field->selected_table);
+ g_object_notify(G_OBJECT(field), "valid");
}
void
@@ -242,6 +245,8 @@
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
--- a/libpurple/request/purplerequestfieldstring.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldstring.c Thu Mar 16 20:43:41 2023 -0500
@@ -265,6 +265,7 @@
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");
}
--- a/libpurple/request/purplerequestfieldstring.h Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/request/purplerequestfieldstring.h Thu Mar 16 20:43:41 2023 -0500
@@ -134,7 +134,7 @@
*
* 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 @@
*
* 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.
*/
--- a/libpurple/tests/test_request_field.c Tue Mar 14 00:42:47 2023 -0500
+++ b/libpurple/tests/test_request_field.c Thu Mar 16 20:43:41 2023 -0500
@@ -113,6 +113,96 @@
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 @@
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();
}
--- a/pidgin/gtkdialogs.c Tue Mar 14 00:42:47 2023 -0500
+++ b/pidgin/gtkdialogs.c Thu Mar 16 20:43:41 2023 -0500
@@ -96,7 +96,8 @@
field = purple_request_field_string_new("screenname", _("_Name"), NULL, FALSE);
purple_request_field_set_type_hint(field, "screenname");
purple_request_field_set_required(field, TRUE);
- purple_request_field_set_validator(field, pidgin_dialogs_im_name_validator, page);
+ purple_request_field_set_validator(field, pidgin_dialogs_im_name_validator,
+ page, NULL);
purple_request_group_add_field(group, field);
field = purple_request_field_account_new("account", _("_Account"), NULL);