mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-13 20:03:33 +01:00
373 lines
17 KiB
Markdown
373 lines
17 KiB
Markdown
Up: [Readme.md](../Readme.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)
|
|
|
|
# Stateful action
|
|
|
|
Some actions have states.
|
|
The values of states can be boolean or string.
|
|
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
|
|
on_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);
|
|
... ... ...
|
|
}
|
|
|
|
- `act_fullscreen` is GSimpleAction.
|
|
It is generated by `g_simple_action_new_stateful`.
|
|
The function has three arguments.
|
|
The first argument "fullscreen" is the name of the action.
|
|
The second argument is a parameter type.
|
|
`NULL` means 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 function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
|
|
- `menu_item_fullscreen` is GMenuItem.
|
|
There are two arguments.
|
|
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
|
|
The second argument is called detailed action.
|
|
Detailed action has three parts, prefix, action name and target.
|
|
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
|
|
The prefix says that the action belongs to the window.
|
|
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_changed`.
|
|
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
|
|
But no handler is connected to "activate" signal.
|
|
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
|
|
|
|
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);
|
|
}
|
|
|
|
- There are three parameters.
|
|
The first parameter is the action which emits the "change-state" signal.
|
|
The second parameter is the value of the state of the action.
|
|
But it is toggled because of no "activate" signal handler.
|
|
Ther third parameter is a user data which is set in `g_signal_connect`.
|
|
- If the value is boolean type and `TRUE`, then maximize the window.
|
|
Otherwise unmaximize.
|
|
- Set `value` to the state of the action.
|
|
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
|
|
So, you need to set the new value by `g_simple_action_set_state`.
|
|
|
|
You can use "activate" signal instead ot "change-state" signal, or both signals.
|
|
But the way above is the simplest and best.
|
|
|
|
### GVariant
|
|
|
|
GVarient can contain boolean, string or other simple type values.
|
|
For example, the following program set 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 generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
|
|
In the same way, you can get a string from `value2`
|
|
|
|
const gchar *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
|
|
on_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);
|
|
... ... ...
|
|
}
|
|
|
|
- `act_color` is GSimpleAction.
|
|
It is generated by `g_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")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
|
|
The third argument is the initial state of the action.
|
|
It is a GVariant.
|
|
GVariantType will be explained in the next subsection.
|
|
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
|
|
- `menu_item_red` is GMenuItem.
|
|
There are two arguments.
|
|
The first argument "Red" is a label which is one of the attributes of GMenuItem.
|
|
The second argument is a detailed action.
|
|
Its prefix is "win", action name is "color" and target is "red".
|
|
Target is sent to the action as a parameter.
|
|
The same goes for `menu_item_green` and `menu_item_blue`.
|
|
- connect the action `act_color` and the "activate" signal handler `color_activate`.
|
|
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
|
|
No handler is connected to "change-state" signal.
|
|
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
|
|
|
|
The following is the "activate" signal handler.
|
|
|
|
static void
|
|
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
gchar *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);
|
|
}
|
|
|
|
- There are three parameters.
|
|
The first parameter is the action which emits the "activate" signal.
|
|
The second parameter is the parameter given to the action.
|
|
It is a color specified by the menu.
|
|
The third parameter is a user data which is set in `g_signal_connect`.
|
|
- `color` is a CSS string generated by `g_strdup_printf`.
|
|
The parameter of `g_strdup_printf` is the same as printf C standard function.
|
|
`g_variant_get_string` get the string contained in `parameter`.
|
|
- Set the color to the css provider.
|
|
- Free the string `color`.
|
|
- Change the state by `g_action_change_state`.
|
|
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
|
|
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
|
|
|
|
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.
|
|
|
|
When GVariantType is generated, the type is expressed by the string.
|
|
|
|
- "b" means boolean type.
|
|
- "s" means string type.
|
|
|
|
The following program is a simple example.
|
|
It finally output the string "s".
|
|
|
|
1 #include <glib.h>
|
|
2
|
|
3 int
|
|
4 main (int argc, char **argv) {
|
|
5 GVariantType *vtype = g_variant_type_new ("s");
|
|
6 const gchar *type_string = g_variant_type_peek_string (vtype);
|
|
7 g_print ("%s\n",type_string);
|
|
8 }
|
|
|
|
- `g_variant_tpe_new` generates GVariantType.
|
|
It uses a type string "s" which means string.
|
|
- `g_variant_type_peek_string` takes a peek at `vtype`.
|
|
It is the string "s" given at the generation time.
|
|
- print the string to the terminal.
|
|
|
|
## Example code
|
|
The following code includes stateful actions above.
|
|
This program has menus like this:
|
|
|
|
![menu2](../image/menu2.png)
|
|
|
|
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
|
|
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
|
|
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
|
|
The menus have radio buttons on the left of each of the menus.
|
|
And the radio button of the selected menu turns on.
|
|
- Quit menu quits the application.
|
|
|
|
The code is as follows.
|
|
|
|
1 #include <gtk/gtk.h>
|
|
2
|
|
3 static GtkCssProvider *provider;
|
|
4
|
|
5 static void
|
|
6 fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
|
|
7 if (g_variant_get_boolean (value))
|
|
8 gtk_window_maximize (GTK_WINDOW (win));
|
|
9 else
|
|
10 gtk_window_unmaximize (GTK_WINDOW (win));
|
|
11 g_simple_action_set_state (action, value);
|
|
12 }
|
|
13
|
|
14 static void
|
|
15 color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
16 gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
|
|
17 gtk_css_provider_load_from_data (provider, color, -1);
|
|
18 g_free (color);
|
|
19 g_action_change_state (G_ACTION (action), parameter);
|
|
20 }
|
|
21
|
|
22 static void
|
|
23 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
|
|
24 {
|
|
25 g_application_quit (G_APPLICATION(app));
|
|
26 }
|
|
27
|
|
28 static void
|
|
29 on_activate (GApplication *app, gpointer user_data) {
|
|
30 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
|
|
31 gtk_window_set_title (GTK_WINDOW (win), "menu2");
|
|
32 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
|
|
33
|
|
34 GtkWidget *lb = gtk_label_new (NULL);
|
|
35 gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
|
|
36 gtk_window_set_child (GTK_WINDOW (win), lb);
|
|
37
|
|
38 GSimpleAction *act_fullscreen
|
|
39 = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
|
|
40 GSimpleAction *act_color
|
|
41 = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
|
|
42 GSimpleAction *act_quit
|
|
43 = g_simple_action_new ("quit", NULL);
|
|
44
|
|
45 GMenu *menubar = g_menu_new ();
|
|
46 GMenu *menu = g_menu_new ();
|
|
47 GMenu *section1 = g_menu_new ();
|
|
48 GMenu *section2 = g_menu_new ();
|
|
49 GMenu *section3 = g_menu_new ();
|
|
50 GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
|
|
51 GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
|
|
52 GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
|
|
53 GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
|
|
54 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
|
|
55
|
|
56 g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
|
|
57 g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
|
|
58 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
|
|
59 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
|
|
60 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
|
|
61 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
|
|
62
|
|
63 g_menu_append_item (section1, menu_item_fullscreen);
|
|
64 g_menu_append_item (section2, menu_item_red);
|
|
65 g_menu_append_item (section2, menu_item_green);
|
|
66 g_menu_append_item (section2, menu_item_blue);
|
|
67 g_menu_append_item (section3, menu_item_quit);
|
|
68 g_object_unref (menu_item_red);
|
|
69 g_object_unref (menu_item_green);
|
|
70 g_object_unref (menu_item_blue);
|
|
71 g_object_unref (menu_item_fullscreen);
|
|
72 g_object_unref (menu_item_quit);
|
|
73
|
|
74 g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
|
|
75 g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
|
|
76 g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
|
|
77 g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
|
|
78
|
|
79 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
|
|
80 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
|
|
81
|
|
82 /* GtkCssProvider *provider = gtk_css_provider_new ();*/
|
|
83 provider = gtk_css_provider_new ();
|
|
84 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
|
|
85 gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
|
|
86 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
|
|
87 GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
88
|
|
89 /* gtk_widget_show (win);*/
|
|
90 gtk_window_present (GTK_WINDOW (win));
|
|
91 }
|
|
92
|
|
93 int
|
|
94 main (int argc, char **argv) {
|
|
95 GtkApplication *app;
|
|
96 int stat;
|
|
97
|
|
98 app = gtk_application_new ("com.github.ToshioCP.menu2", G_APPLICATION_FLAGS_NONE);
|
|
99 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
|
100
|
|
101 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
102 g_object_unref (app);
|
|
103 return stat;
|
|
104 }
|
|
105
|
|
|
|
- 5-26: Signal handlers.
|
|
They have been explained in this section.
|
|
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
|
|
`win` has a title "menu2" and its defaust size is 400x300.
|
|
`lb` is named as "lb".
|
|
The name is used in CSS.
|
|
`lb` is set to `win` as a child.
|
|
- 38-43: Three actions are defined.
|
|
They are:
|
|
- stateful and has no parameter.
|
|
It has a toggle state.
|
|
- stateful and has a parameter.
|
|
Parameter is a string type.
|
|
- stateless and has no parameter.
|
|
- 45-54: Generate GMenu and GMenuItem.
|
|
There are three sections.
|
|
- 56-61: Signals are connected to handlers.
|
|
And actions are added to GActionMap.
|
|
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
|
|
they are added to `win`.
|
|
GtkApplicationWindow implements GActionModel interface like GtkApplication.
|
|
`act_quit` has "app" prefix and belongs to GtkApplication.
|
|
It is added to `app`.
|
|
- 63-77: Connect and build the menus.
|
|
Useless GMenuItem are freed.
|
|
- 79-80: GMenuModel `menubar` is set to `app`.
|
|
Set show menubar property to `TRUE` in `win`.
|
|
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
|
|
This is a different point between Gtk3 and Gtk4.
|
|
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
|
|
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
|
|
- 82-87: Set CSS.
|
|
`provider` is GtkCssProvider which is defined in line three as a static variable.
|
|
Its CSS data is:
|
|
`label#lb {background-color: red;}`.
|
|
"label#lb" is called selector.
|
|
"label" is the node of GtkLabel.
|
|
"#" precedes an ID which is an identifiable name of the widget.
|
|
"lb" is the name of GtkLabel `lb`.
|
|
(See line 35).
|
|
The style is surrounded by open and close braces.
|
|
The style is applied to GtkLabel which has a name "lb".
|
|
Other GtkLabel have no effect from this.
|
|
The provider is added to GdkDisplay.
|
|
- 90: Show the window.
|
|
|
|
|
|
Up: [Readme.md](../Readme.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)
|