Checking a menu item

I am already aware that it is not the best title, but we are going to come to my problem.

After I got the Source Code of gtk_demo_application from Emmanuele Bassi ( Thank you by the way) I tried to implement it my Self.

In the Source code I noticed that GtkAccelLabel was used , but me I am only interested in GMenu and Actions, so I did my own menu which looks almost the same like the original.
Here is the Picture of gtk-demo-application:
gtk_demo_application_old

And here is the Picture of my Own Menu:
gtk_demo_application_new

As you can see, more or less is the same.

Now there is something which I am not clear, If one take a look at the color submenu there are 3 items:

  • red
  • green
  • blue

As you can see, if one clicks on one of those tree items , there is a dot in front of it which gets selected based on the item i choose.

I would like to know How exactly was implemented, because I can not figure out my self from the source code and how can I adapt it to my Code.

Here is the Source code of what I have so far:

#include <gtk/gtk.h>

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

    if ( g_variant_get_boolean ( action_state ) == FALSE )
    {
        g_print ( "The DarkTheme Menu was Checked...\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( TRUE ) );
    }
    else
    {
        g_print ( "The DarkTheme Menu was Unchecked\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( FALSE ) );
    }

    /* Not used */
    ( void ) parameter;
}

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

    if ( g_variant_get_boolean ( action_state ) == FALSE )
    {
        g_print ( "The Hide Title Menu was Checked...\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( TRUE ) );
    }
    else
    {
        g_print ( "The Hide Title Menu was Unchecked\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( FALSE ) );
    }

    /* Not used */
    ( void ) parameter;
}

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

    if ( g_variant_get_boolean ( action_state ) == FALSE )
    {
        g_print ( "The Bold Menu was Checked...\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( TRUE ) );
    }
    else
    {
        g_print ( "The Bold Menu was Unchecked\n" );
        g_simple_action_set_state ( action, g_variant_new_boolean ( FALSE ) );
    }

    /* Not used */
    ( void ) parameter;
}

static void red_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void green_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void blue_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void square_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void rectangle_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void oval_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void startup ( GtkApplication *application )
{

    static GActionEntry actions[] =
    {
        { "red",   red_clbk,   NULL, NULL, NULL, { 0, 0, 0 } },
        { "green", green_clbk, NULL, NULL, NULL, { 0, 0, 0 } },
        { "blue",  blue_clbk, NULL, NULL, NULL, { 0, 0, 0 } },
        /* */
        { "square",    square_clbk,    NULL, NULL, NULL, { 0, 0, 0 } },
        { "rectangle", rectangle_clbk, NULL, NULL, NULL, { 0, 0, 0 } },
        { "oval",      oval_clbk,      NULL, NULL, NULL, { 0, 0, 0 } },
    };

    GMenu *main_menu;
    GMenu *file_menu;
    GMenuItem *item;

    GActionMap    *action_map;
    GSimpleAction *theme_action;
    GSimpleAction *tBar_action;
    GSimpleAction *bold_action;

    GVariant *state;

    /// ***
    state = g_variant_new ( "b", FALSE );

    /// ***
    action_map   = G_ACTION_MAP ( application );

    theme_action = g_simple_action_new_stateful ( "theme", NULL, state );
    tBar_action  = g_simple_action_new_stateful ( "tbar",  NULL, state );
    bold_action  = g_simple_action_new_stateful ( "bold",  NULL, state );

    /// ***
    g_action_map_add_action ( action_map, G_ACTION ( theme_action ) );
    g_action_map_add_action ( action_map, G_ACTION ( tBar_action ) );
    g_action_map_add_action ( action_map, G_ACTION ( bold_action ) );

    g_action_map_add_action_entries ( G_ACTION_MAP ( application ), actions, G_N_ELEMENTS ( actions ), NULL );

    /// ***
    g_signal_connect_swapped ( tBar_action,  "activate", G_CALLBACK ( titlebar_clbk ),  tBar_action );
    g_signal_connect_swapped ( theme_action, "activate", G_CALLBACK ( theme_clbk ),     theme_action );
    g_signal_connect_swapped ( bold_action, "activate", G_CALLBACK ( bold_clbk ),   bold_action );

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

    /// ***
    item = g_menu_item_new ( "Prefer Dark Theme", "app.theme" );
    g_menu_append_item ( file_menu, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Hide Titlebar when maximized", "app.tbar" );
    g_menu_append_item ( file_menu, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    GMenu *submenu;
    GMenu *submenu2;
    submenu  = g_menu_new ();
    submenu2 = g_menu_new ();

    /// ***
    item = g_menu_item_new ( "Color", NULL );
    g_menu_item_set_submenu ( item, G_MENU_MODEL ( submenu ) );
    g_menu_insert_item ( file_menu, 2, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Red", "app.red" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>R", NULL  );
    g_menu_insert_item ( submenu, 0, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Green", "app.green" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>G", NULL  );
    g_menu_insert_item ( submenu, 1, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Blue", "app.blue" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>B", NULL  );
    g_menu_insert_item ( submenu, 2, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Shape", NULL );
    g_menu_item_set_submenu ( item, G_MENU_MODEL ( submenu2 ) );
    g_menu_insert_item ( file_menu, 3, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Square", "app.square" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>S", NULL  );
    g_menu_insert_item ( submenu2, 0, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Rectangle", "app.rectangle" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl><Alt>R", NULL  );
    g_menu_insert_item ( submenu2, 1, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Oval", "app.oval" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>O", NULL  );
    g_menu_insert_item ( submenu2, 2, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Bold", "app.bold" );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl><Shift>B", NULL  );
    g_menu_append_item ( file_menu, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    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;
}
1 Like

You’ve already discovered stateful actions

As well as boolean you can also have string or integer states for doing radio menus

https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/demos/gtk-demo/menus.ui#L68

<submenu>
  <attribute name="label" translatable="yes">_Shape</attribute>
  <section>
    <item>
      <attribute name="label" translatable="yes">_Square</attribute>
      <attribute name="action">win.shape</attribute>
      <attribute name="target">square</attribute>
      <attribute name="accel">&lt;Primary&gt;s</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">_Rectangle</attribute>
      <attribute name="action">win.shape</attribute>
      <attribute name="target">rectangle</attribute>
      <attribute name="accel">&lt;Primary&gt;r</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">_Oval</attribute>
      <attribute name="action">win.shape</attribute>
      <attribute name="target">oval</attribute>
      <attribute name="accel">&lt;Primary&gt;o</attribute>
    </item>
  </section>
</submenu>

https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/demos/gtk-demo/application.c#L326

static GActionEntry win_entries[] = {
  { "titlebar", activate_toggle, NULL, "false", change_titlebar_state },
  { "shape", activate_radio, "s", "'oval'", change_radio_state },
  { "bold", activate_toggle, NULL, "false", NULL },
  { "about", activate_about, NULL, NULL, NULL },
  { "file1", activate_action, NULL, NULL, NULL },
  { "logo", activate_action, NULL, NULL, NULL }
};
static void
activate_radio (GSimpleAction *action,
                GVariant      *parameter,
                gpointer       user_data)
{
  show_action_infobar (action, parameter, user_data);

  g_action_change_state (G_ACTION (action), parameter);
}
static void
change_radio_state (GSimpleAction *action,
                    GVariant      *state,
                    gpointer       user_data)
{
  g_simple_action_set_state (action, state);
}
1 Like

I tried to adapt my code, based on your instruction, but something is not right.
Here is a picture ( I reduced the code):
shape

Everything looks fine, but I am not sure that I understand it. Do I need to work only with one callback when Square, Rectangle and Oval items are clicked, or I can call for every item different callbacks.

This is what I have so far:

#include <gtk/gtk.h>

static void activate_radio ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_action_change_state ( G_ACTION ( action ), parameter );
    /* Not used */
    ( void ) user_data;
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );
}
static void change_radio_state ( GSimpleAction *action, GVariant *state, gpointer user_data )
{
    g_simple_action_set_state ( action, state );
    /* Not used */
    ( void ) user_data;
}

static void square_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void rectangle_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void oval_clbk ( GSimpleAction *action, GVariant *parameter, gpointer user_data )
{
    g_print ( "Action %s Selected\n", g_action_get_name ( G_ACTION ( action ) ) );

    /* Not used */
    ( void ) parameter;
    ( void ) user_data;
}

static void startup ( GtkApplication *application )
{

    static GActionEntry actions[] =
    {
        { "shape",     activate_radio, "s",  "'oval'", change_radio_state, { 0, 0, 0 } },
        { "square",    square_clbk,    NULL, NULL, NULL, { 0, 0, 0 } },
        { "rectangle", rectangle_clbk, NULL, NULL, NULL, { 0, 0, 0 } },
        { "oval",      oval_clbk,      NULL, NULL, NULL, { 0, 0, 0 } },
    };

    GMenu *main_menu;
    GMenu *file_menu;
    GMenuItem *item;
    GActionMap    *action_map;

    /// ***
    action_map   = G_ACTION_MAP ( application );
    g_action_map_add_action_entries ( action_map, actions, G_N_ELEMENTS ( actions ), NULL );

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


    /// ***
    GMenu *submenu2;
    submenu2 = g_menu_new ();


    /// ***
    item = g_menu_item_new ( "Shape", NULL );
    g_menu_item_set_submenu ( item, G_MENU_MODEL ( submenu2 ) );
    g_menu_insert_item ( file_menu, 3, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Square", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "square", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>S", NULL  );
    g_menu_insert_item ( submenu2, 0, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Rectangle", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "rectangle", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl><Alt>R", NULL  );
    g_menu_insert_item ( submenu2, 1, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Oval", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "oval", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>O", NULL  );
    g_menu_insert_item ( submenu2, 2, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    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;
}

At this point the Output is:

Action shape Selected
Action shape Selected
Action shape Selected

But I was expected to be:

Action square Selected
Action rectangle Selected
Action oval Selected

Your square, rectangle, oval actions are never used, instead shape has square, rectangle, oval states

Just as your toggeles before had a boolean state

Try

static void
activate_radio (GSimpleAction          *action,
                GVariant               *parameter,
                G_GNUC_UNUSED gpointer  user_data)
{
    g_autoptr (GVariant) action_state = NULL;
    g_autofree char *state_str = NULL;

    g_action_change_state (G_ACTION (action), parameter);

    action_state = g_action_get_state (G_ACTION (action));
    state_str = g_variant_print (action_state, TRUE);

    g_print ("Action %s Selected: %s\n",
             g_action_get_name (G_ACTION (action)),
             state_atr);
}

… and you should see what’s happening

1 Like

Thank you and god bless you.

I managed to finish it with your help and now it is ok:
shape_app

Here is the working code:

#include <gtk/gtk.h>

static void activate_radio ( GSimpleAction *action, GVariant *parameter, G_GNUC_UNUSED gpointer user_data )
{
    g_autoptr ( GVariant ) action_state = NULL;
    g_autofree char *state_atr = NULL;

    g_action_change_state ( G_ACTION ( action ), parameter );

    action_state = g_action_get_state ( G_ACTION ( action ) );
    state_atr    = g_variant_print ( action_state, TRUE );

    g_print ( "Action %s Selected: %s\n", g_action_get_name ( G_ACTION ( action ) ), state_atr );
}

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

}
static void startup ( GtkApplication *application )
{

    static GActionEntry actions[] =
    {
        { "shape",     activate_radio, "s",  "'oval'", change_radio_state, { 0, 0, 0 } },
    };

    GMenu *main_menu;
    GMenu *file_menu;
    GMenuItem *item;
    GActionMap    *action_map;

    /// ***
    action_map   = G_ACTION_MAP ( application );
    g_action_map_add_action_entries ( action_map, actions, G_N_ELEMENTS ( actions ), NULL );

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


    /// ***
    GMenu *submenu2;
    submenu2 = g_menu_new ();


    /// ***
    item = g_menu_item_new ( "Shape", NULL );
    g_menu_item_set_submenu ( item, G_MENU_MODEL ( submenu2 ) );
    g_menu_insert_item ( file_menu, 3, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Square", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "square", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>S", NULL  );
    g_menu_insert_item ( submenu2, 0, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Rectangle", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "rectangle", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl><Alt>R", NULL  );
    g_menu_insert_item ( submenu2, 1, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    item = g_menu_item_new ( "Oval", "app.shape" );
    g_menu_item_set_attribute ( item, "target", "s", "oval", NULL  );
    g_menu_item_set_attribute ( item, "accel", "s", "<Ctrl>O", NULL  );
    g_menu_insert_item ( submenu2, 2, G_MENU_ITEM ( item ) );
    g_object_unref ( item );

    /// ***
    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;
}

Now if you are agree I would like to ask you two more Questions:

  1. How should now handle the call functions when the item is selected, inside activate_radio should I call the callback functions based on the action?

for example:

static void activate_radio ( GSimpleAction *action, GVariant *parameter, G_GNUC_UNUSED gpointer user_data )
{
    g_autoptr ( GVariant ) action_state = NULL;
    g_autofree char *state_atr = NULL;

    g_action_change_state ( G_ACTION ( action ), parameter );

    action_state = g_action_get_state ( G_ACTION ( action ) );
    state_atr    = g_variant_print ( action_state, TRUE );

    g_print ( "Action %s Selected: %s\n", g_action_get_name ( G_ACTION ( action ) ), state_atr );
    
    /* How do I call this functions? */
    /* based on the selected action */
    if ( state_atr == square )
    {
        square_clbk();
    }else if( state_atr == rectangle){
        rectangle_clbk()
    }else{
        oval_clbk()
    }
}
  1. Does G_GNUC_UNUSED work only with GCC and on any platform?

Use g_variant_get_string().

Use something like:

  if (g_strcmp0 (state_atr, "square") == 0)
    square_clbk ();
  else if (g_strcmp0 (state_atr, "rectangle") == 0)
    rectangle_clbk ();
  else if (g_strcmp0 (state_atr, "oval") == 0)
    oval_clbk ();
  else
    g_assert_not_reached ();
1 Like

So I was on the right track with this one.
Thank you so much.

1 Like

/me was just demoing what was happening

1 Like