Question about the g_simple_action_new_stateful function

After I managed to succeed to recreate the gtk-demo-application with GMenu and Actions to be more sure that I am understanding how Actions works, I decided to create for every item an individual action with the following two functions:

g_simple_action_new()
g_simple_action_new_stateful()

And everything works fine, but there is something what I can not figure out where and what I missing.

The problem is when I create a BOOLEAN state like here:

GVariant *state;
state      = g_variant_new_boolean ( TRUE );
mic_action = g_simple_action_new_stateful ( "microphone", NULL, state );

if I pass, NULL as the second parameter everything works fine, but with the following:

mic_action = g_simple_action_new_stateful ( "microphone", G_VARIANT_TYPE_BOOLEAN, state );

The item is not activatable anymore.

Here the code which describes the problem:

#include <gtk/gtk.h>

static void red_clbk ( GSimpleAction *action, GVariant *parameter )
{
    g_print ( "Action `%s` Selected '%s'\n", g_action_get_name ( G_ACTION ( action ) ), g_variant_get_string ( parameter, NULL ) );
}

static void green_clbk ( GSimpleAction *action, GVariant *parameter )
{
    g_print ( "Action `%s` Selected '%s'\n", g_action_get_name ( G_ACTION ( action ) ), g_variant_get_string ( parameter, NULL ) );
}

static void blue_clbk ( GSimpleAction *action, GVariant *parameter )
{
    g_print ( "Action `%s` Selected '%s'\n", g_action_get_name ( G_ACTION ( action ) ), g_variant_get_string ( parameter, NULL ) );
}

static void create_item ( GMenu *menu,
                          const gchar *const label,
                          const gchar *const action,
                          const gchar *const icon,
                          const gchar *const target,
                          const gchar *const accel )
{
    g_return_if_fail ( G_IS_MENU ( menu ) );

    GMenuItem *item           = g_menu_item_new ( NULL, NULL );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_LABEL,  "s", label, NULL  );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_ACTION, "s", action, NULL  );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_ICON,   "s", icon,  NULL  );

    if ( target )
    {
        g_menu_item_set_attribute ( item, "target", "s", target, NULL  );
    }

    g_menu_item_set_attribute ( item, "accel",  "s", accel, NULL  );
    g_menu_append_item        ( menu, item );

    g_object_unref ( item );
}

static void create_submenu_item ( GMenu *menu,
                                  GMenu *submenu,
                                  const gchar *const label )
{
    g_return_if_fail ( G_IS_MENU ( menu ) );

    GMenuItem *item           = g_menu_item_new ( NULL, NULL );

    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_LABEL,  "s", label, NULL  );
    g_menu_item_set_submenu ( item, G_MENU_MODEL ( submenu ) );
    g_menu_append_item ( menu, item );

    g_object_unref ( item );
}

static void open_clbk ( GSimpleAction *action,
                        G_GNUC_UNUSED GVariant *parameter )
{
    g_print ( "Action `%s` Called\n", g_action_get_name ( G_ACTION ( action ) ) );
}

static void activate_radio ( GSimpleAction *action, GVariant *parameter, G_GNUC_UNUSED gpointer user_data )
{
    g_action_change_state ( G_ACTION ( action ), parameter );

    const gchar *attribut;
    attribut    = g_variant_get_string ( parameter, NULL );

    if ( g_strcmp0 ( attribut, "red" ) == 0 )
    {
        red_clbk ( action, parameter );
    }
    else if ( g_strcmp0 ( attribut, "green" ) == 0 )
    {
        green_clbk ( action, parameter );
    }
    else if ( g_strcmp0 ( attribut, "blue" ) == 0 )
    {
        blue_clbk ( action, parameter );
    }
    else
    {
        g_assert_not_reached ();
    }
}


static void change_radio_state ( GSimpleAction *action, GVariant *state, G_GNUC_UNUSED gpointer user_data )
{
    g_simple_action_set_state ( action, state );
    activate_radio ( action, state, user_data );

}

static void mic_check_clbk ( GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter )
{
    GVariant *action_state;
    action_state = g_action_get_state ( G_ACTION ( action ) );

    if ( g_variant_get_boolean ( action_state ) == FALSE )
    {
        g_simple_action_set_state ( action, g_variant_new_boolean ( TRUE ) );
        g_print ( "Microfon Activated.\n" );
    }
    else
    {
        g_simple_action_set_state ( action, g_variant_new_boolean ( FALSE ) );
        g_print ( "\tMicrofon deactivated.\n" );
    }
}

