Stateful action

Some actions have states. The typical values of states is boolean or string. However, other types of states are possible if you want.

There’s an example menu2_int16.c in the src/men directory. It behaves the same as menu2.c. But it uses gint16 type of states instead of string type.

Actions which have states are called stateful.

Stateful action without a parameter

Some menus are called toggle menu. For example, fullscreen menu has a state which has two values – fullscreen and non-fullscreen. The value of the state is changed every time the menu is clicked. An action corresponds to the fullscreen menu also have a state. Its value is TRUE or FALSE and it is called boolean value. TRUE corresponds to fullscreen and FALSE to non-fullscreen.

The following is an example code to implement a fullscreen menu except the signal handler. The signal handler will be described after the explanation of this code.

static void
app_activate (GApplication *app, gpointer user_data) {
  ... ... ...
  GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen",
                                  NULL, g_variant_new_boolean (FALSE));
  GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
  g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
  ... ... ...
}

The following is the “change-state” signal handler.

static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
  if (g_variant_get_boolean (value))
    gtk_window_maximize (GTK_WINDOW (win));
  else
    gtk_window_unmaximize (GTK_WINDOW (win));
  g_simple_action_set_state (action, value);
}

You can use “activate” signal instead of “change-state” signal, or both signals. But the way above is the simplest and the best.

GVariant

GVarient can contain boolean, string or other type values. For example, the following program assigns TRUE to value whose type is GVariant.

GVariant *value = g_variant_new_boolean (TRUE);

Another example is:

GVariant *value2 = g_variant_new_string ("Hello");

value2 is a GVariant and it has a string type value “Hello”. GVariant can contain other types like int16, int32, int64, double and so on.

If you want to get the original value, use g_variant_get series functions. For example, you can get the boolean value by g_variant_get_boolean.

gboolean bool = g_variant_get_boolean (value);

Because value has been created as a boolean type GVariant and TRUE value, bool equals TRUE. In the same way, you can get a string from value2

const char *str = g_variant_get_string (value2, NULL);

The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long). If it isn’t NULL, then the length of the string will be set by the function. If it is NULL, nothing happens. The returned string str can’t be changed.

Stateful action with a parameter

Another example of stateful actions is an action corresponds to color select menus. For example, there are three menus and each menu has red, green or blue color respectively. They determine the background color of a certain widget. One action is connected to the three menus. The action has a state which values are “red”, “green” and “blue”. The values are string. Those colors are given to the signal handler as a parameter.

static void
app_activate (GApplication *app, gpointer user_data) {
  ... ... ...
  GSimpleAction *act_color = g_simple_action_new_stateful ("color",
                     g_variant_type_new("s"), g_variant_new_string ("red"));
  GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
  GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
  GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
  g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
  ... ... ...
}

The following is the “activate” signal handler.

static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
  char *color = g_strdup_printf ("label#lb {background-color: %s;}",
                                   g_variant_get_string (parameter, NULL));
  gtk_css_provider_load_from_data (provider, color, -1);
  g_free (color);
  g_action_change_state (G_ACTION (action), parameter);
}

Note: If you have set a “change-state” signal handler, g_action_change_state will emit “change-state” signal instead of calling g_simple_action_set_state.

GVariantType

GVariantType gives a type of GVariant. GVariant can contain many kinds of types. And the type often needs to be recognized at runtime. GVariantType provides such functionality.

GVariantType is created with a string which expresses a type.

The following program is a simple example. It finally outputs the string “s”.

#include <glib.h>

int
main (int argc, char **argv) {
  GVariantType *vtype = g_variant_type_new ("s");
  const char *type_string = g_variant_type_peek_string (vtype);
  g_print ("%s\n",type_string);
}

Example code

The following code includes stateful actions above. This program has menus like this:

menu2

The code is as follows.

#include <gtk/gtk.h>

static GtkCssProvider *provider;

static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
  if (g_variant_get_boolean (value))
    gtk_window_maximize (GTK_WINDOW (win));
  else
    gtk_window_unmaximize (GTK_WINDOW (win));
  g_simple_action_set_state (action, value);
}

static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
  char *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
  gtk_css_provider_load_from_data (provider, color, -1);
  g_free (color);
  g_action_change_state (G_ACTION (action), parameter);
}

static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
{
  g_application_quit (G_APPLICATION(app));
}

static void
app_activate (GApplication *app, gpointer user_data) {
  GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
  gtk_window_set_title (GTK_WINDOW (win), "menu2");
  gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);

  GtkWidget *lb = gtk_label_new (NULL);
  gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
  gtk_window_set_child (GTK_WINDOW (win), lb);

  GSimpleAction *act_fullscreen
    = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
  GSimpleAction *act_color
    = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
  GSimpleAction *act_quit
    = g_simple_action_new ("quit", NULL);

  GMenu *menubar = g_menu_new ();
  GMenu *menu = g_menu_new ();
  GMenu *section1 = g_menu_new ();
  GMenu *section2 = g_menu_new ();
  GMenu *section3 = g_menu_new ();
  GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
  GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
  GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
  GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
  GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");

  g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
  g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
  g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
  g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
  g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
  g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));

  g_menu_append_item (section1, menu_item_fullscreen);
  g_menu_append_item (section2, menu_item_red);
  g_menu_append_item (section2, menu_item_green);
  g_menu_append_item (section2, menu_item_blue);
  g_menu_append_item (section3, menu_item_quit);
  g_object_unref (menu_item_red);
  g_object_unref (menu_item_green);
  g_object_unref (menu_item_blue);
  g_object_unref (menu_item_fullscreen);
  g_object_unref (menu_item_quit);

  g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
  g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
  g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
  g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));

  gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
  gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);

/*  GtkCssProvider *provider = gtk_css_provider_new ();*/
  provider = gtk_css_provider_new ();
  GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
  gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
  gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
                                              GTK_STYLE_PROVIDER_PRIORITY_USER);

/*  gtk_widget_show (win);*/
  gtk_window_present (GTK_WINDOW (win));
}

#define APPLICATION_ID "com.github.ToshioCP.menu2"

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

  app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);

  stat =g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return stat;
}