Add section 25.

This commit is contained in:
Toshio Sekiya 2021-03-24 16:37:24 +09:00
parent 126e3624b6
commit 0efa4c2249
33 changed files with 1624 additions and 602 deletions

4
.gitignore vendored
View file

@ -24,8 +24,8 @@ src/color/_build
src/turtle/_build
src/temp
src/le
doc/GFile_example/_build
src/list4/_build
src/list5/_build
html/*
latex/*

View file

@ -38,3 +38,4 @@ You can read it without download.
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec22.md)
1. [Tiny turtle graphics interpreter](gfm/sec23.md)
1. [GtkListView](gfm/sec24.md)
1. [GtkGridView and activate signal](gfm/sec25.md)

View file

@ -1,54 +0,0 @@
/* Samples of GFile related objects */
/* Copy file program */
/* This function uses 'g_input_stream_read' and 'g_output_stream_write'. */
#include <gio/gio.h>
void
usage () {
g_printerr ("Usage: copy file1 file2\n");
exit (1);
}
#define MAXBUF 4096
int
main (int argc, char **argv) {
GFile *file1, *file2;
GFileInputStream *istream;
GFileOutputStream *ostream;
char buf[MAXBUF];
gssize size;
GError *err = NULL;
int status = 0;
if (argc != 3)
usage();
file1 = g_file_new_for_commandline_arg (argv[1]);
file2 = g_file_new_for_commandline_arg (argv[2]);
if (! (istream = g_file_read (file1, NULL, &err))) {
g_warning ("%s\n", err->message);
return 1;
}
if (! (ostream = g_file_replace (file2, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &err))) {
g_warning ("%s\n", err->message);
return 1;
}
while ((size = g_input_stream_read (G_INPUT_STREAM (istream), buf, MAXBUF, NULL, &err)) > 0) {
if ((size = g_output_stream_write (G_OUTPUT_STREAM (ostream), buf, (gsize) size, NULL, &err)) < 0)
break;
}
if (size < 0) {
g_warning ("%s\n", err->message);
status = 1;
}
if (! (g_input_stream_close (G_INPUT_STREAM (istream), NULL, &err))) {
g_warning ("%s\n", err->message);
status = 1;
}
if (! (g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, &err))) {
g_warning ("%s\n", err->message);
status = 1;
}
return status;
}

View file

@ -1,27 +0,0 @@
/* Samples of GFile related objects */
/* Copy file program */
/* This function uses 'g_input_stream_read' and 'g_output_stream_write'. */
#include <gio/gio.h>
void
usage () {
g_printerr ("Usage: gfile_object (file|uri)\n");
exit (1);
}
int
main (int argc, char **argv) {
GFile *file;
GType type;
if (argc != 2)
usage();
file = g_file_new_for_commandline_arg (argv[1]);
type = G_OBJECT_TYPE (file);
g_print ("%s\n", g_type_name (type));
while (type = g_type_parent (type))
g_print ("%s\n", g_type_name (type));
return 0;
}

View file

@ -1,11 +0,0 @@
project('gio_samples', 'c')
compiler = meson.get_compiler('c')
giodep = dependency('gio-2.0')
executable('readline', 'readline.c', dependencies: giodep, install: false)
executable('copy', 'copy.c', dependencies: giodep, install: false)
executable('gfile_object', 'gfile_object.c', dependencies: giodep, install: false)
run_command('ruby', '../../src2md.rb', 'readme.src.md', 'readme.md')

View file

@ -1,84 +0,0 @@
/* Samples of GFile related objects */
/* Functions here has "sgf" prefix. Sgf is an acronym of "Sample of GFile". */
/* List a file */
/* This program shows how to read lines from a file. */
#include <gio/gio.h>
#include <glib/gprintf.h>
void
usage () {
g_printerr ("Usage: readline file\n");
exit (1);
}
/* Inputstream objects */
/* GInputStream */
/* +----- GFileInputStream */
/* +--+-- GFilterInputStream */
/* +--+-- GBufferedInputStream */
/* +----- GDataInputStream */
GDataInputStream *
sgf_open (GFile *file) {
g_return_val_if_fail (G_IS_FILE (file), NULL);
GFileInputStream *stream;
GDataInputStream *dstream;
GError *err;
if (! (stream = g_file_read (file, NULL, &err))) {
g_warning ("%s\n", err->message);
return NULL;
}
/* 'dstream' is a different instance from 'stream' instance. */
/* 'dstream' has "base-stream" property, which is a property of GFilterInputStream, and the property points 'stream'. */
/* GBufferedInputStream uses base-stream to read (input) data from its source. */
/* Therefore, 'dstream' reads a line through the IO interface of 'stream'. */
dstream = g_data_input_stream_new (G_INPUT_STREAM (stream));
return dstream;
}
char *
sgf_readline (GDataInputStream *dstream) {
char *contents;
gsize length;
GError *err = NULL;
contents = g_data_input_stream_read_line_utf8 (dstream, &length, NULL, &err);
if (! contents && err) {
g_warning ("%s\n", err->message);
return NULL;
}
return contents; /* if contents == NULL, then it is EOF */
}
void
sgf_close (GDataInputStream *dstream) {
GError *err = NULL;
/* At the same time dstream closes, its base stream (GFileInputStream created by 'sgf_open') is also closed. */
/* Because the default of "close-base-stream" property (of GFilterInputStream which is an ancester of GDataInputStream) is TRUE */
if (! g_input_stream_close (G_INPUT_STREAM (dstream), NULL, &err))
g_warning ("%s\n", err->message);
}
/* ----- main ----- */
int
main (int argc, char **argv) {
GFile *file;
GDataInputStream *dstream;
char *line;
if (argc != 2)
usage ();
file = g_file_new_for_commandline_arg (argv[1]);
if (! (dstream = sgf_open (file)))
return -1; /* error */
while (line = sgf_readline (dstream))
g_printf ("%s\n", line);
sgf_close (dstream);
return 0;
}

View file