static void startup ( GtkApplication *application )
{
    GMenu *main_menu;
    GMenu *file_menu;

    GSimpleAction *webcam_action;
    GSimpleAction *mic_action;

    GSimpleAction *color_action;

    GSimpleAction *red_action;
    GSimpleAction *green_action;
    GSimpleAction *blue_action;

    /// ***
    webcam_action   = g_simple_action_new ( "webcam", NULL );

    GVariant *state_color;
    state_color     = g_variant_new_string ( "green" );
    color_action    = g_simple_action_new_stateful ( "color", G_VARIANT_TYPE_STRING, state_color );

    red_action      = g_simple_action_new ( "red",   NULL );
    green_action    = g_simple_action_new ( "green", NULL );
    blue_action     = g_simple_action_new ( "blue",  NULL );

    /// ***
    GVariant *state;
    state      = g_variant_new_boolean ( TRUE );

    /// *** THIS does not seem to work!!!!!!!!!!!!!!!
    ///mic_action = g_simple_action_new_stateful ( "microphone", G_VARIANT_TYPE_BOOLEAN, state );

    /// *** But THIS works fine.
    mic_action = g_simple_action_new_stateful ( "microphone", NULL, state );

    /// ***
    g_signal_connect_swapped ( webcam_action, "activate", G_CALLBACK ( open_clbk ),          webcam_action );
    g_signal_connect         ( mic_action,    "activate", G_CALLBACK ( mic_check_clbk ),     mic_action );
    g_signal_connect         ( color_action,  "activate", G_CALLBACK ( change_radio_state ), color_action );


    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( webcam_action ) );
    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( mic_action ) );

    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( color_action ) );

    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( red_action ) );
    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( green_action ) );
    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( blue_action ) );

    /// ***
    main_menu = g_menu_new ();
    file_menu = g_menu_new();

    /// ***
    create_item ( file_menu, "WebCam", "app.webcam", "camera-web", NULL, "<CTRL>O" );

    /// ***
    create_item ( file_menu, "Microphone", "app.microphone", "audio-input-microphone", NULL, "<CTRL>M" );

    /// ***
    GMenu *submenu = g_menu_new();
    create_submenu_item ( file_menu, submenu, "Choose" );

    create_item ( submenu, "Red",   "app.color", "audio-input-microphone", "red", "<CTRL>R" );
    create_item ( submenu, "green", "app.color", "audio-input-microphone", "green", "<CTRL>G" );
    create_item ( submenu, "Blue",  "app.color", "audio-input-microphone", "blue", "<CTRL>B" );

    /// ***
    g_menu_insert_submenu ( main_menu, 0, "Preferences",  G_MENU_MODEL ( file_menu ) );

    /// ***
    gtk_application_set_menubar ( application, G_MENU_MODEL ( main_menu ) );
    g_object_unref ( main_menu );
}

static void activate ( GtkApplication *app )
{
    GtkWidget *window;
    window = gtk_application_window_new ( app );

    gtk_window_set_application ( GTK_WINDOW ( window ), GTK_APPLICATION ( app ) );
    gtk_window_set_title ( GTK_WINDOW ( window ), "Hello GNOME" );

    gtk_widget_show_all ( GTK_WIDGET ( window ) );
}

int main ( int argc, char **argv )
{
    GtkApplication *app;
    int status;

    app = gtk_application_new  ( "org.gtk.example", G_APPLICATION_FLAGS_NONE );
    g_signal_connect_swapped   ( app, "startup",    G_CALLBACK ( startup ),  app );
    g_signal_connect_swapped   ( app, "activate",   G_CALLBACK ( activate ), app );
    status = g_application_run ( G_APPLICATION ( app ), argc, argv );
    g_object_unref ( app );
    return status;
}

in the case of the following code:

GVariant *state_color;
state_color     = g_variant_new_string ( "green" );
color_action    = g_simple_action_new_stateful ( "color", G_VARIANT_TYPE_STRING, state_color );

Passing G_VARIANT_TYPE_STRING works fine.

The second parameter is the parameter type, that is, the type of the parameter passed to activate().

While you changed the parameter type to boolean, you didn’t change the corresponding activate() call – you still pass NULL (for no parameter) there, which no longer matches the expected type.

1 Like

Could you please be a little bit more precise? I am not sure that I am follow you…

This bit of code sets the menu item’s target property, which will be used as parameter when activating the associated action.

For an action with a boolean parameter type, that should be

  g_menu_item_set_attribute (item, "target", "b", value);

with value being either TRUE or FALSE.

But you create the menu item with no target at all, so the menu item cannot activate the action with a parameter of the required type.

(Note that boolean parameter types are quite rare. UI elements like checkbox menu items or toggle buttons expect an action with a boolean state and no parameter, and activating the action toggles the state)

1 Like

I think I understand now. Anyway I adapted my code to your suggestions ( if I was understanding it right) and seems to work, but when I activate the Microphone Item there is no “Check” item icon in front of the microphone icon anymore:

Microphone

If I change the call:

