mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-09 19:06:15 +01:00
gaction: add parser for detailed action names
Expand and formalise the syntax for detailed action names, adding a well-documented (and tested) public parser API for them. Port the only GLib-based user of detailed action names to the new API: g_menu_item_set_detailed_action(). The users in Gtk+ will also be ported soon. https://bugzilla.gnome.org/show_bug.cgi?id=688954
This commit is contained in:
parent
e1fdd59f08
commit
8cddb54659
@ -3093,6 +3093,9 @@ g_action_get_state
|
||||
g_action_change_state
|
||||
g_action_activate
|
||||
|
||||
<SUBSECTION>
|
||||
g_action_parse_detailed_name
|
||||
|
||||
<SUBSECTION Standard>
|
||||
g_action_get_type
|
||||
G_TYPE_ACTION
|
||||
|
100
gio/gaction.c
100
gio/gaction.c
@ -23,6 +23,8 @@
|
||||
#include "gaction.h"
|
||||
#include "glibintl.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
G_DEFINE_INTERFACE (GAction, g_action, G_TYPE_OBJECT)
|
||||
|
||||
/**
|
||||
@ -389,3 +391,101 @@ g_action_activate (GAction *action,
|
||||
if (parameter != NULL)
|
||||
g_variant_unref (parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_action_parse_detailed_name:
|
||||
* @detailed_name: a detailed action name
|
||||
* @action_name: (out): the action name
|
||||
* @target_value: (out): the target value, or %NULL for no target
|
||||
* @error: a pointer to a %NULL #GError, or %NULL
|
||||
*
|
||||
* Parses a detailed action name into its separate name and target
|
||||
* components.
|
||||
*
|
||||
* Detailed action names can have three formats.
|
||||
*
|
||||
* The first format is used to represent an action name with no target
|
||||
* value and consists of just an action name containing no whitespace
|
||||
* nor the characters ':', '(' or ')'. For example: "app.action".
|
||||
*
|
||||
* The second format is used to represent an action with a string-typed
|
||||
* target value. The action name and target value are separated by a
|
||||
* double colon ("::"). For example: "app.action::target".
|
||||
*
|
||||
* The third format is used to represent an action with an
|
||||
* arbitrarily-typed target value. The target value follows the action
|
||||
* name, surrounded in parens. For example: "app.action(42)". The
|
||||
* target value is parsed using g_variant_parse(). If a tuple-typed
|
||||
* value is desired, it must be specified in the same way, resulting in
|
||||
* two sets of parens, for example: "app.action((1,2,3))".
|
||||
*
|
||||
* Returns: %TRUE if successful, else %FALSE with @error set
|
||||
*
|
||||
* Since: 2.38
|
||||
**/
|
||||
gboolean
|
||||
g_action_parse_detailed_name (const gchar *detailed_name,
|
||||
gchar **action_name,
|
||||
GVariant **target_value,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *target;
|
||||
gsize target_len;
|
||||
gsize base_len;
|
||||
|
||||
/* We decide which format we have based on which we see first between
|
||||
* '::' '(' and '\0'.
|
||||
*/
|
||||
|
||||
if (*detailed_name == '\0' || *detailed_name == ' ')
|
||||
goto bad_fmt;
|
||||
|
||||
base_len = strcspn (detailed_name, ": ()");
|
||||
target = detailed_name + base_len;
|
||||
target_len = strlen (target);
|
||||
|
||||
switch (target[0])
|
||||
{
|
||||
case ' ':
|
||||
case ')':
|
||||
goto bad_fmt;
|
||||
|
||||
case ':':
|
||||
if (target[1] != ':')
|
||||
goto bad_fmt;
|
||||
|
||||
*target_value = g_variant_ref_sink (g_variant_new_string (target + 2));
|
||||
break;
|
||||
|
||||
case '(':
|
||||
{
|
||||
if (target[target_len - 1] != ')')
|
||||
goto bad_fmt;
|
||||
|
||||
*target_value = g_variant_parse (NULL, target + 1, target + target_len - 1, NULL, error);
|
||||
if (*target_value == NULL)
|
||||
goto bad_fmt;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\0':
|
||||
*target_value = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
*action_name = g_strndup (detailed_name, base_len);
|
||||
|
||||
return TRUE;
|
||||
|
||||
bad_fmt:
|
||||
if (error)
|
||||
{
|
||||
if (*error == NULL)
|
||||
g_set_error (error, G_VARIANT_PARSE_ERROR, G_VARIANT_PARSE_ERROR_FAILED,
|
||||
"Detailed action name '%s' has invalid format", detailed_name);
|
||||
else
|
||||
g_prefix_error (error, "Detailed action name '%s' has invalid format: ", detailed_name);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -81,6 +81,12 @@ void g_action_change_state (GAction
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_action_activate (GAction *action,
|
||||
GVariant *parameter);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_38
|
||||
gboolean g_action_parse_detailed_name (const gchar *detailed_name,
|
||||
gchar **action_name,
|
||||
GVariant **target_value,
|
||||
GError **error);
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_ACTION_H__ */
|
||||
|
33
gio/gmenu.c
33
gio/gmenu.c
@ -23,6 +23,7 @@
|
||||
|
||||
#include "gmenu.h"
|
||||
|
||||
#include "gaction.h"
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
@ -1056,14 +1057,8 @@ g_menu_item_set_action_and_target (GMenuItem *menu_item,
|
||||
*
|
||||
* Sets the "action" and possibly the "target" attribute of @menu_item.
|
||||
*
|
||||
* If @detailed_action contains a double colon ("::") then it is used as
|
||||
* a separator between an action name and a target string. In this
|
||||
* case, this call is equivalent to calling
|
||||
* g_menu_item_set_action_and_target() with the part before the "::" and
|
||||
* with a string-type #GVariant containing the part following the "::".
|
||||
*
|
||||
* If @detailed_action doesn't contain "::" then the action is set to
|
||||
* the given string (verbatim) and the target value is unset.
|
||||
* The format of @detailed_action is the same format parsed by
|
||||
* g_action_parse_detailed_name().
|
||||
*
|
||||
* See g_menu_item_set_action_and_target() or
|
||||
* g_menu_item_set_action_and_target_value() for more flexible (but
|
||||
@ -1078,21 +1073,17 @@ void
|
||||
g_menu_item_set_detailed_action (GMenuItem *menu_item,
|
||||
const gchar *detailed_action)
|
||||
{
|
||||
const gchar *sep;
|
||||
GError *error = NULL;
|
||||
GVariant *target;
|
||||
gchar *name;
|
||||
|
||||
sep = strstr (detailed_action, "::");
|
||||
if (!g_action_parse_detailed_name (detailed_action, &name, &target, &error))
|
||||
g_error ("g_menu_item_set_detailed_action: %s", error->message);
|
||||
|
||||
if (sep != NULL)
|
||||
{
|
||||
gchar *action;
|
||||
|
||||
action = g_strndup (detailed_action, sep - detailed_action);
|
||||
g_menu_item_set_action_and_target (menu_item, action, "s", sep + 2);
|
||||
g_free (action);
|
||||
}
|
||||
|
||||
else
|
||||
g_menu_item_set_action_and_target_value (menu_item, detailed_action, NULL);
|
||||
g_menu_item_set_action_and_target_value (menu_item, name, target);
|
||||
if (target)
|
||||
g_variant_unref (target);
|
||||
g_free (name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <gio/gio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gdbus-sessionbus.h"
|
||||
|
||||
@ -387,6 +388,79 @@ test_entries (void)
|
||||
g_object_unref (actions);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_detailed (void)
|
||||
{
|
||||
struct {
|
||||
const gchar *detailed;
|
||||
const gchar *expected_name;
|
||||
const gchar *expected_target;
|
||||
const gchar *expected_error;
|
||||
} testcases[] = {
|
||||
{ "abc", "abc", NULL, NULL },
|
||||
{ " abc", NULL, NULL, "invalid format" },
|
||||
{ " abc", NULL, NULL, "invalid format" },
|
||||
{ "abc:", NULL, NULL, "invalid format" },
|
||||
{ ":abc", NULL, NULL, "invalid format" },
|
||||
{ "abc(", NULL, NULL, "invalid format" },
|
||||
{ "abc)", NULL, NULL, "invalid format" },
|
||||
{ "(abc", NULL, NULL, "invalid format" },
|
||||
{ ")abc", NULL, NULL, "invalid format" },
|
||||
{ "abc::xyz", "abc", "'xyz'", NULL },
|
||||
{ "abc('xyz')", "abc", "'xyz'", NULL },
|
||||
{ "abc(42)", "abc", "42", NULL },
|
||||
{ "abc(int32 42)", "abc", "42", NULL },
|
||||
{ "abc(@i 42)", "abc", "42", NULL },
|
||||
{ "abc (42)", NULL, NULL, "invalid format" },
|
||||
{ "abc(42abc)", NULL, NULL, "invalid character in number" },
|
||||
{ "abc(42, 4)", "abc", "(42, 4)", "expected end of input" },
|
||||
{ "abc(42,)", "abc", "(42,)", "expected end of input" }
|
||||
};
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (testcases); i++)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GVariant *target;
|
||||
gboolean success;
|
||||
gchar *name;
|
||||
|
||||
success = g_action_parse_detailed_name (testcases[i].detailed, &name, &target, &error);
|
||||
g_assert (success == (error == NULL));
|
||||
if (success && testcases[i].expected_error)
|
||||
g_error ("Unexpected success on '%s'. Expected error containing '%s'",
|
||||
testcases[i].detailed, testcases[i].expected_error);
|
||||
|
||||
if (!success && !testcases[i].expected_error)
|
||||
g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (!strstr (error->message, testcases[i].expected_error))
|
||||
g_error ("Failure message '%s' for string '%s' did not contained expected substring '%s'",
|
||||
error->message, testcases[i].detailed, testcases[i].expected_error);
|
||||
|
||||
g_error_free (error);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_assert_cmpstr (name, ==, testcases[i].expected_name);
|
||||
g_assert ((target == NULL) == (testcases[i].expected_target == NULL));
|
||||
if (target)
|
||||
{
|
||||
GVariant *expected;
|
||||
|
||||
expected = g_variant_parse (NULL, testcases[i].expected_target, NULL, NULL, NULL);
|
||||
g_assert (expected);
|
||||
|
||||
g_assert (g_variant_equal (expected, target));
|
||||
g_variant_unref (expected);
|
||||
g_variant_unref (target);
|
||||
}
|
||||
|
||||
g_free (name);
|
||||
}
|
||||
}
|
||||
|
||||
GHashTable *activation_counts;
|
||||
|
||||
@ -839,6 +913,7 @@ main (int argc, char **argv)
|
||||
g_test_add_func ("/actions/simplegroup", test_simple_group);
|
||||
g_test_add_func ("/actions/stateful", test_stateful);
|
||||
g_test_add_func ("/actions/entries", test_entries);
|
||||
g_test_add_func ("/actions/parse-detailed", test_parse_detailed);
|
||||
g_test_add_func ("/actions/dbus/export", test_dbus_export);
|
||||
g_test_add_func ("/actions/dbus/threaded", test_dbus_threaded);
|
||||
g_test_add_func ("/actions/dbus/bug679509", test_bug679509);
|
||||
|
Loading…
Reference in New Issue
Block a user