@ -1,213 +0,0 @@
# What are the files in this directory?
They are examples to show how to use GFile related objects such as GInputStream and GOutPutStream.
- `meson.build` is used by meson.
- `readline.c` is an example to show how to read lines from a file.
- `copy.c` is an example to show read and write stream functions with Gio objects.
- `gfile_object.c` shows the object implements GFile interface.
## Compilation
Type as follows on your command line.
1. `meson _build`
2. `ninja -C _build`
Then executable files `readline`, `copy` and `gfile_object` are created under `_build` directory.
## Execution
Readline prints the contents of a file.
The file is given to `readline` as an argument.
~~~
$ _build/readline readline.c
/* Samples of GFile related objects */
/* Functions here has "sgf" prefix. Sgf is an acronym of "Sample of GFile". */
/* List a file */
/* This program shows how to read lines from a file. */
#include <gio/gio.h>
void
usage () {
g_printerr ("Usage: readline file\n");
exit (1);
}
... ... ...
... ... ...
~~~
Readline can also prints a file through the internet.
~~~
$ _build/readline http://www7b.biglobe.ne.jp/~j87107/SINU/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
... ... ...
... ... ...
~~~
This is because GFile can be constructed for local path or uri.
Copy is given two arguments.
The first one is a source filename and the second one is a destination filename.
If the destination file is exist, it will be overwritten.
Before the overwriting, the destination file is backed up.
The backup file name is appended with tilde ('~').
~~~
$ ls
_build copy.c meson.build readline.c readme.md
$ _build/copy copy.c sample.txt
$ ls
_build copy.c meson.build readline.c readme.md sample.txt
$ diff copy.c sample.txt
$ _build/copy readline.c sample.txt
$ ls
_build copy.c meson.build readline.c readme.md sample.txt sample.txt~
$ diff readline.c sample.txt
$ diff copy.c sample.txt~
$
~~~
Files can be on the internet.
~~~
$ _build/copy http://www7b.biglobe.ne.jp/~j87107/SINU/Algebra.pdf algebra.pdf
$ ls
_build algebra.pdf copy.c meson.build readline.c readme.md sample.txt sample.txt~
~~~
Gfile_object shows the object implements GFile interface.
~~~
$ _build/gfile_object gfile_object.c
GLocalFile
GObject
$ _build/gfile_object http://www7b.biglobe.ne.jp/~j87107/SINU/Algebra.pdf
GDaemonFile
GObject
~~~
`g_file_new_for_path` creates GLocalFile object which implements GFile interface.
And it returns the pointer to the object as `GFile *`.
`g_file_new_for_uri` creates GDaemonFile object which implements GFile interface.
And it returns the pointer to the object as `GFile *`.
## GFile interface
GFile is an interface.
It is like a pathname.
If you want to read from a file or write to a file, you need to create a stream object.
`copy.c` creates GInputStream and GOutputStream to copy a file.
~~~C
1 /* Samples of GFile related objects */
2 /* Copy file program */
3 /* This function uses 'g_input_stream_read' and 'g_output_stream_write'. */
4
5 #include <gio/gio.h>
6
7 void
8 usage () {
9 g_printerr ("Usage: copy file1 file2\n");
10 exit (1);
11 }
12
13 #define MAXBUF 4096
14 int
15 main (int argc, char **argv) {
16 GFile *file1, *file2;
17 GFileInputStream *istream;
18 GFileOutputStream *ostream;
19 char buf[MAXBUF];
20 gssize size;
21 GError *err = NULL;
22 int status = 0;
23
24 if (argc != 3)
25 usage();
26 file1 = g_file_new_for_commandline_arg (argv[1]);
27 file2 = g_file_new_for_commandline_arg (argv[2]);
28 if (! (istream = g_file_read (file1, NULL, &err))) {
29 g_warning ("%s\n", err->message);
30 return 1;
31 }
32 if (! (ostream = g_file_replace (file2, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &err))) {
33 g_warning ("%s\n", err->message);
34 return 1;
35 }
36 while ((size = g_input_stream_read (G_INPUT_STREAM (istream), buf, MAXBUF, NULL, &err)) > 0) {
37 if ((size = g_output_stream_write (G_OUTPUT_STREAM (ostream), buf, (gsize) size, NULL, &err)) < 0)
38 break;
39 }
40 if (size < 0) {
41 g_warning ("%s\n", err->message);
42 status = 1;
43 }
44 if (! (g_input_stream_close (G_INPUT_STREAM (istream), NULL, &err))) {
45 g_warning ("%s\n", err->message);
46 status = 1;
47 }
48 if (! (g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, &err))) {
49 g_warning ("%s\n", err->message);
50 status = 1;
51 }
52 return status;
53 }
54
~~~
## Stream objects
Stream objects have hierarchy.
~~~
GInputStream
+--+-- GFilterInputStream
| +--+-- GBufferedInputStream
| | +----- GDataInputStream
| +----- GConverterInputStream
+----- GFileInputStream
+----- GMemoryInputStream
+----- GUnixInputStream
~~~
GInputStream is the top parent object.
It is an abstract object.
Streams are created with specific objects such as files, memories and unix file descriptors.
For example, the following function creates a GFileInputStream.
~~~C
GFileInputStream *istream = g_file_read (GFile *file, GCancellable *cancellable, GError **error);
~~~
This stream is a child of GInputStream object, so you can apply any functions of GInputStream for `istream`.
For example,
~~~C
gssize size = g_input_stream_read (G_INPUT_STREAM (istream), void *buffer, gsize count, GCancellable *cancellable, GError **error);
~~~
This function reads data from `istream` and puts them into `buffer`.
GDataInputStream is used often.
It can read structured data such as sized data (byte, int16, int32 and int64) and lines (new line terminated string).
For example,
~~~C
GDataInputStream *dstream = g_data_input_stream_new (G_INPUT_STREAM (istream));
char *line = g_data_input_stream_read_line_utf8 (dstream, gsize *size, GCancellable *cancellable, GError **error);
~~~
The program above reads a line from `dstream`.

View file