g_menu_item_set_attribute ( item, "target", "b", value );

with this one:
g_menu_item_set_attribute_value ( item, G_MENU_ATTRIBUTE_TARGET, value );

I get instead of the check sign the dot sign:

new_mci

Here is the working ( minimized) code:

#include <gtk/gtk.h>

static void create_item ( GMenu *menu,
                          const gchar *const label,
                          const gchar *const action,
                          const gchar *const icon,
                          ///gboolean target,
                          GVariant *target,
                          const gchar *const accel )
{
    g_return_if_fail ( G_IS_MENU ( menu ) );

    GMenuItem *item           = g_menu_item_new ( NULL, NULL );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_LABEL,  "s", label, NULL  );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_ACTION, "s", action, NULL  );
    g_menu_item_set_attribute ( item, G_MENU_ATTRIBUTE_ICON,   "s", icon,  NULL  );
    ///g_menu_item_set_attribute ( item, "target", "b", target );
    g_menu_item_set_attribute_value ( item, G_MENU_ATTRIBUTE_TARGET, target );
    g_menu_item_set_attribute ( item, "accel",  "s", accel, NULL  );
    g_menu_append_item        ( menu, item );

    g_object_unref ( item );
}

static void mic_check_clbk ( GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, G_GNUC_UNUSED gpointer user_data )
{
    g_return_if_fail ( G_IS_SIMPLE_ACTION ( action ) );
    GVariant *action_state;
    action_state = g_action_get_state ( G_ACTION ( action ) );

    if ( g_variant_get_boolean ( action_state ) == FALSE )
    {
        g_simple_action_set_state ( action, g_variant_new_boolean ( TRUE ) );
        g_print ( "Microfon Activated.\n" );
    }
    else
    {
        g_simple_action_set_state ( action, g_variant_new_boolean ( FALSE ) );
        g_print ( "\tMicrofon deactivated.\n" );
    }
}

static void startup ( GtkApplication *application )
{
    GMenu *main_menu;
    GMenu *file_menu;

    GSimpleAction *mic_action;

    /* Create the State for the Action */
    GVariant *state;
    state    = g_variant_new_boolean ( TRUE );

    /* Create the Action */
    mic_action = g_simple_action_new_stateful ( "microphone", G_VARIANT_TYPE_BOOLEAN, state );

    /* Monitoring the "activate" Signal */
    g_signal_connect_swapped ( mic_action,    "activate", G_CALLBACK ( mic_check_clbk ),     mic_action );

    /* Add the Action to the application*/
    g_action_map_add_action ( G_ACTION_MAP ( application ), G_ACTION ( mic_action ) );

    /* Create the Menu */
    main_menu = g_menu_new ();

    /* Create the File submenu */
    file_menu = g_menu_new();

    /* Create the "Microphone" Item */
    create_item ( file_menu,
                  "Microphone",
                  "app.microphone",
                  "audio-input-microphone",
                  state,
                  "<CTRL>M" );

    /* Insert the file menu into the main Manu */
    g_menu_insert_submenu ( main_menu, 0, "Preferences",  G_MENU_MODEL ( file_menu ) );

    /* Setting the Application MenuBar */
    gtk_application_set_menubar ( application, G_MENU_MODEL ( main_menu ) );

    /* Cleaning up Memory */
    g_object_unref ( mic_action );
    g_object_unref ( main_menu );
    g_object_unref ( file_menu );
}

static void activate ( GtkApplication *app )
{
    GtkWidget *window;
    window = gtk_application_window_new ( app );

    gtk_window_set_application ( GTK_WINDOW ( window ), GTK_APPLICATION ( app ) );
    gtk_window_set_title ( GTK_WINDOW ( window ), "Hello GNOME" );

    gtk_widget_show_all ( GTK_WIDGET ( window ) );
}

int main ( int argc, char **argv )
{
    GtkApplication *app;
    int status;

    app = gtk_application_new  ( "org.gtk.example", G_APPLICATION_FLAGS_NONE );
    g_signal_connect_swapped   ( app, "startup",    G_CALLBACK ( startup ),  app );
    g_signal_connect_swapped   ( app, "activate",   G_CALLBACK ( activate ), app );
    status = g_application_run ( G_APPLICATION ( app ), argc, argv );
    g_object_unref ( app );
    return status;
}

If you want a menu item with a checkbox, the action should have a boolean state and no parameter.

Quoting the relevant documentation:

An action with a boolean state will most typically be used with a “toggle” or “switch” menu item. The state can be set directly, but activating the action (with no parameter) results in the state being toggled.

Selecting a toggle menu item will activate the action. The menu item should be rendered as “checked” when the state is true.

1 Like

Well you made my Day and this is because I was almost sure that I understood and now reading your Answer makes everything clear.

Thank you ( Vielen Dank)