Stateful Action
Some actions have states. The typical values of states are boolean or string values. However, other types of states are possible if necessary.
Actions which have states are called stateful.
Stateful Action without a Parameter
Some menus are called toggle menus. For example, the “Maximized” menu has two state values – maximized and unmaximized. The value of the state is changed every time the menu is clicked. An action that corresponds to the “Maximized” menu also has a state. The value of the state is boolean, which is TRUE or FALSE. TRUE corresponds to maximized and FALSE to unmaximized.
The following is an example code to implement a “Maximized”
menu. The code of maximize_state_changed will be
shown later.
GSimpleAction *act_maximize = g_simple_action_new_stateful ("maximize",
NULL, g_variant_new_boolean (FALSE));
g_signal_connect (act_maximize, "change-state", G_CALLBACK (maximize_state_changed), win);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_maximize));
... ... ...
GMenuItem *menu_item_maximize = g_menu_item_new ("Maximized", "win.maximize");act_maximizeis a GSimpleAction instance. It is created withg_simple_action_new_stateful. The function has three arguments. The first argument “maximize” is the name of the action. The second argument is a parameter type.NULLmeans the action doesn’t have a parameter. The third argument is the initial state of the action. It is a GVariant value. GVariant will be explained in the next subsection. The functiong_variant_new_boolean (FALSE)returns a GVariant value ofFALSE. If there are two or more top-level windows, each window has its ownact_maximizeaction. So, the number of the actions is the same as the number of the windows.- The
act_maximizeaction has a “change-state” signal. The signal is connected to a handlermaximize_state_changed. If the “Maximized” menu is clicked, then the corresponding actionact_maximizeis activated. But no handler is connected to the “activate” signal. Then, the default behavior for boolean-stated actions with a NULL parameter type likeact_maximizeis to toggle them via the “change-state” signal. - The action is added to the GtkWindow,
win. Therefore, the scope of the action is “win”. menu_item_maximizeis a GMenuItem instance. There are two arguments. The first argument “Maximized” is the label ofmenu_item_maximize. The second argument is an action. The action “win.maximize” has a prefix “win” and an action name “maximize”. The prefix indicates that the action belongs to the window.
static void
maximize_state_changed(GSimpleAction *action, GVariant *value, GtkWindow *win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (win);
else
gtk_window_unmaximize (win);
g_simple_action_set_state (action, value);
}This is the signal handler that has been connected to the “change-state” signal of the “maximize” action.
- The handler
maximize_state_changedhas three parameters. The first parameter is the action which emits the “change-state” signal. The second parameter is the value of the new state of the action. The third parameter is the user data that was passed tog_signal_connect. - If the value is of boolean type and
TRUE, then it maximizes the window. Otherwise it unmaximizes it. - Sets the state of the action with
value. Note: Beforeg_simple_action_set_stateis called, the action still holds its original state. You must call this function to update the action to the new state.
You can use “activate” signal instead of “change-state” signal, or both signals. But the way above is the simplest and the best.
GVariant
GVariant is a fundamental type. It isn’t a child of GObject.
GVariant 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
with g_variant_get_boolean.
gboolean bool = g_variant_get_boolean (value);Since value has been created as a boolean type
GVariant with 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
variable is used by the function for the string length. If it is
NULL, nothing happens. The returned string str is
owned by the instance and can’t be changed or freed by the
caller.
Stateful Action with a Parameter
Another example of stateful actions is an action that 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 GtkLabel widget. One action is connected to the three menus. The action has a state whose value is “red”, “green” or “blue”. The values are string. Those colors are given to the signal handler as a parameter.
... ... ...
GVariantType *vtype = g_variant_type_new("s");
GSimpleAction *act_color
= g_simple_action_new_stateful ("color", vtype, g_variant_new_string ("red"));
g_variant_type_free (vtype);
GMenuItem *menu_item_red = g_menu_item_new ("Red", "app.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "app.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "app.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), NULL);
... ... ...- GVariantType is a C structure and it keeps a type of
GVariant. It is created with the function
g_variant_type_new. The argument of the function is a GVariant type string. So,g_variant_type_new("s")returns a GVariantType structure containing a string type. The returned value, GVariantType structure, is owned by the caller. So, you need to free it when it is no longer needed. - The variable
act_colorpoints to a GSimpleAction instance. It is created withg_simple_action_new_stateful. The function has three arguments. The first argument “color” is the name of the action. The second argument is a parameter type which is GVariantType.g_variant_type_new("s")creates GVariantType which is a string type (G_VARIANT_TYPE_STRING). The third argument is the initial state of the action. It is a GVariant. The functiong_variant_new_string ("red")returns a GVariant value which has the string value “red”. GVariant has a reference count andg_variant_new_...series functions return a GVariant value with a floating reference. That means the caller doesn’t own the value at this point. Andg_simple_action_new_statefulfunction consumes the floating reference so you don’t need to care about releasing the GVariant instance. - The GVariantType structure
vtypeis useless afterg_simple_action_new_stateful. It is released with the functiong_variant_type_free. - The variable
menu_item_redpoints to a GMenuItem instance. The functiong_menu_item_newhas two arguments. The first argument “Red” is the label ofmenu_item_red. The second argument is a detailed action. Its prefix is “app”, the action name is “color” and the target is “red”. Target is sent to the action as a parameter. The same goes formenu_item_greenandmenu_item_blue. - The function
g_signal_connectconnects the activate signal on the actionact_colorand the handlercolor_activated. If one of the three menus is clicked, then the actionact_coloris activated with the target (parameter) which is given by the menu.
The following is the “activate” signal handler.
static void
color_activated(GSimpleAction *action, GVariant *parameter) {
char *color = g_strdup_printf ("label.lb {background-color: %s;}",
g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_string (provider, color);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}- The handler originally has three parameters. The third
parameter is the user data set in the
g_signal_connectfunction. But it is left out because the fourth argument of theg_signal_connecthas been NULL. The first parameter is the action which emits the “activate” signal. The second parameter is the parameter, or target, given to the action. It is a color specified by the menu. - The variable
coloris a CSS string created byg_strdup_printf. The arguments ofg_strdup_printfare the same as printf C standard function. The functiong_variant_get_stringgets the string contained inparameter. The string is owned by the instance and you mustn’t change or free it. The stringlabel.lbis a selector. It consists oflabel, a node name of GtkLabel, andlbwhich is a class name. It selects GtkLabel which haslbclass. For example, menus have GtkLabel to display their labels, but they don’t havelbclass. So, the CSS doesn’t change their background color. The string{background-color: %s;}makes the background color%sto which the color fromparameteris assigned. - Frees the string
color. - Changes the state with
g_action_change_state.
Note: If you haven’t set an “activate” signal handler, the signal is forwarded to “change-state” signal. So, you can use “change-state” signal instead of “activate” signal. See src/menu/menu2_change_state.c.
GVariantType
GVariantType gives a type of GVariant. GVariantType is created with a type string.
- “b” means boolean type.
- “s” means string 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);
g_variant_type_free (vtype);
}- The function
g_variant_type_newcreates a GVariantType structure. The argument “s” is a type string. It means string. The returned structure is owned by the caller. When it becomes useless, you need to free it with the functiong_variant_type_free. - The function
g_variant_type_peek_stringtakes a peek atvtype. It is the string “s” given tovtypewhen it was created. The string is owned by the instance and the caller can’t change or free it. - Prints the string to the terminal. You can’t free
vtypebeforeg_printbecause the stringtype_stringis owned byvtype. - Frees
vtype.
Example
The following code includes stateful actions above. This program has menus like this:
- The “Maximized” menu toggles the size of the window between maximized and unmaximized. If the window is maximized, a check mark is put before the “Maximized” label.
- Red, green and blue menu determines the back ground color of the label in the window. The menus have radio buttons on the left of the menus. And the radio button of the selected menu turns on.
- The “quit” menu quits the application.
The code is as follows.
#include <gtk/gtk.h>
/* The provider below provides application wide CSS data. */
GtkCssProvider *provider;
static void
maximize_state_changed(GSimpleAction *action, GVariant *value, GtkWindow *win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (win);
else
gtk_window_unmaximize (win);
g_simple_action_set_state (action, value);
}
static void
color_activated(GSimpleAction *action, GVariant *parameter) {
char *color = g_strdup_printf ("label.lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
/* Change the CSS data in the provider. */
/* Previous data is thrown away. */
gtk_css_provider_load_from_string (provider, color);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
static void
app_shutdown (GApplication *app, GtkCssProvider *provider) {
gtk_style_context_remove_provider_for_display (gdk_display_get_default(), GTK_STYLE_PROVIDER (provider));
}
static void
app_activate (GApplication *app) {
GtkWindow *win = GTK_WINDOW (gtk_application_window_new (GTK_APPLICATION (app)));
gtk_window_set_title (win, "menu2");
gtk_window_set_default_size (win, 400, 300);
GtkWidget *lb = gtk_label_new (NULL);
gtk_widget_add_css_class (lb, "lb"); /* the class is used by CSS Selector */
gtk_window_set_child (win, lb);
GSimpleAction *act_maximize
= g_simple_action_new_stateful ("maximize", NULL, g_variant_new_boolean (FALSE));
g_signal_connect (act_maximize, "change-state", G_CALLBACK (maximize_state_changed), win);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_maximize));
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
gtk_window_present (win);
}
static void
app_startup (GApplication *app) {
GVariantType *vtype = g_variant_type_new("s");
GSimpleAction *act_color
= g_simple_action_new_stateful ("color", vtype, g_variant_new_string ("red"));
g_variant_type_free (vtype);
GSimpleAction *act_quit
= g_simple_action_new ("quit", NULL);
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), NULL);
g_signal_connect_swapped (act_quit, "activate", G_CALLBACK (g_application_quit), app);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_color));
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
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_maximize = g_menu_item_new ("Maximized", "win.maximize");
GMenuItem *menu_item_red = g_menu_item_new ("Red", "app.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "app.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "app.color::blue");
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (section1, menu_item_maximize);
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_maximize);
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));
g_object_unref (section1);
g_object_unref (section2);
g_object_unref (section3);
g_object_unref (menu);
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
provider = gtk_css_provider_new ();
/* Initialize the css data */
gtk_css_provider_load_from_string (provider, "label.lb {background-color: red;}");
/* Add CSS to the default GdkDisplay. */
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), provider);
g_object_unref (provider); /* release provider, but it's still alive because the display owns it */
}
#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_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
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;
}- 6-23: Action signal handlers.
- 25-28: The handler
app_shutdownis called when the application quits. It removes the provider from the display. - 30-48: An activate signal handler.
- 32-34: A new window is created and assigned to
win. Its title and default size are set to “menu2” and 400x300 respectively. - 36-38: A new label is created and assigned to
lbThe label is given a CSS class “lb”. It is added towinas a child. - 40-43: A toggle action is created and assigned to
act_maximize. It’s connected to the signal handlermaximize_state_changed. It’s added to the window, so the action scope is “win”. - 45: The function
gtk_application_window_set_show_menubaradds a menubar to the window. - 47: Shows the window.
- 50-104: A startup signal handler.
- 52-61: Two actions
act_colorandact_quitare created. There is only one instance of each of these actions, because the startup handler is called once. They are connected to their handlers and added to the application. Their scopes are “app”. - 63-92: Menus are built.
- 94: The menubar is added to the application.
- 96-103: A CSS provider is created with the CSS data and added to the default display. The “shutdown” signal on the application is connected to a handler “app_shutdown”. The provider is removed from the display and freed when the application quits.
Compile
Change your current directory to src/menu.
$ comp menu2
$./a.out
Then, you will see a window and the background color of the content is red. You can change the size to maximum and change back to the original size. You can change the background color to green or blue.
If you run the second application while the first application
is running, another window will appear in the same screen. Both
of the windows have the same background color. Because the
act_color action has “app” scope and the CSS is
applied to the default display shared by the windows.
$ ./a.out & # Run the first application
[1] 82113
$ ./a.out # Run the second application
$