From c84cffd8efa1c0a771de04134276bc0db7549ab1 Mon Sep 17 00:00:00 2001 From: Toshio Sekiya Date: Wed, 18 Jan 2023 14:37:14 +0900 Subject: [PATCH] Update gfm files of the section 16-30. --- gfm/sec26.md | 198 ++++----- gfm/sec27.md | 174 ++++---- gfm/sec28.md | 1125 ++++++++++++++++++++++++++++---------------------- gfm/sec29.md | 321 +++++++------- gfm/sec3.md | 18 + 5 files changed, 961 insertions(+), 875 deletions(-) diff --git a/gfm/sec26.md b/gfm/sec26.md index 0254c2b..900e7e6 100644 --- a/gfm/sec26.md +++ b/gfm/sec26.md @@ -3,29 +3,29 @@ Up: [README.md](../README.md), Prev: [Section 25](sec25.md), Next: [Section 27] # GtkListView GTK 4 has added new list objects GtkListView, GtkGridView and GtkColumnView. -The new feature is described in [Gtk API Reference, List Widget Overview](https://docs.gtk.org/gtk4/section-list-widget.html). +The new feature is described in [Gtk API Reference -- List Widget Overview](https://docs.gtk.org/gtk4/section-list-widget.html). GTK 4 has other means to implement lists. They are GtkListBox and GtkTreeView which are took over from GTK 3. There's an article in [Gtk Development blog](https://blog.gtk.org/2020/06/07/scalable-lists-in-gtk-4/) about list widgets by Matthias Clasen. He described why GtkListView are developed to replace GtkListBox and GtkTreeView. -I want to explain GtkListView and its related objects in this tutorial. +GtkListView, GtkGridView, GtkColumnView and related objects are described in Section 26 to 29. ## Outline A list is a sequential data structure. For example, an ordered string sequence "one", "two", "three", "four" is a list. -Each element of the list is called item. -A list is like an array, but in many cases it is implemented with pointers which point to the next item of the list. +Each element is called item. +A list is like an array, but in many cases it is implemented with pointers which point to the next items of the list. And it has a start point. So, each item can be referred by the index of the item (first item, second item, ..., nth item, ...). There are two cases. -One is the index starts from one (one-based) and the other is it starts from zero (zero-based). +One is the index starts from one (one-based) and the other is from zero (zero-based). Gio provides GListModel interface. -It is a zero-based list of the same type of GObject objects, or objects that implement the same interface. -An object implements GListModel is usually not a widget. +It is a zero-based list of the same type of GObject descendants, or objects that implement the same interface. +An object implements GListModel is not a widget. So, the list is not displayed on the screen directly. There's another object GtkListView which is a widget to display the list. The items in the list need to be connected to the items in GtkListView. @@ -33,16 +33,11 @@ GtkListItemFactory object maps items in the list to GListView. ![List](../image/list.png) -The instruction to build the whole list related objects is: - -1. Implement the list object which implements GListModel. -2. Build widgets and put GtkListView as a child of GtkScrolledWindow. -3. Set GtkListItemFactory. - ## GListModel If you want to make a list of strings with GListModel, for example, "one", "two", "three", "four", note that strings can't be items of the list. Because GListModel is a list of GObject objects and strings aren't GObject objects. +The word "GObject" here means "GObject class or its descendant class". So, you need a wrapper which is a GObject and contains a string. GtkStringObject is the wrapper object and GStringList, implements GListModel, is a list of GtkStringObject. @@ -51,7 +46,7 @@ char *array[] = {"one", "two", "three", "four", NULL}; GtkStringList *stringlist = gtk_string_list_new ((const char * const *) array); ~~~ -The function `gtk_string_list_new` creates GtkStringList object. +The function `gtk_string_list_new` creates a GtkStringList object. Its items are GtkStringObject objects which contain the strings "one", "two", "three" and "four". There are functions to add items to the list or remove items from the list. @@ -59,9 +54,9 @@ There are functions to add items to the list or remove items from the list. - `gtk_string_list_remove` removes an item from the list - `gtk_string_list_get_string` gets a string in the list -See [GTK 4 API Reference, GtkStringList](https://docs.gtk.org/gtk4/class.StringList.html) for further information. +See [GTK 4 API Reference -- GtkStringList](https://docs.gtk.org/gtk4/class.StringList.html) for further information. -I'll explain the other list objects later. +Other list objects will be explained later. ## GtkSelectionModel @@ -101,7 +96,7 @@ There are four signals. 1. "setup" is emitted to set up GtkListItem object. A user sets its child widget in the handler. -For example, creates a GtkLabel widget and sets the child property of GtkListItem to it. +For example, creates a GtkLabel widget and sets the child property of GtkListItem with it. This setting is kept even the GtkListItem instance is recycled (to bind to another item of GListModel). 2. "bind" is emitted to bind an item in the list model to the widget. For example, a user gets the item from "item" property of the GtkListItem instance. @@ -121,7 +116,7 @@ GtkNoSelection is used, so user can't select any item. 1 #include 2 3 static void - 4 setup_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { + 4 setup_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { 5 GtkWidget *lb = gtk_label_new (NULL); 6 gtk_list_item_set_child (listitem, lb); 7 } @@ -129,40 +124,40 @@ GtkNoSelection is used, so user can't select any item. 9 static void 10 bind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { 11 GtkWidget *lb = gtk_list_item_get_child (listitem); -12 GtkStringObject *strobj = gtk_list_item_get_item (listitem); -13 const char *text = gtk_string_object_get_string (strobj); -14 -15 gtk_label_set_text (GTK_LABEL (lb), text); -16 } -17 -18 static void -19 unbind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { -20 /* There's nothing to do here. */ -21 /* If you does something like setting a signal in bind_cb, */ -22 /* then disconnecting the signal is necessary in unbind_cb. */ -23 } -24 -25 static void -26 teardown_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { -27 gtk_list_item_set_child (listitem, NULL); -28 /* When the child of listitem is set to NULL, the reference to GtkLabel will be released and lb will be destroyed. */ -29 /* Therefore, g_object_unref () for the GtkLabel object doesn't need in the user code. */ -30 } -31 -32 /* ----- activate, open, startup handlers ----- */ -33 static void -34 app_activate (GApplication *application) { -35 GtkApplication *app = GTK_APPLICATION (application); -36 GtkWidget *win = gtk_application_window_new (app); -37 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400); -38 GtkWidget *scr = gtk_scrolled_window_new (); -39 gtk_window_set_child (GTK_WINDOW (win), scr); -40 -41 char *array[] = { -42 "one", "two", "three", "four", NULL -43 }; -44 GtkStringList *sl = gtk_string_list_new ((const char * const *) array); -45 GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (sl)); +12 /* Strobj is owned by the instance. Caller mustn't change or destroy it. */ +13 GtkStringObject *strobj = gtk_list_item_get_item (listitem); +14 const char *text = gtk_string_object_get_string (strobj); +15 +16 gtk_label_set_text (GTK_LABEL (lb), text); +17 } +18 +19 static void +20 unbind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { +21 /* There's nothing to do here. */ +22 } +23 +24 static void +25 teardown_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) { +26 gtk_list_item_set_child (listitem, NULL); +27 /* The previous child is destroyed automatically. */ +28 } +29 +30 static void +31 app_activate (GApplication *application) { +32 GtkApplication *app = GTK_APPLICATION (application); +33 GtkWidget *win = gtk_application_window_new (app); +34 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400); +35 GtkWidget *scr = gtk_scrolled_window_new (); +36 gtk_window_set_child (GTK_WINDOW (win), scr); +37 +38 char *array[] = { +39 "one", "two", "three", "four", NULL +40 }; +41 /* sl is owned by ns */ +42 /* ns and factory are owned by lv. */ +43 /* Therefore, you don't need to care about their destruction. */ +44 GtkStringList *sl = gtk_string_list_new ((const char * const *) array); +45 GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (sl)); 46 47 GtkListItemFactory *factory = gtk_signal_list_item_factory_new (); 48 g_signal_connect (factory, "setup", G_CALLBACK (setup_cb), NULL); @@ -175,34 +170,26 @@ GtkNoSelection is used, so user can't select any item. 55 gtk_window_present (GTK_WINDOW (win)); 56 } 57 -58 static void -59 app_startup (GApplication *application) { -60 } -61 -62 /* ----- main ----- */ -63 #define APPLICATION_ID "com.github.ToshioCP.list1" -64 -65 int -66 main (int argc, char **argv) { -67 GtkApplication *app; -68 int stat; +58 /* ----- main ----- */ +59 #define APPLICATION_ID "com.github.ToshioCP.list1" +60 +61 int +62 main (int argc, char **argv) { +63 GtkApplication *app; +64 int stat; +65 +66 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +67 +68 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); 69 -70 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); -71 -72 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); -73 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); -74 -75 stat =g_application_run (G_APPLICATION (app), argc, argv); -76 g_object_unref (app); -77 return stat; -78 } -79 +70 stat =g_application_run (G_APPLICATION (app), argc, argv); +71 g_object_unref (app); +72 return stat; +73 } ~~~ The file `list1.c` is located under the directory [src/misc](../src/misc). -Make a shell script below and save it to your bin directory. -(If you've installed GTK 4 from the source to $HOME/local, then your bin directory is $Home/local/bin. -Otherwise, $Home/bin is your private bin directory.) +Make a shell script below and save it to your bin directory, for example `$HOME/bin`. ~~~Shell gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4` @@ -211,16 +198,16 @@ gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4` Change the current directory to the directory includes `list1.c` and type as follows. ~~~ -$ chmod 755 $HOME/local/bin/comp # or chmod 755 $Home/bin/comp +$ chmod 755 $HOME/bin/comp # or chmod 755 (your bin directory)/comp $ comp list1 $ ./a.out ~~~ -Then, `list1.c` has been compiled and executed. +Then, the following window appears. ![list1](../image/list1.png) -I think the program is not so difficult. +The program is not so difficult. If you feel some difficulty, read this section again, especially GtkSignalListItemFactory subsubsection. ### GtkBuilderListItemFactory @@ -311,28 +298,23 @@ Its name is `list2.c` and located under [src/misc](../src/misc) directory. 38 gtk_window_present (GTK_WINDOW (win)); 39 } 40 -41 static void -42 app_startup (GApplication *application) { -43 } -44 -45 /* ----- main ----- */ -46 #define APPLICATION_ID "com.github.ToshioCP.list2" -47 -48 int -49 main (int argc, char **argv) { -50 GtkApplication *app; -51 int stat; +41 /* ----- main ----- */ +42 #define APPLICATION_ID "com.github.ToshioCP.list2" +43 +44 int +45 main (int argc, char **argv) { +46 GtkApplication *app; +47 int stat; +48 +49 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +50 +51 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); 52 -53 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); -54 -55 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); -56 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +53 stat =g_application_run (G_APPLICATION (app), argc, argv); +54 g_object_unref (app); +55 return stat; +56 } 57 -58 stat =g_application_run (G_APPLICATION (app), argc, argv); -59 g_object_unref (app); -60 return stat; -61 } -62 ~~~ No signal handler is needed for GtkBulderListItemFactory. @@ -382,7 +364,7 @@ Instead, closure tag is appropriate in this case. Closure tag specifies a function and the type of the return value of the function. ~~~C -char * +const char * get_file_name (GtkListItem *item, GFileInfo *info) { if (! G_IS_FILE_INFO (info)) return NULL; @@ -408,23 +390,26 @@ get_file_name (GtkListItem *item, GFileInfo *info) { "" ~~~ -- "gchararray" is the type name of strings. -"gchar" is the same as "char" type. +- The string "gchararray" is the type name of strings. +The type "gchar" is the same as "char". Therefore, "gchararray" is "an array of char type", which is the same as string type. It is used to get the type of GValue object. GValue is a generic value and it can contain various type of values. For example, the type name can be gboolean, gchar (char), gint (int), gfloat (float), gdouble (double), gchararray (char *) and so on. These type names are the names of the fundamental types that are registered to the type system. See [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial/blob/main/gfm/sec5.md#gvalue). -- closure tag has type attribute and function attribute. +- Closure tag has type attribute and function attribute. Function attribute specifies the function name and type attribute specifies the type of the return value of the function. The contents of closure tag (it is between \ and\) is parameters of the function. `GtkListItem` gives the value of the item property of the GtkListItem. This will be the second argument of the function. The first parameter is always the GListItem instance. -- `gtk_file_name` function first check the `info` parameter. -Because it can be NULL when GListItem `item` is unbound. -If its GFileInfo, then return the filename (copy of the filename). +- `gtk_file_name` function first checks the `info` parameter. +Because it can be NULL when GListItem `item` is unbounded. +If it's GFileInfo, it returns the filename. +The filename is owned by GFileInfo object. +So, the function `get_file_name` duplicates the string to own the newly created one. +Closure tag binds the property of the outer tag (GtkLabel) to the filename. The whole program (`list3.c`) is as follows. The program is located in [src/misc](../src/misc) directory. @@ -498,7 +483,6 @@ The program is located in [src/misc](../src/misc) directory. 66 g_object_unref (app); 67 return stat; 68 } -69 ~~~ The ui data (xml data above) is used to build the GListItem template at runtime. diff --git a/gfm/sec27.md b/gfm/sec27.md index cde8294..41daf07 100644 --- a/gfm/sec27.md +++ b/gfm/sec27.md @@ -7,7 +7,7 @@ It displays a GListModel as a grid, which is like a square tessellation. ![Grid](../image/list4.png) -This is often seen when you use a file browser like nautilus. +This is often seen when you use a file browser like GNOME Files (Nautilus). In this section, let's make a very simple file browser `list4`. It just shows the files in the current directory. @@ -32,9 +32,7 @@ GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirect ![DirectoryList](../image/directorylist.png) -The following is the part of the ui file `list4.ui`. -It defines GtkListView, GtkSingleSelection and GtkDirectoryList. -It also defines GtkGridView and GtkSingleSelection. +The following is a part of the ui file `list4.ui`. ~~~xml @@ -59,20 +57,19 @@ It is attributes of GFileInfo such as "standard::name", "standard::icon" and "st - standard::name is a filename. - standard::icon is an icon of the file. It is a GIcon object. - standard::content-type is a content-type. -Content-type is the same as mime type for the internet technology. +Content-type is the same as mime type for the internet. For example, "text/plain" is a text file, "text/x-csrc" is a C source code and so on. ("text/x-csrc"is not registered to IANA media types. Such "x-" subtype is not a standard mime type.) -Content type is also used by the desktop system. +Content type is also used by desktop systems. -GtkGridView has the same structure as GtkListView. -But it is enough to specify its model property to `singleselection` which is the identification of the GtkSingleSelection. -Therefore the description for GtkGridView is very short. +GtkGridView uses the same GtkSingleSelection instance (`singleselection`). +So, its model property is set with it. ## Ui file of the window -Look at the screenshot of `list4` at the top of this section. -The widgets are built with the following ui file. +The window is built with the following ui file. +(See the screenshot at the beginning of this section). ~~~xml 1 @@ -136,7 +133,7 @@ The widgets are built with the following ui file. 59 60 61 -62 +62 63 standard::name,standard::icon,standard::content-type 64 65 @@ -151,25 +148,24 @@ The widgets are built with the following ui file. ~~~ The file consists of two parts. -The first part begins at the third line and ends at the 57th line. +The first part begins at the line 3 and ends at line 57. This part is the widgets from the top level window to the scrolled window. It also includes two buttons. -The second part begins at the 58th line and ends at the 71st line. +The second part begins at line 58 and ends at line 71. This is the part of GtkListView and GtkGridView. -They are described in the previous section. - 13-17, 42-46: Two labels are dummy labels. They just work as a space to put the two buttons at the appropriate position. -- 19-41: GtkButton `btnlist` and `btngrid`. +- 18-41: GtkButton `btnlist` and `btngrid`. These two buttons work as selection buttons to switch from list to grid and vice versa. These two buttons are connected to a stateful action `win.view`. -This action is stateful and has a parameter. +This action has a parameter. Such action consists of prefix, action name and parameter. The prefix of the action is `win`, which means the action belongs to the top level window. -So, a prefix gives the scope of the action. +The prefix gives the scope of the action. The action name is `view`. The parameters are `list` or `grid`, which show the state of the action. -A parameter is also called a target, because it is a target to which the buttons are clicked on to change the action state. +A parameter is also called a target, because it is a target to which the action changes its state. We often write the detailed action like "win.view::list" or "win.view::grid". - 21-22: The properties "action-name" and "action-target" belong to GtkActionable interface. GtkButton implements GtkActionable. @@ -186,14 +182,14 @@ GtkImage has a "resource" property. It is a GResource and GtkImage reads an image data from the resource and sets the image. This resource is built from 24x24-sized png image data, which is an original icon. - 50-53: GtkScrolledWindow. -Its child widget will be GtkListView or GtkGridView. +Its child widget will be set with GtkListView or GtkGridView. The action `view` is created, connected to the "activate" signal handler and inserted to the window (action map) as follows. ~~~C - act_view = g_simple_action_new_stateful ("view", g_variant_type_new("s"), g_variant_new_string ("list")); - g_signal_connect (act_view, "activate", G_CALLBACK (view_activated), scr); /* scr is the GtkScrolledWindow object */ - g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view)); +act_view = g_simple_action_new_stateful ("view", g_variant_type_new("s"), g_variant_new_string ("list")); +g_signal_connect (act_view, "activate", G_CALLBACK (view_activated), NULL); +g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view)); ~~~ The signal handler `view_activated` will be explained later. @@ -300,10 +296,10 @@ $ cd list4; diff factory_list.ui factory_grid.ui > 0.5 ~~~ -Each view item has two properties, "gicon" property of GtkImage and "label" property of GtkLabel. +Two properties "gicon" (property of GtkImage) and "label" (property of GtkLabel) are in the ui files above. Because GFileInfo doesn't have properties correspond to icon or filename, the factory uses closure tag to bind "gicon" and "label" properties to GFileInfo information. -A function `get_icon` gets GIcon the GFileInfo object has. -And a function `get_file_name` gets a filename the GFileInfo object has. +A function `get_icon` gets GIcon from the GFileInfo object. +And a function `get_file_name` gets a filename from the GFileInfo object. ~~~C 1 GIcon * @@ -328,11 +324,12 @@ And a function `get_file_name` gets a filename the GFileInfo object has. 20 } ~~~ -One important thing is view items own the instance or string. -It is achieved by `g_object_ref` to increase the reference count by one, or `strdup` to create a copy of the string. -The object or string will be automatically freed in unbinding process when the view item is recycled. +One important thing is the ownership of the return values. +When GtkExpression (closure tag creates a GtkCClosureExpression -- a child class of GtkExpression) is evaluated, +the value is owned by the caller. +So, `g_obect_ref` or `g_strdup` is necessary. -## An activate signal handler of the action +## An activate signal handler of the button action An activate signal handler `view_activate` switches the view. It does two things. @@ -342,24 +339,23 @@ It does two things. ~~~C 1 static void - 2 view_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) { - 3 GtkScrolledWindow *scr = GTK_SCROLLED_WINDOW (user_data); - 4 const char *view = g_variant_get_string (parameter, NULL); - 5 const char *other; - 6 char *css; - 7 - 8 if (strcmp (view, "list") == 0) { - 9 other = "grid"; -10 gtk_scrolled_window_set_child (scr, list); -11 }else { -12 other = "list"; -13 gtk_scrolled_window_set_child (scr, grid); -14 } -15 css = g_strdup_printf ("button#btn%s {background: silver;} button#btn%s {background: white;}", view, other); -16 gtk_css_provider_load_from_data (provider, css, -1); -17 g_free (css); -18 g_action_change_state (G_ACTION (action), parameter); -19 } + 2 view_activated(GSimpleAction *action, GVariant *parameter) { + 3 const char *view = g_variant_get_string (parameter, NULL); + 4 const char *other; + 5 char *css; + 6 + 7 if (strcmp (view, "list") == 0) { + 8 other = "grid"; + 9 gtk_scrolled_window_set_child (scr, GTK_WIDGET (list)); +10 }else { +11 other = "list"; +12 gtk_scrolled_window_set_child (scr, GTK_WIDGET (grid)); +13 } +14 css = g_strdup_printf ("button#btn%s {background: silver;} button#btn%s {background: white;}", view, other); +15 gtk_css_provider_load_from_data (provider, css, -1); +16 g_free (css); +17 g_action_change_state (G_ACTION (action), parameter); +18 } ~~~ The second parameter of this handler is the target of the clicked button. @@ -368,17 +364,17 @@ Its type is GVariant. - If `btnlist` has been clicked, then `parameter` is a GVariant of the string "list". - If `btngrid` has been clicked, then `parameter` is a GVariant of the string "grid". -The third parameter `user_data` points GtkScrolledWindow, which is set in the `g_signal_connect` function. +The third parameter `user_data` points NULL and it is ignored here. -- 4: `g_variant_get_string` gets the string from the GVariant variable. -- 8-14: Sets the child of `scr`. +- 3: `g_variant_get_string` gets the string from the GVariant variable. +- 7-13: Sets the child of `scr`. The function `gtk_scrolled_window_set_child` decreases the reference count of the old child by one. And it increases the reference count of the new child by one. -- 15-17: Sets the CSS of the buttons. +- 14-16: Sets the CSS for the buttons. The background of the clicked button will be silver color and the other button will be white. -- 18: Changes the state of the action. +- 17: Changes the state of the action. -## Activate signal of GtkListView and GtkGridView +## Activate signal on GtkListView and GtkGridView Views (GtkListView and GtkGridView) have an "activate" signal. It is emitted when an item in the view is double clicked or the enter key is pressed. @@ -406,10 +402,10 @@ grid_activate (GtkGridView *grid, int position, gpointer user_data) { g_signal_connect (GTK_GRID_VIEW (grid), "activate", G_CALLBACK (grid_activate), NULL); ~~~ -The second parameter of the handlers is the position of the item (GFileInfo) of the GListModel. +The second parameter of each handler is the position of the item (GFileInfo) of the GListModel. So you can get the item with `g_list_model_get_item` function. -## Content type and launching an application +### Content type and application launch The function `launch_tfe_with_file` gets a file from the GFileInfo instance. If the file is a text file, it launches `tfe` with the file. @@ -433,52 +429,54 @@ Content type can be got with `g_file_info_get_content_type` function. 11 if (! info) 12 return; 13 content_type = g_file_info_get_content_type (info); -14 g_print ("%s\n", content_type); /* This line can be commented out if unnecessary */ -15 if (! content_type) -16 return; -17 for (i=0;i<5;++i) { -18 if (content_type[i] != text_type[i]) -19 return; -20 } -21 appinfo = g_app_info_create_from_commandline ("tfe", "tfe", G_APP_INFO_CREATE_NONE, &err); -22 if (err) { -23 g_printerr ("%s\n", err->message); -24 g_error_free (err); -25 return; -26 } -27 err = NULL; -28 file = g_file_new_for_path (g_file_info_get_name (info)); -29 files = g_list_append (files, file); -30 if (! (g_app_info_launch (appinfo, files, NULL, &err))) { -31 g_printerr ("%s\n", err->message); -32 g_error_free (err); -33 } -34 g_list_free_full (files, g_object_unref); -35 g_object_unref (appinfo); -36 } +14 #ifdef debug +15 g_print ("%s\n", content_type); +16 #endif +17 if (! content_type) +18 return; +19 for (i=0;i<5;++i) { /* compare the first 5 characters */ +20 if (content_type[i] != text_type[i]) +21 return; +22 } +23 appinfo = g_app_info_create_from_commandline ("tfe", "tfe", G_APP_INFO_CREATE_NONE, &err); +24 if (err) { +25 g_printerr ("%s\n", err->message); +26 g_error_free (err); +27 return; +28 } +29 err = NULL; +30 file = g_file_new_for_path (g_file_info_get_name (info)); +31 files = g_list_append (files, file); +32 if (! (g_app_info_launch (appinfo, files, NULL, &err))) { +33 g_printerr ("%s\n", err->message); +34 g_error_free (err); +35 } +36 g_list_free_full (files, g_object_unref); +37 g_object_unref (appinfo); +38 } ~~~ - 13: Gets the content type of the file from GFileInfo. -- 14: Prints the content type. +- 14-16: Prints the content type if "debug" is defined. This is only useful to know a content type of a file. -You can delete it if unnecessary. -- 17-20: If the content type doesn't begin with "text/", then it returns. -- 21: Creates GAppInfo object of `tfe` application. +If you don't want this, delete or uncomment the definition `#define debug 1` iat line 6 in the source file. +- 17-22: If no content type or the content type doesn't begin with "text/",the function returns. +- 23: Creates GAppInfo object of `tfe` application. GAppInfo is an interface and the variable `appinfo` points a GDesktopAppInfo instance. -GAppInfo is a collection of information of an application. -- 30: Launches the application (`tfe`) with an argument `file`. +GAppInfo is a collection of information of applications. +- 32: Launches the application (`tfe`) with an argument `file`. `g_app_info_launch` has four parameters. The first parameter is GAppInfo object. The second parameter is a list of GFile objects. In this function, only one GFile instance is given to `tfe`, but you can give more arguments. The third parameter is GAppLaunchContext, but this program gives NULL instead. The last parameter is the pointer to the pointer to a GError. -- 34: `g_list_free_full` frees the memories used by the list and items. +- 36: `g_list_free_full` frees the memories used by the list and items. If your distribution supports GTK 4, using `g_app_info_launch_default_for_uri` is convenient. The function automatically determines the default application from the file and launches it. For example, if the file is text, then it launches gedit with the file. -Such functionality comes from desktop. +Such feature comes from desktop. ## Compilation and execution @@ -511,7 +509,7 @@ The following shows a part of the new ui file (`list5.ui`). - + standard::name,standard::icon,standard::content-type diff --git a/gfm/sec28.md b/gfm/sec28.md index bde1b0b..610554d 100644 --- a/gfm/sec28.md +++ b/gfm/sec28.md @@ -1,4 +1,4 @@ -Up: [Readme.md](../Readme.md), Prev: [Section 27](sec27.md), Next: [Section 29](sec29.md) +Up: [README.md](../README.md), Prev: [Section 27](sec27.md), Next: [Section 29](sec29.md) # GtkExpression @@ -22,211 +22,93 @@ GtkExpression is a way to get a value. Evaluation is like a calculation. A value is got by evaluating the expression. -First, I want to show you the C file of the example for GtkExpression. -Its name is `exp.c` and located under [src/expression](../src/expression) directory. -You don't need to understand the details now, just look at it. -It will be explained in the next subsection. - -~~~C - 1 #include - 2 - 3 GtkWidget *win1; - 4 int width, height; - 5 GtkExpressionWatch *watch_width; - 6 GtkExpressionWatch *watch_height; - 7 - 8 /* Notify is called when "default-width" or "default-height" property is changed. */ - 9 static void - 10 notify (gpointer user_data) { - 11 GValue value = G_VALUE_INIT; - 12 char *title; - 13 - 14 if (watch_width && gtk_expression_watch_evaluate (watch_width, &value)) - 15 width = g_value_get_int (&value); - 16 g_value_unset (&value); - 17 if (watch_height && gtk_expression_watch_evaluate (watch_height, &value)) - 18 height = g_value_get_int (&value); - 19 g_value_unset (&value); - 20 title = g_strdup_printf ("%d x %d", width, height); - 21 gtk_window_set_title (GTK_WINDOW (win1), title); - 22 g_free (title); - 23 } - 24 - 25 /* This function is used by closure tag in exp.ui. */ - 26 char * - 27 set_title (GtkWidget *win, int width, int height) { - 28 return g_strdup_printf ("%d x %d", width, height); - 29 } - 30 - 31 /* ----- activate, open, startup handlers ----- */ - 32 static void - 33 app_activate (GApplication *application) { - 34 GtkApplication *app = GTK_APPLICATION (application); - 35 GtkWidget *box; - 36 GtkWidget *label1, *label2, *label3; - 37 GtkWidget *entry; - 38 GtkEntryBuffer *buffer; - 39 GtkBuilder *build; - 40 GtkExpression *expression, *expression1, *expression2; - 41 GValue value = G_VALUE_INIT; - 42 char *s; - 43 - 44 /* Creates GtkApplicationWindow instance. */ - 45 /* The codes below are complecated. It does the same as "win1 = gtk_application_window_new (app);". */ - 46 /* The codes are written just to show how to use GtkExpression. */ - 47 expression = gtk_cclosure_expression_new (GTK_TYPE_APPLICATION_WINDOW, NULL, 0, NULL, - 48 G_CALLBACK (gtk_application_window_new), NULL, NULL); - 49 if (gtk_expression_evaluate (expression, app, &value)) { - 50 win1 = GTK_WIDGET (g_value_get_object (&value)); /* GtkApplicationWindow */ - 51 g_object_ref (win1); - 52 g_print ("Got GtkApplicationWindow instance.\n"); - 53 }else - 54 g_print ("The cclosure expression wasn't evaluated correctly.\n"); - 55 gtk_expression_unref (expression); - 56 g_value_unset (&value); /* At the same time, the reference count of win1 is decreased by one. */ - 57 - 58 /* Builds a window with components */ - 59 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); - 60 label1 = gtk_label_new (NULL); - 61 label2 = gtk_label_new (NULL); - 62 label3 = gtk_label_new (NULL); - 63 buffer = gtk_entry_buffer_new (NULL, 0); - 64 entry = gtk_entry_new_with_buffer (buffer); - 65 gtk_box_append (GTK_BOX (box), label1); - 66 gtk_box_append (GTK_BOX (box), label2); - 67 gtk_box_append (GTK_BOX (box), label3); - 68 gtk_box_append (GTK_BOX (box), entry); - 69 gtk_window_set_child (GTK_WINDOW (win1), box); - 70 - 71 /* Constant expression */ - 72 expression = gtk_constant_expression_new (G_TYPE_INT,100); - 73 if (gtk_expression_evaluate (expression, NULL, &value)) { - 74 s = g_strdup_printf ("%d", g_value_get_int (&value)); - 75 gtk_label_set_text (GTK_LABEL (label1), s); - 76 g_free (s); - 77 } else - 78 g_print ("The constant expression wasn't evaluated correctly.\n"); - 79 gtk_expression_unref (expression); - 80 g_value_unset (&value); - 81 - 82 /* Property expression and binding*/ - 83 expression1 = gtk_property_expression_new (GTK_TYPE_ENTRY, NULL, "buffer"); - 84 expression2 = gtk_property_expression_new (GTK_TYPE_ENTRY_BUFFER, expression1, "text"); - 85 gtk_expression_bind (expression2, label2, "label", entry); - 86 - 87 /* Constant expression instead of "this" instance */ - 88 expression1 = gtk_constant_expression_new (GTK_TYPE_APPLICATION, app); - 89 expression2 = gtk_property_expression_new (GTK_TYPE_APPLICATION, expression1, "application-id"); - 90 if (gtk_expression_evaluate (expression2, NULL, &value)) - 91 gtk_label_set_text (GTK_LABEL (label3), g_value_get_string (&value)); - 92 else - 93 g_print ("The property expression wasn't evaluated correctly.\n"); - 94 gtk_expression_unref (expression1); /* expression 2 is also freed. */ - 95 g_value_unset (&value); - 96 - 97 width = 800; - 98 height = 600; - 99 gtk_window_set_default_size (GTK_WINDOW (win1), width, height); -100 notify(NULL); -101 -102 /* GtkExpressionWatch */ -103 expression1 = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-width"); -104 watch_width = gtk_expression_watch (expression1, win1, notify, NULL, NULL); -105 expression2 = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-height"); -106 watch_height = gtk_expression_watch (expression2, win1, notify, NULL, NULL); -107 -108 gtk_widget_show (win1); -109 -110 /* Builds a window with exp.ui resource */ -111 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp.ui"); -112 GtkWidget *win2 = GTK_WIDGET (gtk_builder_get_object (build, "win2")); -113 gtk_window_set_application (GTK_WINDOW (win2), app); -114 g_object_unref (build); -115 -116 gtk_widget_show (win2); -117 } -118 -119 static void -120 app_startup (GApplication *application) { -121 } -122 -123 #define APPLICATION_ID "com.github.ToshioCP.exp" -124 -125 int -126 main (int argc, char **argv) { -127 GtkApplication *app; -128 int stat; -129 -130 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE); -131 -132 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); -133 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); -134 /* g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);*/ -135 -136 stat =g_application_run (G_APPLICATION (app), argc, argv); -137 g_object_unref (app); -138 return stat; -139 } -140 -~~~ - -`exp.c` consists of five functions. - -- `notify` -- `set_title` -- `app_activate`. This is a handler of "activate" signal on GtkApplication instance. -- `app_startup`. This is a handler of "startup"signal. But nothing is done in this function. -- `main`. - -The function `app_activate` is an actual main body in `exp.c`. - ## Constant expression -Constant expression provides constant value or instance when it is evaluated. - -- 72-80: A constant expression. -It is extracted and put into here. +A constant expression (GtkConstantExpression) provides constant value or instance when it is evaluated. ~~~C + GValue value = G_VALUE_INIT; expression = gtk_constant_expression_new (G_TYPE_INT,100); - if (gtk_expression_evaluate (expression, NULL, &value)) { - s = g_strdup_printf ("%d", g_value_get_int (&value)); - gtk_label_set_text (GTK_LABEL (label1), s); - g_free (s); - } else - g_print ("The constant expression wasn't evaluated correctly.\n"); - gtk_expression_unref (expression); - g_value_unset (&value); + gtk_expression_evaluate (expression, NULL, &value); ~~~ +- GtkExpression uses GValue to hold a value. +GValue is a structure and container to hold a type and value. +It must be initialized with `G_VALUE_INIT`, first. +Be careful that `value` is a structure, not a pointer to a structure. - Constant expression is created with `gtk_constant_expression_new` function. The parameter of the function is a type (GType) and a value (or instance). +This expression holds a constant value. +`G_TYPE_INT` is a type that is registered to the type system. +It is integer type. +Some types are shown in the following table. - `gtk_expression_evaluate` evaluates the expression. -It has three parameters, the expression to evaluate, `this` instance and GValue for being set with the value. +It has three parameters, the expression to evaluate, `this` instance and a pointer to a GValue for being set with the value. `this` instance isn't necessary for constant expressions. -Therefore the second argument is NULL. +Therefore, the second argument is NULL. `gtk_expression_evaluate` returns TRUE if it successfully evaluates the expression. Otherwise it returns FALSE. - If it returns TRUE, the GValue `value` is set with the value of the expression. The type of the value is int. -`g_strdup_printf` converts the value to a string `s`. -- GtkLabel `label1` is set with `s`. -The string `s` needs to be freed. -- If the evaluation fails a message is outputted to stderr. -- The expression and GValue are freed. + +|GType |C type|type name |notes | +|:----------------|:-----|:---------|:----------------------| +|G\_TYPE\_CHAR |char |gchar | | +|G\_TYPE\_BOOLEAN |int |gboolean | | +|G\_TYPE\_INT |int |gint | | +|G\_TYPE\_FLOAT |float |gfloat | | +|G\_TYPE\_DOUBLE |double|gdouble | | +|G\_TYPE\_POINTER | |gpointer | | +|G\_TYPE\_STRING | |gchararray|null-terminated Cstring| +|G\_TYPE\_OBJECT | |GObject | | +|GTK\_TYPE\_WINDOW| |GtkWindow | | + + +A sample program `exp_constant_simple.c` is in `src/expression` directory. + +~~~C + 1 #include + 2 + 3 int + 4 main (int argc, char **argv) { + 5 GtkExpression *expression; + 6 GValue value = G_VALUE_INIT; + 7 + 8 /* Create an expression */ + 9 expression = gtk_constant_expression_new (G_TYPE_INT,100); +10 /* Evaluate the expression */ +11 if (gtk_expression_evaluate (expression, NULL, &value)) +12 g_print ("The value is %d.\n", g_value_get_int (&value)); +13 else +14 g_print ("The constant expression wasn't evaluated correctly.\n"); +15 gtk_expression_unref (expression); +16 g_value_unset (&value); +17 +18 return 0; +19 } +~~~ + +- 9: A constant expression is created. It holds an int value 100. The variable `expression` points the expression. +- 11-14: Evaluates the expression. If it successes, show the value to the stdout. Otherwise show an error message. +- 15-16: Releases the expression and unsets the GValue. Constant expression is usually used to give a constant value or instance to another expression. ## Property expression -Property expression looks up a property in a GObject object. -For example, a property expression that refers "label" property in GtkLabel object is created like this. +A property expression (GtkPropertyExpression) looks up a property in a GObject instance. +For example, a property expression that refers "label" property in a GtkLabel object is created like this. ~~~C expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label"); ~~~ -`another_expression` is expected to give a GtkLabel instance when it is evaluated. +The second parameter `another_expression` is one of: + +- An expression that gives a GtkLabel instance when it is evaluated. +- NULL. When NULL is given, a GtkLabel instance will be given when it is evaluated. +The instance is called `this` object. + For example, ~~~C @@ -236,55 +118,71 @@ expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "l ~~~ If `expression` is evaluated, the second parameter `another_expression` is evaluated in advance. -The value of `another_expression` is `label` (GtkLabel instance). -Then, `expression` looks up "label" property of `label` and the evaluation result is "Hello". +The value of `another_expression` is the `label` (GtkLabel instance). +Then, `expression` looks up "label" property of the label and the evaluation results "Hello". In the example above, the second argument of `gtk_property_expression_new` is another expression. But the second argument can be NULL. If it is NULL, `this` instance is used instead. -`this` is given by `gtk_expression_evaluate` function at the evaluation. +`this` is given by `gtk_expression_evaluate` function. -Now look at `exp.c`. -The lines from 83 to 85 is extracted here. +There's a simple program `exp_property_simple.c` in `src/expression` directory. ~~~C - expression1 = gtk_property_expression_new (GTK_TYPE_ENTRY, NULL, "buffer"); - expression2 = gtk_property_expression_new (GTK_TYPE_ENTRY_BUFFER, expression1, "text"); - gtk_expression_bind (expression2, label2, "label", entry); + 1 #include + 2 + 3 int + 4 main (int argc, char **argv) { + 5 GtkWidget *label; + 6 GtkExpression *expression; + 7 GValue value = G_VALUE_INIT; + 8 + 9 gtk_init (); +10 label = gtk_label_new ("Hello world."); +11 /* Create an expression */ +12 expression = gtk_property_expression_new (GTK_TYPE_LABEL, NULL, "label"); +13 /* Evaluate the expression */ +14 if (gtk_expression_evaluate (expression, label, &value)) +15 g_print ("The value is %s.\n", g_value_get_string (&value)); +16 else +17 g_print ("The constant expression wasn't evaluated correctly.\n"); +18 gtk_expression_unref (expression); +19 g_value_unset (&value); +20 +21 return 0; +22 } ~~~ -- `expression1` looks up "buffer" property of `this` object, which is `GTK_TYPE_ENTRY` type. -- `expression2` looks up "text" property of GtkEntryBuffer object given by `epression1`. -- `gtk_expression_bind` binds a property to a value given by the expression. -In this program, it binds a "label" property in `label2` to the value evaluated with `expresion2` with `entry` as `this` object. -The evaluation process is as follows. - 1. `expression2` is evaluated. But it includes `expression1` so `expression1` is evaluated in advance. - 2. Because the second argument of `expression1` is NULL, `this` object is used. -`this` is given by `gtk_expression_bind`. -It is `entry` (GtkEntry instance). -`expression1` looks up "buffer" property in `entry`. -It is a GtkEntryBuffer instance `buffer`. -(See line 64 in `exp.c`.) - 3. Then, `expression2` looks up "text" property in `buffer`. -It is a text held in `entry`. - 4. The text is assigned to "label" property in `label2`. -- `gtk_expression_bind` creates a GtkExpressionWatch. -(But it isn't assigned to a variable in the program above. -If you want to keep the GtkExpressionWatch instance, assign it to a variable.) +- 9-10: `gtk_init` initializes GTK GUI toolkit. +It isn't usually necessary because the GtkApplication default startup handler does the initialization. +A GtkLabel instance is created with the text "Hello world.". +- 12: A property expression is created. +It looks a "label" property of a GtkLabel instance. +But at the creation, no instance is given because the second argument is NULL. +The expression just knows how to take the property from a future-given GtkLabel instance. +- 14-17: The function `gtk_expression_evaluate` evaluates the expression with a 'this' instance `label`. +The result is stored in the GValue `value`. +The function `g_value_get_string` gets a string from the GValue. +But the string is owned by the GValue so you must not free the string. +- 18-19: Release the expression and unset the GValue. +At the same time the string in the GValue is freed. -~~~C - GtkExpressionWatch *watch; - watch = gtk_expression_bind (expression2, label2, "label", entry); -~~~ - -- Whenever the value from `expression2` changes, it evaluates `expression2` and set "label" property in `label2`. -So, the change of the text in `entry` makes the "label" property reflect it immediately. +If the second argument of `gtk_property_expression_new` isn't NULL, it is another expression. +The expression is owned by a newly created property expression. +So, when the expressions are useless, you just release the last expression. +Then it releases another expression it has. ## Closure expression -Closure expression calls closure when it is evaluated. +A closure expression calls closure when it is evaluated. A closure is a generic representation of a callback (a pointer to a function). -For information about closure, see [GObject API Reference, The GObject messaging system](https://docs.gtk.org/gobject/concepts.html#the-gobject-messaging-system). +For information about closure, see [GObject API Reference -- The GObject messaging system](https://docs.gtk.org/gobject/concepts.html#the-gobject-messaging-system). +There are simple closure example files `closure.c` and `closure_each.c` in the `src/expression` directory. + +There are two types of closure expressions, GtkCClosureExpression and GtkClosureExpression. +They corresponds to GCClosure and GClosure respectively. +When you program in C language, GtkCClosureExpression and GCClosure are appropriate. + A closure expression is created with `gtk_cclosure_expression_new` function. ~~~C @@ -306,6 +204,9 @@ It is a generic marshaller function implemented via libffi. - `n_params` is the number of parameters. - `params` points expressions for each parameter of the call back function. - `callback_func` is a callback function. +It is given arguments `this` and parameters above. +So, if `n_params` is 3, the number of arguments of the function is 4. +(`this` and `params`. See below.) - `user_data` is user data. You can add it for the closure. It is like `user_data` in `g_signal_connect`. @@ -314,124 +215,455 @@ If it is not necessary, assign NULL. It is called to destroy `user_data` when it is no longer needed. If NULL is assigned to `user_data`, assign NULL to `user_destroy`, too. -The following is extracted from `exp.c`. -It is from line 47 to line 56. +Call back functions have the following format. -~~~C -expression = gtk_cclosure_expression_new (GTK_TYPE_APPLICATION_WINDOW, NULL, 0, NULL, - G_CALLBACK (gtk_application_window_new), NULL, NULL); -if (gtk_expression_evaluate (expression, app, &value)) { - win1 = GTK_WIDGET (g_value_get_object (&value)); /* GtkApplicationWindow */ - g_object_ref (win1); - g_print ("Got GtkApplicationWindow object.\n"); -}else - g_print ("The cclosure expression wasn't evaluated correctly.\n"); -gtk_expression_unref (expression); -g_value_unset (&value); /* At the same time, the reference count of win1 is decreased by one. */ +~~~ +C-type +callback (this, param1, param2, ...) ~~~ -The callback function is `gtk_application_window_new`. -This function has one parameter which is an instance of GtkApplication. -And it returns newly created GtkApplicationWindow instance. -So, the first argument is `GTK_TYPE_APPLICATION_WINDOW` which is the type of the return value. -The second argument is NULL so general marshaller `g_cclosure_marshal_generic ()` will be used. -I think assigning NULL works in most cases when you program in C language. - -The arguments given to the call back function are `this` object and parameters which are the fourth argument of `gtk_cclosure_expression_new`. -So, the number of arguments is `n_params + 1`. -Because `gtk_application_window_new` has one parameter, so `n_params` is zero and `**params` is NULL. -No user data is necessary, so `user_data` and `user_destroy` are NULL. - -`gtk_expression_evaluate` evaluates the expression. -`this` instance will be the first argument for `gtk_application_window_new`, so it is `app`. - -If the evaluation succeeds, the GValue `value` holds a newly created GtkApplicationWindow instance. -It is assigned to `win1`. -The GValue will be unset when it is no longer used. -And when it is unset, the GtkApplicationWindow instance will be released and its reference count will be decreased by one. -It is necessary to increase the reference count by one in advance to keep the instance. -`gtk_expression_unref` frees `expression` and `value` is unset. - -As a result, we got a GtkApplicationWindow instance `win1`. -We can do the same by: +For example, ~~~C -win1 = gtk_application_window_new (app); +int +callback (GObject *object, int x, const char *s) ~~~ -The example is more complicated and not practical than this one line code. -The aim of the example is just to show how closure expression works. +The following is `exp_closure_simple.c` in `src/expression`. + +~~~C + 1 #include + 2 + 3 static int + 4 calc (GtkLabel *label) { /* this object */ + 5 const char * s; + 6 int i, j; + 7 + 8 s = gtk_label_get_text (label); /* s is owned by the label. */ + 9 sscanf (s, "%d+%d", &i, &j); +10 return i+j; +11 } +12 +13 int +14 main (int argc, char **argv) { +15 GtkExpression *expression; +16 GValue value = G_VALUE_INIT; +17 GtkLabel *label; +18 +19 gtk_init (); +20 label = GTK_LABEL (gtk_label_new ("123+456")); +21 g_object_ref_sink (label); +22 expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 0, NULL, +23 G_CALLBACK (calc), NULL, NULL); +24 if (gtk_expression_evaluate (expression, label, &value)) /* 'this' object is the label. */ +25 g_print ("%d\n", g_value_get_int (&value)); +26 else +27 g_print ("The closure expression wasn't evaluated correctly.\n"); +28 gtk_expression_unref (expression); +29 g_value_unset (&value); +30 g_object_unref (label); +31 +32 return 0; +33 } +~~~ + +- 3-11: A call back function. +The parameter is only one and it is a 'this' object. +It is a GtkLabel and its label is assumed to be "(number)+(number)". +- 8-10: Retrieves two integers from the label and returns the sum of them. +This function has no error report. +If you want to return error report, change the return value type to be a pointer to a structure of gboolean and integer. +One for error and the other for the sum. +The first argument of `gtk_cclosure_expression_new` is `G_TYPE_POINTER`. +There is a sample program `exp_closure_with_error_report` in `src/expression` directory. +- 19: gtk\_init initializes GTK. It is necessary for GtkLabel. +- 20: A GtkLabel instance is created with "123+456". +- 21: The instance has floating reference. It is changed to an ordinary reference count. +- 22-23: Create a closure expression. Its return value type is `G_TYPE_INT` and no parameters or 'this' object. +- 24: Evaluates the expression with the label as a 'this' object. +- 25: If the evaluation successes, show the sum of "123+456". It's 579. +- 27: If it fails, show an error message. +- 28-30: Releases the expression and the label. Unsets the value. Closure expression is flexible than other type of expression because you can specify your own callback function. ## GtkExpressionWatch -GtkExpressionWatch watches an expression and if the value of the expression changes it calls its notify handler. +GtkExpressionWatch is a structure, not an object. +It represents a watched GtkExpression. +Two functions create GtkExpressionWatch structure. -The example uses GtkExpressionWatch in the line 103 to 106. +### gtk\_expression\_bind function + +This function binds the target object's property to the expression. +If the value of the expression changes, the property reflects the value immediately. ~~~C -expression1 = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-width"); -watch_width = gtk_expression_watch (expression1, win1, notify, NULL, NULL); -expression2 = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-height"); -watch_height = gtk_expression_watch (expression2, win1, notify, NULL, NULL); +GtkExpressionWatch* +gtk_expression_bind ( + GtkExpression* self, + GObject* target, + const char* property, + GObject* this_ +) ~~~ -The expressions above refer to "default-width" and "default-height" properties of GtkWindow. -The variable `watch_width` watches `expression1`. -The second argument `win1` is `this` instance for `expression1`. -So, `watch_width` watches the value of "default-width" property of `win1`. -If the value changes, it calls `notify` handler. -The fourth and fifth arguments are NULL because no user data is necessary. +This function takes the ownership of the expression. +So, if you want to own the expression, call `gtk_expression_ref ()` to increase the reference count of the expression. +And you should unref it when it is useless. +If you don't own the expression, you don't care about releasing the expression. -The variable `watch_height` connects `notify` handler to `expression2`. -So, `notiry` is also called when "default-height" changes. +An example `exp_bind.c` and `exp_bind.ui` is in [`src/expression`](../src/expression) directory. -The handler `norify` is as follows. +![exp\_bind](../image/exp_bind.png) + +It includes a label and a scale. +If you move the slider to the right, the scale value increases and the number on the label also increases. +In the same way, if you move it to the left, the number on the label decreases. +The label is bound to the scale value via an adjustment. + +~~~xml + 1 + 2 + 3 + 4 600 + 5 + 6 + 7 GTK_ORIENTATION_VERTICAL + 8 + 9 +10 10 +11 +12 +13 +14 +15 +16 +17 20.0 +18 0.0 +19 10.0 +20 1.0 +21 5.0 +22 0.0 +23 +24 +25 0 +26 true +27 true +28 0 +29 +30 +31 +32 +33 +34 +~~~ + +The ui file describes the following parent-child relationship. + +~~~ +GtkApplicationWindow (win) -- GtkBox -+- GtkLabel (label) + +- GtkScale +~~~ + +Four GtkScale properties are defined. + +- adjustment. GtkAdjustment provides the followings. + - upper and lower: the range of the scale. + - value: current value of the scale. It reflects the value of the scale. + - step increment and page increment: When a user press an arrow key or page up/down key, +the scale moves by the step increment or page increment respectively. + - page-size: When an adjustment is used with a scale, page-size is zero. +- digits: The number of decimal places that are displayed in the value. +- draw-value: Whether the value is displayed. +- has-origin: Whether the scale has the origin. If it's true, an orange bar appears between the origin and the current point. +- round-digits: The number of digits to round the value to when it changes. +For example, if it is zero, the slider moves to an integer point. ~~~C - 1 static void - 2 notify (gpointer user_data) { - 3 GValue value = G_VALUE_INIT; - 4 char *title; + 1 #include + 2 + 3 GtkExpressionWatch *watch; + 4 + 5 static int + 6 f2i (GObject *object, double d) { + 7 return (int) d; + 8 } + 9 +10 static int +11 close_request_cb (GtkWindow *win) { +12 gtk_expression_watch_unwatch (watch); +13 return false; +14 } +15 +16 static void +17 app_activate (GApplication *application) { +18 GtkApplication *app = GTK_APPLICATION (application); +19 gtk_window_present (gtk_application_get_active_window(app)); +20 } +21 +22 static void +23 app_startup (GApplication *application) { +24 GtkApplication *app = GTK_APPLICATION (application); +25 GtkBuilder *build; +26 GtkWidget *win, *label; +27 GtkAdjustment *adjustment; +28 GtkExpression *expression, *params[1]; +29 +30 /* Builds a window with exp.ui resource */ +31 build = gtk_builder_new_from_file ("exp_bind.ui"); +32 win = GTK_WIDGET (gtk_builder_get_object (build, "win")); +33 label = GTK_WIDGET (gtk_builder_get_object (build, "label")); +34 // scale = GTK_WIDGET (gtk_builder_get_object (build, "scale")); +35 adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (build, "adjustment")); +36 gtk_window_set_application (GTK_WINDOW (win), app); +37 g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL); +38 g_object_unref (build); +39 +40 /* GtkExpressionWatch */ +41 params[0] = gtk_property_expression_new (GTK_TYPE_ADJUSTMENT, NULL, "value"); +42 expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 1, params, G_CALLBACK (f2i), NULL, NULL); +43 watch = gtk_expression_bind (expression, label, "label", adjustment); /* watch takes the ownership of the expression. */ +44 } +45 +46 #define APPLICATION_ID "com.github.ToshioCP.exp_watch" +47 +48 int +49 main (int argc, char **argv) { +50 GtkApplication *app; +51 int stat; +52 +53 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +54 +55 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); +56 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +57 +58 stat =g_application_run (G_APPLICATION (app), argc, argv); +59 g_object_unref (app); +60 return stat; +61 } +~~~ + +The point of the program is: + +- 41-42: Two expressions are defined. +One is a property expression and the other is a closure expression. +The property expression look up the "value"property of the adjustment instance. +The closure expression just converts the double into an integer. +- 43: `gtk_expression_bind` binds the label property of the GtkLabel instance to the integer returned by the closure expression. +It creates a GtkExpressionWatch structure. +The binding works during the watch lives. +When the window is destroyed, the scale and adjustment are also destroyed. +And the watch recognizes the value of the expression changes and tries to change the property of the label. +Obviously, it is not a correct behavior. +The watch should be unwatched before the window is destroyed. +- 37: Connects the "close-request" signal on the window to a handler `close_request_cb`. +This signal is emitted when the close button is clicked. +The handler is called just before the window closes. +It is the right moment to make the GtkExpressionWatch unwatched. +- 10-14: "close-request" signal handler. +`gtk_expression_watch_unwatch (watch)` makes the watch stop watching the expression. +It releases the expression and calls `gtk_expression_watch_unref (watch)` in it. + +If you want to bind a property to an expression, `gtk_expression_bind` is the best choice. +You can do it with `gtk_expression_watch` function, but it is less suitable. + +### gtk\_expression\_watch function + +~~~C +GtkExpressionWatch* +gtk_expression_watch ( + GtkExpression* self, + GObject* this_, + GtkExpressionNotify notify, + gpointer user_data, + GDestroyNotify user_destroy +) +~~~ + +The function doesn't take the ownership of the expression. +It differs from `gtk_expression_bind`. +So, you need to release the expression when it is useless. +It creates a GtkExpressionWatch structure. +The third parameter `notify` is a callback to invoke when the expression changes. +You can set `user_data` to give it to the callback. +The last parameter is a function to destroy the `user_data` when the watch is unwatched. +Put NULL if you don't need them. + +Notify callback has the following format. + +~~~C +void +notify ( + gpointer user_data +) +~~~ + +This function is used to do something when the value of the expression changes. +But if you want to bind a property to the value, use `gtk_expression_bind` instead. + +There's a sample program `exp_watch.c` in [`src/expression`](../src/expression) directory. +It outputs the width of the window to the standard output. + +![exp\_watch](../image/exp_watch.png) + +When you resize the window, the width is displayed in the terminal. + +~~~C + 1 #include + 2 + 3 GtkExpression *expression; + 4 GtkExpressionWatch *watch; 5 - 6 if (watch_width && gtk_expression_watch_evaluate (watch_width, &value)) - 7 width = g_value_get_int (&value); - 8 g_value_unset (&value); - 9 if (watch_height && gtk_expression_watch_evaluate (watch_height, &value)) -10 height = g_value_get_int (&value); -11 g_value_unset (&value); -12 title = g_strdup_printf ("%d x %d", width, height); -13 gtk_window_set_title (GTK_WINDOW (win1), title); -14 g_free (title); -15 } + 6 static void + 7 notify (gpointer user_data) { + 8 GValue value = G_VALUE_INIT; + 9 +10 if (gtk_expression_watch_evaluate (watch, &value)) +11 g_print ("width = %d\n", g_value_get_int (&value)); +12 else +13 g_print ("evaluation failed.\n"); +14 } +15 +16 static int +17 close_request_cb (GtkWindow *win) { +18 gtk_expression_watch_unwatch (watch); +19 gtk_expression_unref (expression); +20 return false; +21 } +22 +23 static void +24 app_activate (GApplication *application) { +25 GtkApplication *app = GTK_APPLICATION (application); +26 gtk_window_present (gtk_application_get_active_window(app)); +27 } +28 +29 static void +30 app_startup (GApplication *application) { +31 GtkApplication *app = GTK_APPLICATION (application); +32 GtkWidget *win; +33 +34 win = GTK_WIDGET (gtk_application_window_new (app)); +35 g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL); +36 +37 expression = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-width"); +38 watch = gtk_expression_watch (expression, win, notify, NULL, NULL); +39 } +40 +41 #define APPLICATION_ID "com.github.ToshioCP.exp_watch" +42 +43 int +44 main (int argc, char **argv) { +45 GtkApplication *app; +46 int stat; +47 +48 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +49 +50 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); +51 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +52 +53 stat =g_application_run (G_APPLICATION (app), argc, argv); +54 g_object_unref (app); +55 return stat; +56 } ~~~ -- 6-11: Evaluates `expression1` and `expression2` with `expression_watch_evaluate` function. -- 12: Creates a string `title`. -It contains the width and height, for example, "800 x 600". -- 13: Sets the title of `win1` with the string `title`. +- 37: A property expression looks up the "default-width" property of the window. +- 38: Create a watch structure for the expression. +The callback `notify` is called every time the value of the expression changes. +The 'this' object is `win`, so the expression returns the default width of the window. +- 6-14: The callback function `notify`. +It uses `gtk_expression_watch_evaluate` to get the value of the expression. +The 'this' object is given in advance (when the watch is created). +It outputs the window width to the standard output. +- 16-21: A handler for the "close-request"signal on the window. +It stops the watch. +In addition, it releases the reference to the expression. +Because `gtk_expression_watch` doesn't take the ownership of the expression, you own it. +So, the release is necessary. -The title of the window reflects the size of the window. +## Gtkexpression in ui files -## exp.ui +GtkBuilder supports GtkExpressions. +There are four tags. -`exp.c` builds a GtkWindow instance `win2` with `exp.ui`. -The ui file `exp.ui` includes tags to create GtkExpressions. -The tags are: +- constant tag to create constant expression. Type attribute specifies the type name of the value. +If no type is specified, the type is assumed to be an object. +The content is the value of the expression. +- lookup tag to create property expression. Type attribute specifies the type of the object. +Name attribute specifies the property name. +The content is an expression or object which has the property to look up. +If there's no content, 'this' object is used. +- closure tag to create closure expression. Type attribute specifies the type of the returned value. +Function attribute specifies the callback function. +The contents of the tag are arguments that are expressions. +- binding tag to bind a property to an expression. +It is put in the content of an object tag. +Name attribute specifies the property name of the object. +The content is an expression. -- constant tag to create constant expression -- lookup tag to create property expression -- closure tag to create closure expression -- binding tag to bind a property to an expression +~~~xml +Hello world +label + + + win + +~~~ -The window `win2` behaves like `win1`. -Because similar expressions are built with the ui file. +These tags are usually used for GtkBuilderListItemFactory. + +~~~xml + + + +~~~ + +In the xml file above, "GtkListItem" is an instance of the GtkListItem template. +It is the 'this' object given to the expressions. +(The information is in the [GTK Development Blog](https://blog.gtk.org/2020/09/)). + +GtkBuilderListItemFactory uses GtkBuilder to build the XML data. +It sets the current object of the GtkBuilder to the GtkListItem instance. + +GtkBuilder calls `gtk_expression_bind` function in the binding tag analysis. +The function sets the 'this' object like this: + +1. If the binding tag has object attribute, the object will be the 'this' object. +2. If the current object of the GtkBuilder exists, it will be the 'this' object. +That's why a GtkListItem instance is the 'this' object of the XML data for a GtkBuilderListItemFactory. +3. Otherwise, the target object of the binding tag will be the 'this' object. + +GTK 4 document doesn't describe information about "this" object when expressions are defined in a ui file. +The information above is found from the GTK 4 source files and it is possible to include mistakes. +If you have accurate information, please let me know. + +A sample program `exp.c` and a ui file `exp.ui` is in [`src/expression`](../src/expression) directory. +The ui file includes lookup, closure and bind tags. +No constant tag is included. +However, constant tags are not used so often. + +![exp.c](../image/exp.png) + +If you resize the window, the size is shown at the title of the window. +If you type characters in the entry, the same characters appear on the label. + +The ui file is as follows. ~~~xml 1 2 - 3 + 3 4 5 6 @@ -446,215 +678,108 @@ Because similar expressions are built with the ui file. 15 16 17 -18 100 -19 -20 -21 -22 -23 -24 -25 -26 -27 entry -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 win2 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 +18 +19 buffer +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 ~~~ -### Constant tag +- 4-9: The title property of the main window is bound to a closure expression. +Its callback function `set_title` is defined in the C source file. +It returns a string because the type attribute of the tag is "gchararray". +Two parameters are given to the function. +They are width and height of the window. +Lookup tags don't have contents, so 'this' object is used to look up the properties. +The 'this' object is `win`, which is the target of the binding (`win` includes the binding tag). +- 17-21: The "label" property of the GtkLabel instance is bound to the "text" property of `buffer`, +which is the buffer of GtkEntry defined in line 25. +If a user types characters in the entry, the same characters appear on the label. -A constant tag corresponds to a constant expression. +The C source file is as follows. -- 18: Constant tag. -The constant expression is created with the tag. -It returns 100, the type is "gint", when it is evaluated. -The type "gint" is a name of `G_TYPE_INT` type. -Similarly, the types which is registered to the type system has type and name. -For example, "gchararray" is a name of `G_TYPE_STRING` type. -You need to use the name of types for the `type`attribute. -See [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial/blob/main/gfm/sec5.md#gvalue). -- 17-19: Binding tag corresponds to `gtk_expression_bind` function. -`name` attribute specifies the "label" property of the GtkLabel object just before the binding tag. -The expression returns a int type GValue. -On the other hand "label" property holds a string type GValue. -When a GValue is copied to another GValue, the type is automatically converted if possible. -In this case, an int `100` is converted to a string `"100"`. +~~~C + 1 #include + 2 + 3 char * + 4 set_title (GtkWidget *win, int width, int height) { + 5 return g_strdup_printf ("%d x %d", width, height); + 6 } + 7 + 8 static void + 9 app_activate (GApplication *application) { +10 GtkApplication *app = GTK_APPLICATION (application); +11 gtk_window_present (gtk_application_get_active_window(app)); +12 } +13 +14 static void +15 app_startup (GApplication *application) { +16 GtkApplication *app = GTK_APPLICATION (application); +17 GtkBuilder *build; +18 GtkWidget *win; +19 +20 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp.ui"); +21 win = GTK_WIDGET (gtk_builder_get_object (build, "win")); +22 gtk_window_set_application (GTK_WINDOW (win), app); +23 g_object_unref (build); +24 } +25 +26 #define APPLICATION_ID "com.github.ToshioCP.exp" +27 +28 int +29 main (int argc, char **argv) { +30 GtkApplication *app; +31 int stat; +32 +33 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +34 +35 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); +36 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +37 +38 stat =g_application_run (G_APPLICATION (app), argc, argv); +39 g_object_unref (app); +40 return stat; +41 } +~~~ -These binding and constant tag works. -But they are not good. -A property tag is more straightforward. +- 4-6: The callback function. +It returns a string (w)x(h), where the w and h are the width and height of the window. +String duplication is necessary. + +The C source file is very simple because almost everything is done in the ui file. + +### Conversion between GValues + +If you bind different type properties, type conversion is automatically done. +Suppose a label property (string) is bound to default-width property (int). ~~~xml - 100 + + + win + + ~~~ -This example just shows the way how to use constant tag. -Constant tag is mainly used to give a constant argument to a closure. +The expression created by the lookup tag returns a int type GValue. +On the other hand "label" property holds a string type GValue. +When a GValue is copied to another GValue, the type is automatically converted if possible. +If the current width is 100, an int `100` is converted to a string `"100"`. -### Lookup tag +If you use `g_object_get` and `g_object_set` to copy properties, the value is automatically converted. -A lookup tag corresponds to a property expression. -Line 23 to 31 is copied here. - -~~~xml - - - - - entry - - - - -~~~ - -- binding tag binds a "label" property in GtkLabel to an expression. -The expression is defined with a lookup tag. -- The lookup tag defines a property expression looks up a "text" property in the instance which is defined in the next expression. -The next expression is created with the lookup tag. -The expression looks up the `buffer` property of the `entry` instance. -The `entry` instance is defined in the line 43. -It is a GtkEntry `entry`. -A lookup tag takes an instance in some ways to look up for a property. - - If it has no contents, it takes `this` instance when it is evaluated. - - If it has a content of a tag for an expression, which is constant, lookup or closure tag, the value of the expression will be the instance to look up when it is evaluated. - - If it has a content of an id of an object, then the instance of the object will be taken as the instance to lookup. - -As a result, the label of the GtkLabel instance are bound to the text in the field of GtkEntry. -If a user input a text in the field in the GtkEntry, GtkLabel displays the same text. - -Another lookup tag is in the lines from 34 to 40. - -~~~xml - - - - win2 - - - -~~~ - -- Two expressions are nested. -- A lookup tag looks up "application-id" property of the next expression. -- The next lookup tag looks up "application" property of `win2` instance. - -As a result, the "label" property in the GtkLabel instance is bound to the "application-id" property. -The nested tag makes a chain like: - -~~~ -"label" <= "application-id" <= "application" <= `win2` -~~~ - -By the way, the application of `win2` is set after the objects in ui file are built. -Look at `exp.c`. -`gtk_window_set_application` is called after `gtk_build_new_from_resource`. - -~~~C -build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp.ui"); -GtkWidget *win2 = GTK_WIDGET (gtk_builder_get_object (build, "win2")); -gtk_window_set_application (GTK_WINDOW (win2), app); -~~~ - -Therefore, before the call for `gtk_window_set_application`, the "application" property of `win2` is *not* set. -So, the evaluation of `win2` fails. -And the evaluation of `` also fails. -A function `gtk_expression_bind ()`, which corresponds to `binding` tag, doesn't update the target property if the expression fails. -So, the "label" property isn't updated at the first evaluation. - -Note that an evaluation can fail. -The care is especially necessary when you write a callback for a closure tag which has contents of expressions like lookup tags. -The expressions are given to the callback as an argument. -If an expression fails the argument will be NULL. -You need to check if the argument exactly points the instance that is expected by the callback. - -### Closure tag - -The lines from 3 to 9 include a closure tag. - -~~~xml - - - - - - - -~~~ - -- A binding tag corresponds to a `gtk_expression_bind` function. -`name` attribute specifies the "title" property of `win2`. -Binding tag gives `win2` as the `this` instance to the expressions, which are the contents of the binding tag. -So, closure tag and lookup tags use `win2` as the `this` object when they are evaluated. -- A closure tag corresponds to a closure expression. -Its callback function is `set_title` and it returns "gchararray" type, which is "an array of characters" i.e. a string. -The contents of the closure tag are assigned to parameters of the function. -So, `set_title` has three parameters, `win2` (`this` instance), default width and default height. -- Lookup tags correspond to property expressions. -They lookup "default-width" and "default-height" properties of `win2` (`this` instance). -- Binding tab creates GtkExpressionWatch automatically, so "title" property reflects the changes of "default-width" and "default-height" properties. - -`set_title` function in `exp.c` is as follows. - -~~~C -1 char * -2 set_title (GtkWidget *win, int width, int height) { -3 return g_strdup_printf ("%d x %d", width, height); -4 } -~~~ - -It just creates a string, for example, "800 x 600", and returns it. - -You've probably been noticed that ui file is easier and clearer than the corresponding C program. -One of the most useful case of GtkExpression is building GtkListItem instance with GtkBuilderListItemFatory. -Such case has already been described in the prior two sections. - -It will be used in the next section to build GtkListItem in GtkColumnView, which is the most useful view object for GListModel. - -## Compilation and execution - -All the sources are in [src/expression](../src/expression) directory. -Change your current directory to the directory and run meson and ninja. -Then, execute the application. - -~~~ -$ meson _build -$ ninja -C _build -$ build/exp -~~~ - -Then, two windows appear. - -![Expression](../image/expression.png) - -If you put some text in the field of the entry, then the same text appears in the second GtkLabel. -Because the "label" property of the second GtkLabel instance is bound to the text in the GtkEntryBuffer. - -If you resize the window, then the size appears in the title bar because the "title" property is bound to "default-width" and "default-height" properties. - - -Up: [Readme.md](../Readme.md), Prev: [Section 27](sec27.md), Next: [Section 29](sec29.md) +Up: [README.md](../README.md), Prev: [Section 27](sec27.md), Next: [Section 29](sec29.md) diff --git a/gfm/sec29.md b/gfm/sec29.md index 3bea364..bbdb25c 100644 --- a/gfm/sec29.md +++ b/gfm/sec29.md @@ -1,4 +1,4 @@ -Up: [Readme.md](../Readme.md), Prev: [Section 28](sec28.md) +Up: [README.md](../README.md), Prev: [Section 28](sec28.md) # GtkColumnView @@ -13,11 +13,11 @@ Each column is GtkColumnViewColumn. The property points a GtkSelectionModel object. - Each GtkColumnViewColumn has "factory" property. The property points a GtkListItemFactory (GtkSignalListItemFactory or GtkBuilderListItemFactory). -- The factory connects GtkListItem, which belongs to GtkColumnViewColumn, and items of GtkSelectionModel. -And the factory builds the descendants widgets of GtkColumnView to display the item on the display. +- The factory connects GtkListItem and items of GtkSelectionModel. +And the factory builds the descendant widgets of GtkColumnView to display the item on the display. This process is the same as the one in GtkListView. -The following diagram shows the image how it works. +The following diagram shows how it works. ![ColumnView](../image/column.png) @@ -29,7 +29,7 @@ In addition, the example uses GtkSortListModel and GtkSorter to sort the informa ## column.ui -Ui file specifies whole widgets and their structure. +Ui file specifies widgets and list item templates. ~~~xml 1 @@ -39,15 +39,15 @@ Ui file specifies whole widgets and their structure. 5 800 6 600 7 - 8 + 8 9 TRUE 10 TRUE 11 12 13 - 14 + 14 15 - 16 + 16 17 18 19 standard::name,standard::icon,standard::size,time::modified @@ -61,7 +61,7 @@ Ui file specifies whole widgets and their structure. 27 28 29 - 30 + 30 31 Name 32 TRUE 33 @@ -102,7 +102,7 @@ Ui file specifies whole widgets and their structure. 68 69 70 - 71 + 71 72 73 74 @@ -112,7 +112,7 @@ Ui file specifies whole widgets and their structure. 78 79 80 - 81 + 81 82 Size 83 84 @@ -125,7 +125,7 @@ Ui file specifies whole widgets and their structure. 91 TRUE 92 0 93 - 94 + 94 95 GtkListItem 96 97 @@ -137,7 +137,7 @@ Ui file specifies whole widgets and their structure. 103 104 105 -106 +106 107 108 109 @@ -148,7 +148,7 @@ Ui file specifies whole widgets and their structure. 114 115 116 -117 +117 118 Date modified 119 120 @@ -173,7 +173,7 @@ Ui file specifies whole widgets and their structure. 139 140 141 -142 +142 143 144 145 @@ -191,18 +191,18 @@ Ui file specifies whole widgets and their structure. 157 ~~~ -- 3-12: Widget parent-child relationship is GtkApplicationWindow => GtkScrolledWindow => GtkColumnView. +- 3-12: GtkApplicationWindow has a child widget GtkScrolledWindow. +GtkScrolledWindoww has a child widget GtkColumnView. - 12-18: GtkColumnView has "model" property. It points GtkSelectionModel interface. -In this ui file, GtkSingleSelection is used as GtkSelectionModel. -GtkSingleSelection is an object that implements GtkSelectionModel. +GtkNoSelection class is used as GtkSelectionModel. And again, it has "model" property. It points GtkSortListModel. This list model supports sorting the list. It will be explained in the later subsection. And it also has "model" property. It points GtkDirectoryList. -Therefore, the chain is: GtkColumnView => GtkSingleSelection => GtkSortListModel => GtkDirectoryList. +Therefore, the chain is: GtkColumnView => GtkNoSelection => GtkSortListModel => GtkDirectoryList. - 18-20: GtkDirectoryList. It is a list of GFileInfo, which holds information of files under a directory. It has "attributes" property. @@ -213,30 +213,27 @@ It specifies what attributes is kept in each GFileInfo. - "time::modified" is the date and time the file was last modified. - 29-79: The first GtkColumnViewColumn object. There are four properties, "title", "expand", factory" and "sorter". -- 31: Sets the "title" property with "Name". +- 31: Sets the "title" property to "Name". This is the title on the header of the column. - 32: Sets the "expand" property to TRUE to allow the column to expand as much as possible. (See the image above). -- 33- 69: Sets the "factory" property with GtkBuilderListItemFactory. +- 33- 69: Sets the "factory" property to GtkBuilderListItemFactory. The factory has "bytes" property which holds a ui string to define a template to build GtkListItem composite widget. The CDATA section (line 36-66) is the ui string to put into the "bytes" property. The contents are the same as the ui file `factory_list.ui` in the section 27. -- 70-77: Sets the "sorter" property with GtkStringSorter object. +- 70-77: Sets the "sorter" property to GtkStringSorter object. This object provides a sorter that compares strings. -It has "expression" property which is set with GtkExpression. +It has "expression" property. A closure tag with a string type function `get_file_name` is used here. The function will be explained later. - 80-115: The second GtkColumnViewColumn object. -Its "title", "factory" and "sorter" properties are set. -GtkNumericSorter is used. +Its sorter property is set to GtkNumericSorter. - 116-151: The third GtkColumnViewColumn object. -Its "title", "factory" and "sorter" properties are set. -GtkNumericSorter is used. +Its sorter property is set to GtkNumericSorter. ## GtkSortListModel and GtkSorter -GtkSortListModel is a list model that sorts its elements according to a GtkSorter. -It has "sorter" property that is set with GtkSorter. +GtkSortListModel is a list model that sorts its elements according to a GtkSorter instance assigned to the "sorter" property. The property is bound to "sorter" property of GtkColumnView in line 22 to 24. ~~~xml @@ -255,21 +252,25 @@ If the user clicks the header of another column, then the "sorter" property of t The binding above makes a indirect connection between the "sorter" property of GtkSortListModel and the "sorter" property of each column. +GtkSorter compares two items (GObject or its descendant). GtkSorter has several child objects. -- GtkStringSorter compares strings. -- GtkNumericSorter compares numbers. +- GtkStringSorter compares strings taken from the items. +- GtkNumericSorter compares numbers taken from the items. - GtkCustomSorter uses a callback to compare. - GtkMultiSorter combines multiple sorters. The example uses GtkStringSorter and GtkNumericSorter. -GtkStringSorter uses GtkExpression to get the strings from the objects. -The GtkExpression is stored in the "expression" property of GtkStringSorter. -For example, in the ui file above, the GtkExpression is in the line 71 to 76. +GtkStringSorter uses GtkExpression to get the strings from the items (objects). +The GtkExpression is stored in the "expression" property of the GtkStringSorter. +When GtkStringSorter compares two items, it evaluates the expression by calling `gtk_expression_evaluate` function. +It assigns each item to the second argument ('this' object) of the function. + +In the ui file above, the GtkExpression is in the line 71 to 76. ~~~xml - + @@ -282,24 +283,23 @@ The GtkExpression calls `get_file_name` function when it is evaluated. ~~~C 1 char * 2 get_file_name (GFileInfo *info) { -3 g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); -4 -5 return g_strdup(g_file_info_get_name (info)); -6 } +3 return G_IS_FILE_INFO (info) ? g_strdup(g_file_info_get_name (info)) : NULL; +4 } ~~~ The function is given the item (GFileInfo) of the GtkSortListModel as an argument (`this` object). +But you need to be careful that it can be NULL while the list item is being recycled. +So, `G_IS_FILE_INFO (info)` is always necessary in callback functions. The function retrieves a filename from `info`. The string is owned by `info` so it is necessary to duplicate it. And it returns the copied string. -The string will be owned by the expression. GtkNumericSorter compares numbers. It is used in the line 106 to 112 and line 142 to 148. The lines from 106 to 112 is: ~~~xml - + @@ -313,10 +313,8 @@ The closure tag specifies a callback function `get_file_size`. ~~~C 1 goffset 2 get_file_size (GFileInfo *info) { -3 g_return_val_if_fail (G_IS_FILE_INFO (info), -1); -4 -5 return g_file_info_get_size (info); -6 } +3 return G_IS_FILE_INFO (info) ? g_file_info_get_size (info): -1; +4 } ~~~ It just returns the size of `info`. @@ -340,13 +338,11 @@ The closure tag specifies a callback function `get_file_unixtime_modified`. ~~~C 1 gint64 2 get_file_unixtime_modified (GFileInfo *info) { -3 g_return_val_if_fail (G_IS_FILE_INFO (info), -1); +3 GDateTime *dt; 4 -5 GDateTime *dt; -6 -7 dt = g_file_info_get_modification_date_time (info); -8 return g_date_time_to_unix (dt); -9 } +5 dt = G_IS_FILE_INFO (info) ? g_file_info_get_modification_date_time (info) : NULL; +6 return dt ? g_date_time_to_unix (dt) : -1; +7 } ~~~ It gets the modification date and time (GDateTime type) of `info`. @@ -357,140 +353,106 @@ It returns the unix time (gint64 type). ## column.c `column.c` is as follows. +It is simple and short thanks to `column.ui`. ~~~C - 1 #include - 2 - 3 /* functions (closures) for GtkBuilderListItemFactory */ - 4 GIcon * - 5 get_icon_factory (GtkListItem *item, GFileInfo *info) { - 6 GIcon *icon; - 7 if (! G_IS_FILE_INFO (info)) - 8 return NULL; - 9 else { - 10 icon = g_file_info_get_icon (info); - 11 g_object_ref (icon); - 12 return icon; - 13 } - 14 } - 15 - 16 char * - 17 get_file_name_factory (GtkListItem *item, GFileInfo *info) { - 18 if (! G_IS_FILE_INFO (info)) - 19 return NULL; - 20 else - 21 return g_strdup (g_file_info_get_name (info)); - 22 } - 23 - 24 char * - 25 get_file_size_factory (GtkListItem *item, GFileInfo *info) { - 26 /* goffset is gint64 */ - 27 goffset size; - 28 - 29 if (! G_IS_FILE_INFO (info)) - 30 return NULL; - 31 else { - 32 size = g_file_info_get_size (info); - 33 return g_strdup_printf ("%ld", (long int) size); - 34 } - 35 } - 36 - 37 char * - 38 get_file_time_modified_factory (GtkListItem *item, GFileInfo *info) { - 39 GDateTime *dt; - 40 - 41 if (! G_IS_FILE_INFO (info)) - 42 return NULL; - 43 else { - 44 dt = g_file_info_get_modification_date_time (info); - 45 return g_date_time_format (dt, "%F"); - 46 } - 47 } - 48 - 49 /* Functions (closures) for GtkSorter */ - 50 char * - 51 get_file_name (GFileInfo *info) { - 52 g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); - 53 - 54 return g_strdup(g_file_info_get_name (info)); - 55 } - 56 - 57 goffset - 58 get_file_size (GFileInfo *info) { - 59 g_return_val_if_fail (G_IS_FILE_INFO (info), -1); - 60 - 61 return g_file_info_get_size (info); - 62 } - 63 - 64 gint64 - 65 get_file_unixtime_modified (GFileInfo *info) { - 66 g_return_val_if_fail (G_IS_FILE_INFO (info), -1); - 67 - 68 GDateTime *dt; - 69 - 70 dt = g_file_info_get_modification_date_time (info); - 71 return g_date_time_to_unix (dt); - 72 } - 73 - 74 /* ----- activate, open, startup handlers ----- */ - 75 static void - 76 app_activate (GApplication *application) { - 77 GtkApplication *app = GTK_APPLICATION (application); - 78 GFile *file; - 79 - 80 GtkBuilder *build = gtk_builder_new_from_resource ("/com/github/ToshioCP/column/column.ui"); - 81 GtkWidget *win = GTK_WIDGET (gtk_builder_get_object (build, "win")); - 82 GtkDirectoryList *directorylist = GTK_DIRECTORY_LIST (gtk_builder_get_object (build, "directorylist")); - 83 g_object_unref (build); - 84 - 85 gtk_window_set_application (GTK_WINDOW (win), app); - 86 - 87 file = g_file_new_for_path ("."); - 88 gtk_directory_list_set_file (directorylist, file); - 89 g_object_unref (file); - 90 - 91 gtk_widget_show (win); - 92 } - 93 - 94 static void - 95 app_startup (GApplication *application) { - 96 } - 97 - 98 #define APPLICATION_ID "com.github.ToshioCP.columnview" - 99 -100 int -101 main (int argc, char **argv) { -102 GtkApplication *app; -103 int stat; -104 -105 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE); -106 -107 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); -108 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); -109 /* g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);*/ -110 -111 stat =g_application_run (G_APPLICATION (app), argc, argv); -112 g_object_unref (app); -113 return stat; -114 } -115 + 1 #include + 2 + 3 /* functions (closures) for GtkBuilderListItemFactory */ + 4 GIcon * + 5 get_icon_factory (GtkListItem *item, GFileInfo *info) { + 6 GIcon *icon; + 7 + 8 /* g_file_info_get_icon can return NULL */ + 9 icon = G_IS_FILE_INFO (info) ? g_file_info_get_icon (info) : NULL; +10 return icon ? g_object_ref (icon) : NULL; +11 } +12 +13 char * +14 get_file_name_factory (GtkListItem *item, GFileInfo *info) { +15 return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL; +16 } +17 +18 /* goffset is defined as gint64 */ +19 /* It is used for file offsets. */ +20 goffset +21 get_file_size_factory (GtkListItem *item, GFileInfo *info) { +22 return G_IS_FILE_INFO (info) ? g_file_info_get_size (info) : -1; +23 } +24 +25 char * +26 get_file_time_modified_factory (GtkListItem *item, GFileInfo *info) { +27 GDateTime *dt; +28 +29 /* g_file_info_get_modification_date_time can return NULL */ +30 dt = G_IS_FILE_INFO (info) ? g_file_info_get_modification_date_time (info) : NULL; +31 return dt ? g_date_time_format (dt, "%F") : NULL; +32 } +33 +34 /* Functions (closures) for GtkSorter */ +35 char * +36 get_file_name (GFileInfo *info) { +37 return G_IS_FILE_INFO (info) ? g_strdup(g_file_info_get_name (info)) : NULL; +38 } +39 +40 goffset +41 get_file_size (GFileInfo *info) { +42 return G_IS_FILE_INFO (info) ? g_file_info_get_size (info): -1; +43 } +44 +45 gint64 +46 get_file_unixtime_modified (GFileInfo *info) { +47 GDateTime *dt; +48 +49 dt = G_IS_FILE_INFO (info) ? g_file_info_get_modification_date_time (info) : NULL; +50 return dt ? g_date_time_to_unix (dt) : -1; +51 } +52 +53 static void +54 app_activate (GApplication *application) { +55 GtkApplication *app = GTK_APPLICATION (application); +56 gtk_window_present (gtk_application_get_active_window(app)); +57 } +58 +59 static void +60 app_startup (GApplication *application) { +61 GtkApplication *app = GTK_APPLICATION (application); +62 GFile *file; +63 GtkBuilder *build = gtk_builder_new_from_resource ("/com/github/ToshioCP/column/column.ui"); +64 GtkWidget *win = GTK_WIDGET (gtk_builder_get_object (build, "win")); +65 GtkDirectoryList *directorylist = GTK_DIRECTORY_LIST (gtk_builder_get_object (build, "directorylist")); +66 g_object_unref (build); +67 +68 gtk_window_set_application (GTK_WINDOW (win), app); +69 +70 file = g_file_new_for_path ("."); +71 gtk_directory_list_set_file (directorylist, file); +72 g_object_unref (file); +73 } +74 +75 #define APPLICATION_ID "com.github.ToshioCP.columnview" +76 +77 int +78 main (int argc, char **argv) { +79 GtkApplication *app; +80 int stat; +81 +82 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); +83 +84 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); +85 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +86 +87 stat =g_application_run (G_APPLICATION (app), argc, argv); +88 g_object_unref (app); +89 return stat; +90 } +91 ~~~ -- 4-47: Functions for the closure tag in the "bytes" property of GtkBuilderListItemFactory. -These are almost same as the functions in section 26 and 26. -- 50-72: Functions for the closure in the expression property of GtkStringSorter or GtkNumericSorter. -- 75-92: `app_activate` is an "activate" handler of GApplication. -- 80-83: Builds objects with ui resource and gets `win` and `directorylist`. -- 85: Sets the application of the top level window with `app`. -- 87-89: Sets the file of `directorylist` with "." (current directory). -- 94-96: Startup handler. -- 98-114: `main` function. - -`exp.c` is simple and short thanks to `exp.ui`. ## Compilation and execution. -All the source files are in [src/column](../src/column) directory. +All the source files are in [`src/column`](../src/column) directory. Change your current directory to the directory and type the following. ~~~ @@ -509,5 +471,4 @@ If you click the header of another column, then the whole lists are sorted by th GtkColumnView is very useful and it can manage very big GListModel. It is possible to use it for file list, application list, database frontend and so on. - -Up: [Readme.md](../Readme.md), Prev: [Section 28](sec28.md) +Up: [README.md](../README.md), Prev: [Section 28](sec28.md) diff --git a/gfm/sec3.md b/gfm/sec3.md index cc74132..6c7f88d 100644 --- a/gfm/sec3.md +++ b/gfm/sec3.md @@ -59,9 +59,27 @@ In a broad sense, object has wider meaning than instance. So, readers should be careful of the contexts to find the meaning of "object". In many cases, object and instance are the same. +The function `gtk_application_new` has two parameters. + +- Application ID (com.github.ToshioCP.pr1). +It is used to distinguish applications by the system. +The format is reverse-DNS. +See [GNOME Developer Documentation -- Application ID](https://developer.gnome.org/documentation/tutorials/application-id.html) for further information. + +- Application flag (G\_APPLICATION\_DEFAULT\_FLAGS). +If the application runs without any arguments, the flag is G\_APPLICATION\_DEFAULT\_FLAGS. +Otherwise, you need other flags. +See [GIO API reference](https://docs.gtk.org/gio/flags.ApplicationFlags.html) for further information. + + + To compile this, the following command needs to be run. The string `pr1.c` is the filename of the C source code above. +Notice: If your GLib-2.0 version is older than 2.74, use `G_APPLICATION_FLAGS_NONE` instead of `G_APPLICATION_DEFAULT_FLAGS`. +It is an old flag replaced by `G_APPLICATION_DEFAULT_FLAGS` and deprecated since version 2.74. +However, many distributions use GLib-2.0 version 2.72 or older, for example, Ubuntu 22.04 LTS. + ~~~ $ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4` ~~~