@ -1,160 +0,0 @@
# What are the files in this directory?
They are examples to show how to use GFile related objects such as GInputStream and GOutPutStream.
- `meson.build` is used by meson.
- `readline.c` is an example to show how to read lines from a file.
- `copy.c` is an example to show read and write stream functions with Gio objects.
- `gfile_object.c` shows the object implements GFile interface.
## Compilation
Type as follows on your command line.
1. `meson _build`
2. `ninja -C _build`
Then executable files `readline`, `copy` and `gfile_object` are created under `_build` directory.
## Execution
Readline prints the contents of a file.
The file is given to `readline` as an argument.
~~~
$ _build/readline readline.c
/* Samples of GFile related objects */
/* Functions here has "sgf" prefix. Sgf is an acronym of "Sample of GFile". */
/* List a file */
/* This program shows how to read lines from a file. */
#include <gio/gio.h>
void
usage () {
g_printerr ("Usage: readline file\n");
exit (1);
}
... ... ...
... ... ...
~~~
Readline can also prints a file through the internet.
~~~
$ _build/readline http://www7b.biglobe.ne.jp/~j87107/SINU/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
... ... ...
... ... ...
~~~
This is because GFile can be constructed for local path or uri.
Copy is given two arguments.
The first one is a source filename and the second one is a destination filename.
If the destination file is exist, it will be overwritten.
Before the overwriting, the destination file is backed up.
The backup file name is appended with tilde ('~').
~~~
$ ls
_build copy.c meson.build readline.c readme.md
$ _build/copy copy.c sample.txt
$ ls
_build copy.c meson.build readline.c readme.md sample.txt
$ diff copy.c sample.txt
$ _build/copy readline.c sample.txt
$ ls
_build copy.c meson.build readline.c readme.md sample.txt sample.txt~
$ diff readline.c sample.txt
$ diff copy.c sample.txt~
$
~~~
Files can be on the internet.
~~~
$ _build/copy http://www7b.biglobe.ne.jp/~j87107/SINU/Algebra.pdf algebra.pdf
$ ls
_build algebra.pdf copy.c meson.build readline.c readme.md sample.txt sample.txt~
~~~
Gfile_object shows the object implements GFile interface.
~~~
$ _build/gfile_object gfile_object.c
GLocalFile
GObject
$ _build/gfile_object http://www7b.biglobe.ne.jp/~j87107/SINU/Algebra.pdf
GDaemonFile
GObject
~~~
`g_file_new_for_path` creates GLocalFile object which implements GFile interface.
And it returns the pointer to the object as `GFile *`.
`g_file_new_for_uri` creates GDaemonFile object which implements GFile interface.
And it returns the pointer to the object as `GFile *`.
## GFile interface
GFile is an interface.
It is like a pathname.
If you want to read from a file or write to a file, you need to create a stream object.
`copy.c` creates GInputStream and GOutputStream to copy a file.
@@@include
copy.c
@@@
## Stream objects
Stream objects have hierarchy.
~~~
GInputStream
+--+-- GFilterInputStream
| +--+-- GBufferedInputStream
| | +----- GDataInputStream
| +----- GConverterInputStream
+----- GFileInputStream
+----- GMemoryInputStream
+----- GUnixInputStream
~~~
GInputStream is the top parent object.
It is an abstract object.
Streams are created with specific objects such as files, memories and unix file descriptors.
For example, the following function creates a GFileInputStream.
~~~C
GFileInputStream *istream = g_file_read (GFile *file, GCancellable *cancellable, GError **error);
~~~
This stream is a child of GInputStream object, so you can apply any functions of GInputStream for `istream`.
For example,
~~~C
gssize size = g_input_stream_read (G_INPUT_STREAM (istream), void *buffer, gsize count, GCancellable *cancellable, GError **error);
~~~
This function reads data from `istream` and puts them into `buffer`.
GDataInputStream is used often.
It can read structured data such as sized data (byte, int16, int32 and int64) and lines (new line terminated string).
For example,
~~~C
GDataInputStream *dstream = g_data_input_stream_new (G_INPUT_STREAM (istream));
char *line = g_data_input_stream_read_line_utf8 (dstream, gsize *size, GCancellable *cancellable, GError **error);
~~~
The program above reads a line from `dstream`.

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 23](sec23.md)
Up: [Readme.md](../Readme.md), Prev: [Section 23](sec23.md), Next: [Section 25](sec25.md)
# GtkListView
@ -150,7 +150,7 @@ GtkNoSelection is used, so user can't select any item.
30
31 /* ----- activate, open, startup handlers ----- */
32 static void
33 tfe_activate (GApplication *application) {
33 app_activate (GApplication *application) {
34 GtkApplication *app = GTK_APPLICATION (application);
35 GtkWidget *win = gtk_application_window_new (app);
36 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -175,7 +175,7 @@ GtkNoSelection is used, so user can't select any item.
55 }
56
57 static void
58 tfe_startup (GApplication *application) {
58 app_startup (GApplication *application) {
59 }
60
61 /* ----- main ----- */
@ -186,8 +186,8 @@ GtkNoSelection is used, so user can't select any item.
66
67 app = gtk_application_new ("com.github.ToshioCP.list1", G_APPLICATION_FLAGS_NONE);
68
69 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
70 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
69 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
70 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
71
72 stat =g_application_run (G_APPLICATION (app), argc, argv);
73 g_object_unref (app);
@ -260,7 +260,7 @@ Its name is `list2.c` and located under [src/misc](../src/misc) directory.
2
3 /* ----- activate, open, startup handlers ----- */
4 static void
5 tfe_activate (GApplication *application) {
5 app_activate (GApplication *application) {
6 GtkApplication *app = GTK_APPLICATION (application);
7 GtkWidget *win = gtk_application_window_new (app);
8 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -297,7 +297,7 @@ Its name is `list2.c` and located under [src/misc](../src/misc) directory.
39 }
40
41 static void
42 tfe_startup (GApplication *application) {
42 app_startup (GApplication *application) {
43 }
44
45 /* ----- main ----- */
@ -308,8 +308,8 @@ Its name is `list2.c` and located under [src/misc](../src/misc) directory.
50
51 app = gtk_application_new ("com.github.ToshioCP.list2", G_APPLICATION_FLAGS_NONE);
52
53 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
54 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
53 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
54 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
55
56 stat =g_application_run (G_APPLICATION (app), argc, argv);
57 g_object_unref (app);
@ -424,7 +424,7 @@ The program is located in [src/misc](../src/misc) directory.
10
11 /* ----- activate, open, startup handlers ----- */
12 static void
13 tfe_activate (GApplication *application) {
13 app_activate (GApplication *application) {
14 GtkApplication *app = GTK_APPLICATION (application);
15 GtkWidget *win = gtk_application_window_new (app);
16 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -455,31 +455,30 @@ The program is located in [src/misc](../src/misc) directory.
41 GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
42
43 GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
44 gtk_list_view_set_enable_rubberband (GTK_LIST_VIEW (lv), TRUE);
45 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
46 gtk_widget_show (win);
47 }
48
49 static void
50 tfe_startup (GApplication *application) {
51 }
52
53 /* ----- main ----- */
54 int
55 main (int argc, char **argv) {
56 GtkApplication *app;
57 int stat;
58
59 app = gtk_application_new ("com.github.ToshioCP.list2", G_APPLICATION_FLAGS_NONE);
60
61 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
62 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
63
64 stat =g_application_run (G_APPLICATION (app), argc, argv);
65 g_object_unref (app);
66 return stat;
67 }
68
44 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
45 gtk_widget_show (win);
46 }
47
48 static void
49 app_startup (GApplication *application) {
50 }
51
52 /* ----- main ----- */
53 int
54 main (int argc, char **argv) {
55 GtkApplication *app;
56 int stat;
57
58 app = gtk_application_new ("com.github.ToshioCP.list3", G_APPLICATION_FLAGS_NONE);
59
60 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
61 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
62
63 stat =g_application_run (G_APPLICATION (app), argc, argv);
64 g_object_unref (app);
65 return stat;
66 }
67
~~~
Compile and execute it.
@ -493,4 +492,4 @@ $ ./a.out
![screenshot list3](../image/list3.png)
Up: [Readme.md](../Readme.md), Prev: [Section 23](sec23.md)
Up: [Readme.md](../Readme.md), Prev: [Section 23](sec23.md), Next: [Section 25](sec25.md)

564
gfm/sec25.md Normal file
View file

@ -0,0 +1,564 @@
Up: [Readme.md](../Readme.md), Prev: [Section 24](sec24.md)
# GtkGridView and activate signal
GtkGridView is similar to GtkListView.
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.
In this section, let's make a very simple file browser `list4`.
It just shows the files in the current directory.
And a user can choose list or grid by clicking on buttons in the tool bar.
Each item in the list or grid has an icon and a filename.
In addition, `list4` provides the way to open the `tfe` text editor to show a text file.
A user can do that by double clicking on an item or pressing enter key when an item is selected.
## GtkDirectoryList
GtkDirectoryList implements GListModel and it contains information of files in a certain directory.
The items of the list are GFileInfo objects.
In the `list4` source files, GtkDirectoryList is described in a ui file and built by GtkBuilder.
It is referenced to by a GtkSingleSelection model and the GtkSingleSelection model is referenced to by a GListView or GGridView object.
~~~
GtkListView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
~~~
![DirectoryList](../image/directorylist.png)
The part of the ui file `list4.ui` is as follows.
~~~xml
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
</object>
~~~
GtkDirectoryList has an "attributes" property.
It is attributes of GFileInfo such as "standard::name", "standard::icon" and "standard::content-type".
- 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.
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.
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.
## 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.
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <object class="GtkApplicationWindow" id="win">
4 <property name="title">file list</property>
5 <property name="default-width">600</property>
6 <property name="default-height">400</property>
7 <child>
8 <object class="GtkBox" id="boxv">
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <child>
11 <object class="GtkBox" id="boxh">
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <child>
14 <object class="GtkLabel" id="dmy1">
15 <property name="hexpand">TRUE</property>
16 </object>
17 </child>
18 <child>
19 <object class="GtkButton" id="btnlist">
20 <property name="name">btnlist</property>
21 <property name="action-name">win.view</property>
22 <property name="action-target">&apos;list&apos;</property>
23 <child>
24 <object class="GtkImage">
25 <property name="resource">/com/github/ToshioCP/list4/list.png</property>
26 </object>
27 </child>
28 </object>
29 </child>
30 <child>
31 <object class="GtkButton" id="btngrid">
32 <property name="name">btngrid</property>
33 <property name="action-name">win.view</property>
34 <property name="action-target">&apos;grid&apos;</property>
35 <child>
36 <object class="GtkImage">
37 <property name="resource">/com/github/ToshioCP/list4/grid.png</property>
38 </object>
39 </child>
40 </object>
41 </child>
42 <child>
43 <object class="GtkLabel" id="dmy2">
44 <property name="width-chars">10</property>
45 </object>
46 </child>
47 </object>
48 </child>
49 <child>
50 <object class="GtkScrolledWindow" id="scr">
51 <property name="hexpand">TRUE</property>
52 <property name="vexpand">TRUE</property>
53 </object>
54 </child>
55 </object>
56 </child>
57 </object>
58 <object class="GtkListView" id="list">
59 <property name="model">
60 <object class="GtkSingleSelection" id="singleselection">
61 <property name="model">
62 <object class="GtkDirectoryList" id="directorylist">
63 <property name="attributes">standard::name,standard::icon,standard::content-type</property>
64 </object>
65 </property>
66 </object>
67 </property>
68 </object>
69 <object class="GtkGridView" id="grid">
70 <property name="model">singleselection</property>
71 </object>
72 </interface>
73
~~~
The file consists of two parts.
The first part begins at the third line and ends at the 57th line.
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.
This is 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`.
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.
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 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.
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.
The action name is "win.view" and the target is "list".
Generally, a target is GVariant, which can be string, integer, float and so on.
You need to use GVariant text format to write GVariant value in ui files.
If the type of the GVarinat value is string, then the value with GVariant text format is bounded by single quotes or double quotes.
Because ui file is xml format text, single quote cannot be written without escape.
Its escape sequence is \&apos;.
Therefore, the target 'list' is written as \&apos;list\&apos;.
Because the button is connected to the action, "clicked" signal handler isn't needed.
- 23-27: The child widget of the button is GtkImage.
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.
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));
~~~
The signal handler `view_activated` will be explained later.
## Factories
Each view (GtkListView and GtkGridView) has its own factory because its items have different structure of widgets.
The factories are GtkBuilderListItemFactory objects.
Their ui files are as follows.
factory_list.ui
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <template class="GtkListItem">
4 <property name="child">
5 <object class="GtkBox">
6 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
7 <property name="spacing">20</property>
8 <child>
9 <object class="GtkImage">
10 <binding name="gicon">
11 <closure type="GIcon" function="get_icon">
12 <lookup name="item">GtkListItem</lookup>
13 </closure>
14 </binding>
15 </object>
16 </child>
17 <child>
18 <object class="GtkLabel">
19 <property name="hexpand">TRUE</property>
20 <property name="xalign">0</property>
21 <binding name="label">
22 <closure type="gchararray" function="get_file_name">
23 <lookup name="item">GtkListItem</lookup>
24 </closure>
25 </binding>
26 </object>
27 </child>
28 </object>
29 </property>
30 </template>
31 </interface>
32
~~~
factory_grid.ui
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <template class="GtkListItem">
4 <property name="child">
5 <object class="GtkBox">
6 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
7 <property name="spacing">20</property>
8 <child>
9 <object class="GtkImage">
10 <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
11 <binding name="gicon">
12 <closure type="GIcon" function="get_icon">
13 <lookup name="item">GtkListItem</lookup>
14 </closure>
15 </binding>
16 </object>
17 </child>
18 <child>
19 <object class="GtkLabel">
20 <property name="hexpand">TRUE</property>
21 <property name="xalign">0.5</property>
22 <binding name="label">
23 <closure type="gchararray" function="get_file_name">
24 <lookup name="item">GtkListItem</lookup>
25 </closure>
26 </binding>
27 </object>
28 </child>
29 </object>
30 </property>
31 </template>
32 </interface>
33
~~~
The two files above are almost same.
The difference is:
- The orientation of the box
- The icon size
- The position of the text of the label
~~~
$ cd list4; diff factory_list.ui factory_grid.ui
6c6
< <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
---
> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9a10
> <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
20c21
< <property name="xalign">0</property>
---
> <property name="xalign">0.5</property>
~~~
Each view item has two properties, "gicon" in GtkImage and "label" in GtkLabel.
Because GFileInfo doesn't have properties correspond to icon object 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.
~~~C
1 GIcon *
2 get_icon (GtkListItem *item, GFileInfo *info) {
3 GIcon *icon;
4
5 if (! G_IS_FILE_INFO (info))
6 return NULL;
7 else {
8 icon = g_file_info_get_icon (info);
9 g_object_ref (icon);
10 return icon;
11 }
12 }
13
14 char *
15 get_file_name (GtkListItem *item, GFileInfo *info) {
16 if (! G_IS_FILE_INFO (info))
17 return NULL;
18 else
19 return g_strdup (g_file_info_get_name (info));
20 }
~~~
One important thing is view items own the object 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.
## An activate signal handler of the action
An activate signal handler `view_activate` switches the view.
It does two things.
- Change the child widget of GtkScrolledWindow.
- Change the CSS of buttons to show the current state.
~~~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 }
~~~
The second parameter of this handler is the target of the clicked button.
Its type is GVariant.
- If `btnlist` has been clicked, then `parameter` is "list".
- If `btngrid` has been clicked, then `parameter` is "grid".
The third parameter `user_data` points GtkScrolledWindow, which is set in the `g_signal_connect` function.
- 4: `g_variant_get_string` gets the string from the GVariant variable.
- 8-14: Sets the child of `scr`.
- 15-17: Sets the CSS of 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.
## Activate signal of 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.
You can do anything you like by connecting the "activate" signal to the handler.
The example `list4` launches `tfe` text file editor if the item of the list is a text file.
~~~C
static void
list_activate (GtkListView *list, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position));
launch_tfe_with_file (info);
}
static void
grid_activate (GtkGridView *grid, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_grid_view_get_model (grid)), position));
launch_tfe_with_file (info);
}
... ...
... ...
g_signal_connect (GTK_LIST_VIEW (list), "activate", G_CALLBACK (list_activate), NULL);
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 list.
So you can get the item with `g_list_model_get_item` function.
## Content type and launching an application
The function `launch_tfe_with_file` launches `tfe` with the file whose information has been taken by GFileInfo object, if the file is a text file.
GFileInfo has information about file type.
The file type is like "text/plain", "text/x-csrc" and so on.
It is called content type.
Content type can be got with `g_file_info_get_content_type` function.
~~~C
1 static void
2 launch_tfe_with_file (GFileInfo *info) {
3 GError *err = NULL;
4 GFile *file;
5 GList *files = NULL;
6 const char *content_type;
7 const char *text_type = "text/";
8 GAppInfo *appinfo;
9 int i;
10
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 return;
25 }
26 err = NULL;
27 file = g_file_new_for_path (g_file_info_get_name (info));
28 files = g_list_append (files, file);
29 if (! (g_app_info_launch (appinfo, files, NULL, &err))) {
30 g_printerr ("%s\n", err->message);
31 }
32 g_list_free_full (files, g_object_unref);
33 g_object_unref (appinfo);
34 }
~~~
- 13: Gets the content type of GFileInfo.
- 14: Prints the content type.
This is only useful to know a content type of a file.
- 17-20: If the content type doesn't begin with "text/", then it returns.
- 21: Creates GAppInfo object of `tfe` application.
GAppInfo is interface and the variable `appinfo` points a GDesktopAppInfo object.
GAppInfo is a collection of information of an application.
- 29: 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 object 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 GError.
- 32: `g_list_free_full` frees the memories used by the list and items.
At present, my ubuntu runs on Gtk3, not Gtk4.
If it runs on Gtk4, 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.
## Compilation and execution
The source files are located in [src/list4](../src/list4) directory.
To compile and execute list4, type as follows.
~~~
$ cd list4 # or cd src/list4. It depends your current directory.
$ meson _build
$ ninja -C _build
$ _build/list3
~~~
Then a file list appears as a list style.
Click on a button on the tool bar so that you can change the style to grid or back to list.
Double click "list4.c" item, then `tfe` text editor runs with the argument "list4.c".
The following is the screenshot.
![Screenshot](../image/screenshot_list4.png)
## "gbytes" property of GtkBuilderListItemFactory
GtkBuilderListItemFactory has "gbytes" property.
The property contains a byte sequence of ui data.
If you use this property, you can put the contents of `factory_list.ui` and `factory_grid.ui`into `list4.ui`.
The following shows a part of the new list4.ui file.
~~~xml
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
~~~
CDATA section begins with "<![CDATA[" and ends with "]]>".
The contents of CDATA section is recognized as a string.
Any character, even if it is a key syntax marker such as '<' or '>', is recognized literally.
Therefore, the text between "<![CDATA[" and "]]>" is inserted to "bytes" property as it is.
This method decreases the number of ui files.
But, the new ui file is a bit complicated especially for the beginners.
If you feel some difficulty, it is better for you to separate the ui file.
A directory [src/list5](../src/list5) includes the ui file above.
Up: [Readme.md](../Readme.md), Prev: [Section 24](sec24.md)

BIN
image/directorylist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
image/directorylist.xcf Normal file

Binary file not shown.

BIN
image/list4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
image/screenshot_list4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

33
src/list4/factory_grid.ui Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<property name="icon-size">GTK_ICON_SIZE_LARGE</property>
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0.5</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>

32
src/list4/factory_list.ui Normal file
View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>

BIN
src/list4/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

BIN
src/list4/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

171
src/list4/list4.c Normal file
View file

@ -0,0 +1,171 @@
#include <gtk/gtk.h>
#include <string.h>
static GtkWidget *list;
static GtkWidget *grid;
static GdkDisplay *display;
static GtkCssProvider *provider;
static gboolean
before_close (GtkWindow *win, gpointer user_data) {
g_object_unref (list);
g_object_unref (grid);
return FALSE;
}
static void
view_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
GtkScrolledWindow *scr = GTK_SCROLLED_WINDOW (user_data);
const char *view = g_variant_get_string (parameter, NULL);
const char *other;
char *css;
if (strcmp (view, "list") == 0) {
other = "grid";
gtk_scrolled_window_set_child (scr, list);
}else {
other = "list";
gtk_scrolled_window_set_child (scr, grid);
}
css = g_strdup_printf ("button#btn%s {background: silver;} button#btn%s {background: white;}", view, other);
gtk_css_provider_load_from_data (provider, css, -1);
g_free (css);
g_action_change_state (G_ACTION (action), parameter);
}
static void
launch_tfe_with_file (GFileInfo *info) {
GError *err = NULL;
GFile *file;
GList *files = NULL;
const char *content_type;
const char *text_type = "text/";
GAppInfo *appinfo;
int i;
if (! info)
return;
content_type = g_file_info_get_content_type (info);
g_print ("%s\n", content_type); /* This line can be commented out if unnecessary */
if (! content_type)
return;
for (i=0;i<5;++i) {
if (content_type[i] != text_type[i])
return;
}
appinfo = g_app_info_create_from_commandline ("tfe", "tfe", G_APP_INFO_CREATE_NONE, &err);
if (err) {
g_printerr ("%s\n", err->message);
return;
}
err = NULL;
file = g_file_new_for_path (g_file_info_get_name (info));
files = g_list_append (files, file);
if (! (g_app_info_launch (appinfo, files, NULL, &err))) {
g_printerr ("%s\n", err->message);
}
g_list_free_full (files, g_object_unref);
g_object_unref (appinfo);
}
static void
list_activate (GtkListView *list, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position));
launch_tfe_with_file (info);
}
static void
grid_activate (GtkGridView *grid, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_grid_view_get_model (grid)), position));
launch_tfe_with_file (info);
}
GIcon *
get_icon (GtkListItem *item, GFileInfo *info) {
GIcon *icon;
if (! G_IS_FILE_INFO (info))
return NULL;
else {
icon = g_file_info_get_icon (info);
g_object_ref (icon);
return icon;
}
}
char *
get_file_name (GtkListItem *item, GFileInfo *info) {
if (! G_IS_FILE_INFO (info))
return NULL;
else
return g_strdup (g_file_info_get_name (info));
}
/* ----- activate, open, startup handlers ----- */
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GFile *file;
GSimpleAction *act_view;
GtkBuilder *build = gtk_builder_new_from_resource ("/com/github/ToshioCP/list4/list4.ui");
GtkWidget *win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
GtkWidget *scr = GTK_WIDGET (gtk_builder_get_object (build, "scr"));
list = GTK_WIDGET (gtk_builder_get_object (build, "list"));
grid = GTK_WIDGET (gtk_builder_get_object (build, "grid"));
GtkDirectoryList *directorylist = GTK_DIRECTORY_LIST (gtk_builder_get_object (build, "directorylist"));
g_object_ref (list);
g_object_ref (grid);
g_object_unref (build);
GtkListItemFactory *factory_list = gtk_builder_list_item_factory_new_from_resource (NULL, "/com/github/ToshioCP/list4/factory_list.ui");
GtkListItemFactory *factory_grid = gtk_builder_list_item_factory_new_from_resource (NULL, "/com/github/ToshioCP/list4/factory_grid.ui");
gtk_window_set_application (GTK_WINDOW (win), app);
/* First, 'list' is a child of scr. The child will be list or grid according to the clicked button, btnlist or btngrid. */
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), list);
file = g_file_new_for_path (".");
gtk_directory_list_set_file (directorylist, file);
g_object_unref (file);
gtk_list_view_set_factory (GTK_LIST_VIEW (list), factory_list);
gtk_grid_view_set_factory (GTK_GRID_VIEW (grid), factory_grid);
g_signal_connect (GTK_LIST_VIEW (list), "activate", G_CALLBACK (list_activate), NULL);
g_signal_connect (GTK_GRID_VIEW (grid), "activate", G_CALLBACK (grid_activate), NULL);
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);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view));
display = gtk_widget_get_display (GTK_WIDGET (win));
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "button#btnlist {background: silver;} button#btngrid {background: white;}", -1);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
g_signal_connect (GTK_WINDOW (win), "close-request", G_CALLBACK (before_close), NULL);
gtk_widget_show (win);
}
static void
app_startup (GApplication *application) {
}
#define APPLICATION_ID "com.github.ToshioCP.list4"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
/* g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);*/
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/list4">
<file>list4.ui</file>
<file>factory_list.ui</file>
<file>factory_grid.ui</file>
<file>list.png</file>
<file>grid.png</file>
</gresource>
</gresources>

73
src/list4/list4.ui Normal file
View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file list</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnlist">
<property name="name">btnlist</property>
<property name="action-name">win.view</property>
<property name="action-target">&apos;list&apos;</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/list.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="btngrid">
<property name="name">btngrid</property>
<property name="action-name">win.view</property>
<property name="action-target">&apos;grid&apos;</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/grid.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scr">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
</object>
</interface>

11
src/list4/meson.build Normal file
View file

@ -0,0 +1,11 @@
project('list4', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','list4.gresource.xml')
sourcefiles=files('list4.c')
executable('list4', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: false)

BIN
src/list5/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

BIN
src/list5/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

166
src/list5/list5.c Normal file
View file

@ -0,0 +1,166 @@
#include <gtk/gtk.h>
#include <string.h>
static GtkWidget *list;
static GtkWidget *grid;
static GdkDisplay *display;
static GtkCssProvider *provider;
static gboolean
before_close (GtkWindow *win, gpointer user_data) {
g_object_unref (list);
g_object_unref (grid);
return FALSE;
}
static void
view_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
GtkScrolledWindow *scr = GTK_SCROLLED_WINDOW (user_data);
const char *view = g_variant_get_string (parameter, NULL);
const char *other;
char *css;
if (strcmp (view, "list") == 0) {
other = "grid";
gtk_scrolled_window_set_child (scr, list);
}else {
other = "list";
gtk_scrolled_window_set_child (scr, grid);
}
css = g_strdup_printf ("button#btn%s {background: silver;} button#btn%s {background: white;}", view, other);
gtk_css_provider_load_from_data (provider, css, -1);
g_free (css);
g_action_change_state (G_ACTION (action), parameter);
}
static void
launch_tfe_with_file (GFileInfo *info) {
GError *err = NULL;
GFile *file;
GList *files = NULL;
const char *content_type;
const char *text_type = "text/";
GAppInfo *appinfo;
int i;
if (! info)
return;
content_type = g_file_info_get_content_type (info);
g_print ("%s\n", content_type); /* This line can be commented out if unnecessary */
if (! content_type)
return;
for (i=0;i<5;++i) {
if (content_type[i] != text_type[i])
return;
}
appinfo = g_app_info_create_from_commandline ("tfe", "tfe", G_APP_INFO_CREATE_NONE, &err);
if (err) {
g_printerr ("%s\n", err->message);
return;
}
err = NULL;
file = g_file_new_for_path (g_file_info_get_name (info));
files = g_list_append (files, file);
if (! (g_app_info_launch (appinfo, files, NULL, &err))) {
g_printerr ("%s\n", err->message);
}
g_list_free_full (files, g_object_unref);
g_object_unref (appinfo);
}
static void
list_activate (GtkListView *list, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position));
launch_tfe_with_file (info);
}
static void
grid_activate (GtkGridView *grid, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_grid_view_get_model (grid)), position));
launch_tfe_with_file (info);
}
GIcon *
get_icon (GtkListItem *item, GFileInfo *info) {
GIcon *icon;
if (! G_IS_FILE_INFO (info))
return NULL;
else {
icon = g_file_info_get_icon (info);
g_object_ref (icon);
return icon;
}
}
char *
get_file_name (GtkListItem *item, GFileInfo *info) {
if (! G_IS_FILE_INFO (info))
return NULL;
else
return g_strdup (g_file_info_get_name (info));
}
/* ----- activate, open, startup handlers ----- */
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GFile *file;
GSimpleAction *act_view;
GtkBuilder *build = gtk_builder_new_from_resource ("/com/github/ToshioCP/list5/list5.ui");
GtkWidget *win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
GtkWidget *scr = GTK_WIDGET (gtk_builder_get_object (build, "scr"));
list = GTK_WIDGET (gtk_builder_get_object (build, "list"));
grid = GTK_WIDGET (gtk_builder_get_object (build, "grid"));
GtkDirectoryList *directorylist = GTK_DIRECTORY_LIST (gtk_builder_get_object (build, "directorylist"));
g_object_ref (list);
g_object_ref (grid);
g_object_unref (build);
gtk_window_set_application (GTK_WINDOW (win), app);
/* First, 'list' is a child of scr. The child will be list or grid according to the clicked button, btnlist or btngrid. */
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), list);
file = g_file_new_for_path (".");
gtk_directory_list_set_file (directorylist, file);
g_object_unref (file);
g_signal_connect (GTK_LIST_VIEW (list), "activate", G_CALLBACK (list_activate), NULL);
g_signal_connect (GTK_GRID_VIEW (grid), "activate", G_CALLBACK (grid_activate), NULL);
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);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view));
display = gtk_widget_get_display (GTK_WIDGET (win));
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "button#btnlist {background: silver;} button#btngrid {background: white;}", -1);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
g_signal_connect (GTK_WINDOW (win), "close-request", G_CALLBACK (before_close), NULL);
gtk_widget_show (win);
}
static void
app_startup (GApplication *application) {
}
#define APPLICATION_ID "com.github.ToshioCP.list5"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
/* g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);*/
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/list5">
<file>list5.ui</file>
<file>list.png</file>
<file>grid.png</file>
</gresource>
</gresources>

148
src/list5/list5.ui Normal file
View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file list</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnlist">
<property name="name">btnlist</property>
<property name="action-name">win.view</property>
<property name="action-target">&apos;list&apos;</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/list.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="btngrid">
<property name="name">btngrid</property>
<property name="action-name">win.view</property>
<property name="action-target">&apos;grid&apos;</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/grid.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scr">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<property name="icon-size">GTK_ICON_SIZE_LARGE</property>
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0.5</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</interface>

11
src/list5/meson.build Normal file
View file

@ -0,0 +1,11 @@
project('list5', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','list5.gresource.xml')
sourcefiles=files('list5.c')
executable('list5', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: false)

View file

@ -30,7 +30,7 @@ teardown_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_d
/* ----- activate, open, startup handlers ----- */
static void
tfe_activate (GApplication *application) {
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -55,7 +55,7 @@ tfe_activate (GApplication *application) {
}
static void
tfe_startup (GApplication *application) {
app_startup (GApplication *application) {
}
/* ----- main ----- */
@ -66,8 +66,8 @@ main (int argc, char **argv) {
app = gtk_application_new ("com.github.ToshioCP.list1", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -2,7 +2,7 @@
/* ----- activate, open, startup handlers ----- */
static void
tfe_activate (GApplication *application) {
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -39,7 +39,7 @@ tfe_activate (GApplication *application) {
}
static void
tfe_startup (GApplication *application) {
app_startup (GApplication *application) {
}
/* ----- main ----- */
@ -50,8 +50,8 @@ main (int argc, char **argv) {
app = gtk_application_new ("com.github.ToshioCP.list2", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -10,7 +10,7 @@ get_file_name (GtkListItem *item, GFileInfo *info) {
/* ----- activate, open, startup handlers ----- */
static void
tfe_activate (GApplication *application) {
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
@ -41,13 +41,12 @@ tfe_activate (GApplication *application) {
GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
gtk_list_view_set_enable_rubberband (GTK_LIST_VIEW (lv), TRUE);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
gtk_widget_show (win);
}
static void
tfe_startup (GApplication *application) {
app_startup (GApplication *application) {
}
/* ----- main ----- */
@ -56,10 +55,10 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.list2", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.list3", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -303,5 +303,5 @@ $ comp list3
$ ./a.out
~~~
![screenshot list3](../image/list3.png){width=6.04cm height=4.41cm}
![screenshot list3](../image/list3.png){width=10cm height=7.3cm}

345
src/sec25.src.md Normal file
View file

@ -0,0 +1,345 @@
# GtkGridView and activate signal
GtkGridView is similar to GtkListView.
It displays a GListModel as a grid, which is like a square tessellation.
![Grid](../image/list4.png){width=10cm height=6.6cm}
This is often seen when you use a file browser like nautilus.
In this section, let's make a very simple file browser `list4`.
It just shows the files in the current directory.
And a user can choose list or grid by clicking on buttons in the tool bar.
Each item in the list or grid has an icon and a filename.
In addition, `list4` provides the way to open the `tfe` text editor to show a text file.
A user can do that by double clicking on an item or pressing enter key when an item is selected.
## GtkDirectoryList
GtkDirectoryList implements GListModel and it contains information of files in a certain directory.
The items of the list are GFileInfo objects.
In the `list4` source files, GtkDirectoryList is described in a ui file and built by GtkBuilder.
It is referenced to by a GtkSingleSelection model and the GtkSingleSelection model is referenced to by a GListView or GGridView object.
~~~
GtkListView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
~~~
![DirectoryList](../image/directorylist.png){width=10cm height=7.5cm}
The part of the ui file `list4.ui` is as follows.
~~~xml
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
</object>
~~~
GtkDirectoryList has an "attributes" property.
It is attributes of GFileInfo such as "standard::name", "standard::icon" and "standard::content-type".
- 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.
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.
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.
## 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.
@@@include
list4/list4.ui
@@@
The file consists of two parts.
The first part begins at the third line and ends at the 57th line.
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.
This is 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`.
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.
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 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.
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.
The action name is "win.view" and the target is "list".
Generally, a target is GVariant, which can be string, integer, float and so on.
You need to use GVariant text format to write GVariant value in ui files.
If the type of the GVarinat value is string, then the value with GVariant text format is bounded by single quotes or double quotes.
Because ui file is xml format text, single quote cannot be written without escape.
Its escape sequence is \&apos;.
Therefore, the target 'list' is written as \&apos;list\&apos;.
Because the button is connected to the action, "clicked" signal handler isn't needed.
- 23-27: The child widget of the button is GtkImage.
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.
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));
~~~
The signal handler `view_activated` will be explained later.
## Factories
Each view (GtkListView and GtkGridView) has its own factory because its items have different structure of widgets.
The factories are GtkBuilderListItemFactory objects.
Their ui files are as follows.
factory_list.ui
@@@include
list4/factory_list.ui
@@@
factory_grid.ui
@@@include
list4/factory_grid.ui
@@@
The two files above are almost same.
The difference is:
- The orientation of the box
- The icon size
- The position of the text of the label
@@@shell
cd list4; diff factory_list.ui factory_grid.ui
@@@
Each view item has two properties, "gicon" in GtkImage and "label" in GtkLabel.
Because GFileInfo doesn't have properties correspond to icon object 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.
@@@include
list4/list4.c get_icon get_file_name
@@@
One important thing is view items own the object 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.
## An activate signal handler of the action
An activate signal handler `view_activate` switches the view.
It does two things.
- Change the child widget of GtkScrolledWindow.
- Change the CSS of buttons to show the current state.
@@@include
list4/list4.c view_activated
@@@
The second parameter of this handler is the target of the clicked button.
Its type is GVariant.
- If `btnlist` has been clicked, then `parameter` is "list".
- If `btngrid` has been clicked, then `parameter` is "grid".
The third parameter `user_data` points GtkScrolledWindow, which is set in the `g_signal_connect` function.
- 4: `g_variant_get_string` gets the string from the GVariant variable.
- 8-14: Sets the child of `scr`.
- 15-17: Sets the CSS of 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.
## Activate signal of 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.
You can do anything you like by connecting the "activate" signal to the handler.
The example `list4` launches `tfe` text file editor if the item of the list is a text file.
~~~C
static void
list_activate (GtkListView *list, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position));
launch_tfe_with_file (info);
}
static void
grid_activate (GtkGridView *grid, int position, gpointer user_data) {
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_grid_view_get_model (grid)), position));
launch_tfe_with_file (info);
}
... ...
... ...
g_signal_connect (GTK_LIST_VIEW (list), "activate", G_CALLBACK (list_activate), NULL);
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 list.
So you can get the item with `g_list_model_get_item` function.
## Content type and launching an application
The function `launch_tfe_with_file` launches `tfe` with the file whose information has been taken by GFileInfo object, if the file is a text file.
GFileInfo has information about file type.
The file type is like "text/plain", "text/x-csrc" and so on.
It is called content type.
Content type can be got with `g_file_info_get_content_type` function.
@@@include
list4/list4.c launch_tfe_with_file
@@@
- 13: Gets the content type of GFileInfo.
- 14: Prints the content type.
This is only useful to know a content type of a file.
- 17-20: If the content type doesn't begin with "text/", then it returns.
- 21: Creates GAppInfo object of `tfe` application.
GAppInfo is interface and the variable `appinfo` points a GDesktopAppInfo object.
GAppInfo is a collection of information of an application.
- 29: 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 object 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 GError.
- 32: `g_list_free_full` frees the memories used by the list and items.
At present, my ubuntu runs on Gtk3, not Gtk4.
If it runs on Gtk4, 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.
## Compilation and execution
The source files are located in [src/list4](list4) directory.
To compile and execute list4, type as follows.
~~~
$ cd list4 # or cd src/list4. It depends your current directory.
$ meson _build
$ ninja -C _build
$ _build/list3
~~~
Then a file list appears as a list style.
Click on a button on the tool bar so that you can change the style to grid or back to list.
Double click "list4.c" item, then `tfe` text editor runs with the argument "list4.c".
The following is the screenshot.
![Screenshot](../image/screenshot_list4.png){width=8cm height=5.62cm}
## "gbytes" property of GtkBuilderListItemFactory
GtkBuilderListItemFactory has "gbytes" property.
The property contains a byte sequence of ui data.
If you use this property, you can put the contents of `factory_list.ui` and `factory_grid.ui`into `list4.ui`.
The following shows a part of the new list4.ui file.
~~~xml
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directorylist">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
~~~
CDATA section begins with "<![CDATA[" and ends with "]]>".
The contents of CDATA section is recognized as a string.
Any character, even if it is a key syntax marker such as '<' or '>', is recognized literally.
Therefore, the text between "<![CDATA[" and "]]>" is inserted to "bytes" property as it is.
This method decreases the number of ui files.
But, the new ui file is a bit complicated especially for the beginners.
If you feel some difficulty, it is better for you to separate the ui file.
A directory [src/list5](list5) includes the ui file above.