diff --git a/.gitignore b/.gitignore index e8bcf8b..bdace7f 100755 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/Readme.md b/Readme.md index 48c9fbb..a830571 100644 --- a/Readme.md +++ b/Readme.md @@ -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) diff --git a/doc/GFile_example/copy.c b/doc/GFile_example/copy.c deleted file mode 100644 index 60fd69c..0000000 --- a/doc/GFile_example/copy.c +++ /dev/null @@ -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 - -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; -} - diff --git a/doc/GFile_example/gfile_object.c b/doc/GFile_example/gfile_object.c deleted file mode 100644 index 931c000..0000000 --- a/doc/GFile_example/gfile_object.c +++ /dev/null @@ -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 - -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; -} - diff --git a/doc/GFile_example/meson.build b/doc/GFile_example/meson.build deleted file mode 100644 index a625269..0000000 --- a/doc/GFile_example/meson.build +++ /dev/null @@ -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') diff --git a/doc/GFile_example/readline.c b/doc/GFile_example/readline.c deleted file mode 100644 index 7ad9611..0000000 --- a/doc/GFile_example/readline.c +++ /dev/null @@ -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 -#include - -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; -} - diff --git a/doc/GFile_example/readme.md b/doc/GFile_example/readme.md deleted file mode 100644 index 5de3e78..0000000 --- a/doc/GFile_example/readme.md +++ /dev/null @@ -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 - -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 - - - - - - - ... ... ... - ... ... ... -~~~ - -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 - 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`. - diff --git a/doc/GFile_example/readme.src.md b/doc/GFile_example/readme.src.md deleted file mode 100644 index 97015a2..0000000 --- a/doc/GFile_example/readme.src.md +++ /dev/null @@ -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 - -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 - - - - - - - ... ... ... - ... ... ... -~~~ - -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`. - diff --git a/gfm/sec24.md b/gfm/sec24.md index c7f7084..23216c0 100644 --- a/gfm/sec24.md +++ b/gfm/sec24.md @@ -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) diff --git a/gfm/sec25.md b/gfm/sec25.md new file mode 100644 index 0000000..deb8274 --- /dev/null +++ b/gfm/sec25.md @@ -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 + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + singleselection + +~~~ + +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 + 2 + 3 + 4 file list + 5 600 + 6 400 + 7 + 8 + 9 GTK_ORIENTATION_VERTICAL +10 +11 +12 GTK_ORIENTATION_HORIZONTAL +13 +14 +15 TRUE +16 +17 +18 +19 +20 btnlist +21 win.view +22 'list' +23 +24 +25 /com/github/ToshioCP/list4/list.png +26 +27 +28 +29 +30 +31 +32 btngrid +33 win.view +34 'grid' +35 +36 +37 /com/github/ToshioCP/list4/grid.png +38 +39 +40 +41 +42 +43 +44 10 +45 +46 +47 +48 +49 +50 +51 TRUE +52 TRUE +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 standard::name,standard::icon,standard::content-type +64 +65 +66 +67 +68 +69 +70 singleselection +71 +72 +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 \'. +Therefore, the target 'list' is written as \'list\'. +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 + 2 + 3 +31 +32 +~~~ + +factory_grid.ui + +~~~xml + 1 + 2 + 3 +32 +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 +< GTK_ORIENTATION_HORIZONTAL +--- +> GTK_ORIENTATION_VERTICAL +9a10 +> GTK_ICON_SIZE_LARGE +20c21 +< 0 +--- +> 0.5 +~~~ + +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 + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + + + + + ]]> + + + +~~~ + +CDATA section begins 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 "" 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) diff --git a/image/directorylist.png b/image/directorylist.png new file mode 100644 index 0000000..6db3440 Binary files /dev/null and b/image/directorylist.png differ diff --git a/image/directorylist.xcf b/image/directorylist.xcf new file mode 100644 index 0000000..8658707 Binary files /dev/null and b/image/directorylist.xcf differ diff --git a/image/list4.png b/image/list4.png new file mode 100644 index 0000000..91c1d7a Binary files /dev/null and b/image/list4.png differ diff --git a/image/screenshot_list4.png b/image/screenshot_list4.png new file mode 100644 index 0000000..c162b25 Binary files /dev/null and b/image/screenshot_list4.png differ diff --git a/src/list4/factory_grid.ui b/src/list4/factory_grid.ui new file mode 100644 index 0000000..07fccd2 --- /dev/null +++ b/src/list4/factory_grid.ui @@ -0,0 +1,33 @@ + + + + + diff --git a/src/list4/factory_list.ui b/src/list4/factory_list.ui new file mode 100644 index 0000000..75387fc --- /dev/null +++ b/src/list4/factory_list.ui @@ -0,0 +1,32 @@ + + + + + diff --git a/src/list4/grid.png b/src/list4/grid.png new file mode 100644 index 0000000..46a0e8a Binary files /dev/null and b/src/list4/grid.png differ diff --git a/src/list4/list.png b/src/list4/list.png new file mode 100644 index 0000000..bc86b2d Binary files /dev/null and b/src/list4/list.png differ diff --git a/src/list4/list4.c b/src/list4/list4.c new file mode 100644 index 0000000..aaf6569 --- /dev/null +++ b/src/list4/list4.c @@ -0,0 +1,171 @@ +#include +#include + +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; +} + diff --git a/src/list4/list4.gresource.xml b/src/list4/list4.gresource.xml new file mode 100644 index 0000000..88843ac --- /dev/null +++ b/src/list4/list4.gresource.xml @@ -0,0 +1,10 @@ + + + + list4.ui + factory_list.ui + factory_grid.ui + list.png + grid.png + + diff --git a/src/list4/list4.ui b/src/list4/list4.ui new file mode 100644 index 0000000..f2d3843 --- /dev/null +++ b/src/list4/list4.ui @@ -0,0 +1,73 @@ + + + + file list + 600 + 400 + + + GTK_ORIENTATION_VERTICAL + + + GTK_ORIENTATION_HORIZONTAL + + + TRUE + + + + + btnlist + win.view + 'list' + + + /com/github/ToshioCP/list4/list.png + + + + + + + btngrid + win.view + 'grid' + + + /com/github/ToshioCP/list4/grid.png + + + + + + + 10 + + + + + + + TRUE + TRUE + + + + + + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + singleselection + + + diff --git a/src/list4/meson.build b/src/list4/meson.build new file mode 100644 index 0000000..1d56b1d --- /dev/null +++ b/src/list4/meson.build @@ -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) + diff --git a/src/list5/grid.png b/src/list5/grid.png new file mode 100644 index 0000000..46a0e8a Binary files /dev/null and b/src/list5/grid.png differ diff --git a/src/list5/list.png b/src/list5/list.png new file mode 100644 index 0000000..bc86b2d Binary files /dev/null and b/src/list5/list.png differ diff --git a/src/list5/list5.c b/src/list5/list5.c new file mode 100644 index 0000000..dc831b1 --- /dev/null +++ b/src/list5/list5.c @@ -0,0 +1,166 @@ +#include +#include + +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; +} + diff --git a/src/list5/list5.gresource.xml b/src/list5/list5.gresource.xml new file mode 100644 index 0000000..acb6df4 --- /dev/null +++ b/src/list5/list5.gresource.xml @@ -0,0 +1,8 @@ + + + + list5.ui + list.png + grid.png + + diff --git a/src/list5/list5.ui b/src/list5/list5.ui new file mode 100644 index 0000000..fb0b450 --- /dev/null +++ b/src/list5/list5.ui @@ -0,0 +1,148 @@ + + + + file list + 600 + 400 + + + GTK_ORIENTATION_VERTICAL + + + GTK_ORIENTATION_HORIZONTAL + + + TRUE + + + + + btnlist + win.view + 'list' + + + /com/github/ToshioCP/list4/list.png + + + + + + + btngrid + win.view + 'grid' + + + /com/github/ToshioCP/list4/grid.png + + + + + + + 10 + + + + + + + TRUE + TRUE + + + + + + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + + + + + ]]> + + + + + singleselection + + + + + + + ]]> + + + + + diff --git a/src/list5/meson.build b/src/list5/meson.build new file mode 100644 index 0000000..fe2eb31 --- /dev/null +++ b/src/list5/meson.build @@ -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) + diff --git a/src/misc/list1.c b/src/misc/list1.c index a38ec4d..788358e 100644 --- a/src/misc/list1.c +++ b/src/misc/list1.c @@ -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); diff --git a/src/misc/list2.c b/src/misc/list2.c index 54b9a80..2c99308 100644 --- a/src/misc/list2.c +++ b/src/misc/list2.c @@ -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); diff --git a/src/misc/list3.c b/src/misc/list3.c index 55dbe8f..b759765 100644 --- a/src/misc/list3.c +++ b/src/misc/list3.c @@ -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); diff --git a/src/sec24.src.md b/src/sec24.src.md index 616d678..8086fd4 100644 --- a/src/sec24.src.md +++ b/src/sec24.src.md @@ -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} diff --git a/src/sec25.src.md b/src/sec25.src.md new file mode 100644 index 0000000..e45b1ac --- /dev/null +++ b/src/sec25.src.md @@ -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 + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + singleselection + +~~~ + +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 \'. +Therefore, the target 'list' is written as \'list\'. +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 + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + + + + + ]]> + + + +~~~ + +CDATA section begins 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 "" 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. +