From 95320ca6fbb5f41a5c8ab2a808207a720714fe2b Mon Sep 17 00:00:00 2001 From: Toshio Sekiya Date: Tue, 31 Jan 2023 19:04:06 +0900 Subject: [PATCH] Section 30 is added. It is about GtkSignalListItemFactory. --- README.md | 1 + docs/image/listeditor.png | Bin 0 -> 23695 bytes docs/index.html | 1 + docs/sec21.html | 2 +- docs/sec29.html | 5 +- docs/sec30.html | 429 +++++++++++++++ gfm/sec21.md | 2 +- gfm/sec29.md | 4 +- gfm/sec30.md | 303 +++++++++++ image/listeditor.png | Bin 0 -> 23695 bytes src/listeditor/.vscode/c_cpp_properties.json | 17 + src/listeditor/listeditor.c | 530 +++++++++++++++++++ src/listeditor/listeditor.gresource.xml | 6 + src/listeditor/listeditor.ui | 133 +++++ src/listeditor/meson.build | 11 + src/sec30.src.md | 212 ++++++++ src/tfe7/tfeapplication.c | 2 +- 17 files changed, 1652 insertions(+), 6 deletions(-) create mode 100644 docs/image/listeditor.png create mode 100644 docs/sec30.html create mode 100644 gfm/sec30.md create mode 100644 image/listeditor.png create mode 100644 src/listeditor/.vscode/c_cpp_properties.json create mode 100644 src/listeditor/listeditor.c create mode 100644 src/listeditor/listeditor.gresource.xml create mode 100644 src/listeditor/listeditor.ui create mode 100644 src/listeditor/meson.build create mode 100644 src/sec30.src.md diff --git a/README.md b/README.md index 4adddce..5ff2ed3 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,4 @@ There is a document \("[How to build GTK 4 Tutorial](gfm/Readme_for_developers.m 1. [GtkGridView and activate signal](gfm/sec27.md) 1. [GtkExpression](gfm/sec28.md) 1. [GtkColumnView](gfm/sec29.md) +1. [GtkSignalListItemFactory](gfm/sec30.md) diff --git a/docs/image/listeditor.png b/docs/image/listeditor.png new file mode 100644 index 0000000000000000000000000000000000000000..7e99012f35084f4236a37cca08d025c23133a447 GIT binary patch literal 23695 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 bVs-qgzn1fsrLbwCHb~Ud)z4*}Q$iB}lYac` literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html index d101682..974d81a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -190,6 +190,7 @@ gsettings

  • GtkGridView and activate signal
  • GtkExpression
  • GtkColumnView
  • +
  • GtkSignalListItemFactory
  • This website uses Bootstrap.

    diff --git a/docs/sec21.html b/docs/sec21.html index 90f7851..7aaa74e 100644 --- a/docs/sec21.html +++ b/docs/sec21.html @@ -820,7 +820,7 @@ class="sourceCode numberSource C numberLines"> GtkCssProvider *provider; }; -G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION); +G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION) /* gsettings changed::font signal handler */ static void diff --git a/docs/sec29.html b/docs/sec29.html index 54942d5..c59651e 100644 --- a/docs/sec29.html +++ b/docs/sec29.html @@ -103,7 +103,10 @@ Prev: section28 - + + diff --git a/docs/sec30.html b/docs/sec30.html new file mode 100644 index 0000000..89cf420 --- /dev/null +++ b/docs/sec30.html @@ -0,0 +1,429 @@ + + + + + + + + GTK 4 tutorial + + + +
    + +

    GtkSignalListItemFactory

    +

    GtkSignalListItemFactory +and GtkBulderListItemFactory

    +

    GtkBuilderlistItemFactory is convenient when GtkListView just shows +the contents of a list. Its binding direction is always from an item of +a list to a child of GtkListItem.

    +

    When it comes to dynamic connection, it’s not enough. For example, +you want to edit the contents of a list. You set a child of GtkListItem +to a GtkText instance so a user can edit a text with it. You need to +bind an item in the list with the buffer of the GtkText. The direction +is opposite from the one with GtkBuilderListItemFactory. It is from the +GtkText instance to the item in the list. You can implement this with +GtkSignalListItemFactory, which is more flexible than +GtkBuilderListItemFactory.

    +

    Two things are shown in this section.

    +
      +
    • Binding from a child of a GtkListItem instance to an item of a +list.
    • +
    • Access a child of GtkListItem dynamically. This direction is the +same as the one with GtkBulderListItemFactory. But +GtkBulderListItemFactory uses GtkExpression from the item property of +the GtkListItem. So, it updates its child widget only when the item +property changes. In this example the child reflects the change in the +same item in the list dynamically.
    • +
    +

    This section shows just a part of the source file +listeditor.c. If you want to see the whole codes, see +src/listeditor directory of the Gtk4 tutorial +repository.

    +

    A list editor

    +

    The sample program is a list editor and data of the list are strings. +It’s the same as a line editor. It reads a text file line by line. Each +line is an item of the list. The list is displayed with GtkColumnView. +There are two columns. The one is a button, which makes the line be a +current line. If the line is the current line, the button is colored +with red. The other is a string which is the contents of the +corresponding item of the list.

    +
    +List editor + +
    +

    The source files are located at src/listeditor +directory. You can compile end execute it as follows.

    +
      +
    • Download the program from the repository.
    • +
    • Change your current directory to src/listeditor.
    • +
    • Type the following on your commandline.
    • +
    +
    $ meson _build
    +$ ninja -C _build
    +$ _build/listeditor
    +
      +
    • Append button: appends a line after the current line, or at the last +line if no current line exists.
    • +
    • Insert button: inserts a line before the current line.
    • +
    • Remove button: removes a current line.
    • +
    • Read button: reads a file.
    • +
    • Write button: writes the contents to a file.
    • +
    • close button: close the contents.
    • +
    • quit button: quit the application.
    • +
    • Button on the select column: makes the line current.
    • +
    • String column: GtkText. You can edit a string in the field.
    • +
    +

    The current line number (zero-based) is shown at the left of the tool +bar. The file name is shown at the right of the write button.

    +

    Connect a +GtkText instance and an item in the list

    +

    The second column (GtkColumnViewColumn) sets its factory property to +GtkSignalListItemFactory. It uses three signals setup, bind and unbind. +The following is their sgnal handlers.

    +
    static void
    +setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  GtkWidget *text = gtk_text_new ();
    +  gtk_list_item_set_child (listitem, GTK_WIDGET (text));
    +}
    +
    +static void
    +bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  GtkWidget *text = gtk_list_item_get_child (listitem);
    +  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    +  GtkEntryBuffer *buffer;
    +  GBinding *bind;
    +
    +  buffer = gtk_text_get_buffer (GTK_TEXT (text));
    +  gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1);
    +  bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT);
    +  g_object_set_data (G_OBJECT (listitem), "bind", bind);
    +}
    +
    +static void
    +unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind"));
    +
    +  g_binding_unbind(bind);
    +  g_object_set_data (G_OBJECT (listitem), "bind", NULL);
    +}
    +
      +
    • 1-5: setup2_cb is a setup signal handler on the +GtkSignalListItemFactory. This factory is inserted to the factory +property of the second GtkColumnViewColumn. Te handler just creates a +GtkText instance and sets the child of listitem to it. The +instance will be destroyed automatically when the listitem +is destroyed. So, teardown signal handler isn’t necessary.
    • +
    • 7-18: bind2_cb is a bind signal handler. It is called +when the listitem is bound to an item in the list. The list +items are LeData instances. LeData is defined in the file +listeditor.c (the C source file of the list editor). It is +a child class of GObject and has two data. The one is +listitem which points a GtkListItem instance when they are +connected. If no GtkListItem is connected, it is NULL. The other is +string which is a content of the line. +
        +
      • 9-10: text is a child of the listitem and +it is a GtkText instance. data is an item pointed by the +listitem. It is a LeData instance.
      • +
      • 14: Gets the buffer of the text.
      • +
      • 15: Sets the text of the buffer to +le_data_look_string (data). le_data_look_string returns the +string of the data and the ownership of the string is taken by the +data. So, the caller don’t need to free the string.
      • +
      • 16: g_object_bind_property binds a property and another +object property. This line binds the “text” property of the +buffer (source) and the “string” property of the +data (destination). It is a uni-directional binding +(G_BINDING_DEFAULT). When a user changes the GtkText text, +the same string is immediately put into the data. The +function returns a GBinding instance. This binding is different from +bindings of GtkExpression. This binding needs the existence of the two +properties.
      • +
      • 17: GObjec has a table. The key is a string (or GQuark) and the +value is a gpointer (pointer to any type). The function +g_object_set_data sets the association from the key to the +value. This line sets the association from “bind” to bind +instance on the listitem instance. It makes possible for +the “unbind” handler to get the bind instance.
      • +
    • +
    • 20-26: unbind2_cb is a unbind signal handler. +
        +
      • 22: Retrieves the bind instance from the table in the +listitem instance.
      • +
      • 24: Unbind the binding.
      • +
      • 25: Removes the value corresponds to the “bind” key.
      • +
    • +
    +

    This technique is not so complicated. You can use it when you make a +cell editable application.

    +

    Change the cell of +GtkColumnView dynamically

    +

    Next topic is to change the GtkColumnView (or GtkListView) cells +dynamically. The example changes the color of the buttons, which are +children of GtkListItem instances, as the current line position +moves.

    +

    The line editor has the current position of the list.

    +
      +
    • At first, no line is current.
    • +
    • When a line is appended or inserted, the line is current.
    • +
    • When the current line is deleted, no line will be current.
    • +
    • When a button in the first column of GtkColumnView is clicked, the +line will be current.
    • +
    +

    The button of the current line is colored with red and otherwise +white.

    +

    The current line has no relationship to GtkSingleSelection object. +GtkSingleSelection selects a line on the display. The current line +doesn’t need to be on the display. It is possible to be on the line out +of the Window (GtkScrolledWindow). Actually, the program doesn’t use +GtkSingleSelection.

    +

    It is necessary to know the corresponding GtkListItem instance from +the item in the list. It is the opposite direction from +gtk_list_item_get_item function. To accomplish this, we set +a listitem element of LeData to point the corresponding +GtkListItem instance. Therefore, items (LeData) in the list always know +the GtkListItem. If there’s no GtkListItem binded to the item, NULL is +assigned.

    +
    void
    +select_cb (GtkButton *btn, GtkListItem *listitem) {
    +  LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW));
    +
    +  update_current (win, gtk_list_item_get_position (listitem));
    +}
    +
    +static void
    +setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  GtkWidget *button = gtk_button_new ();
    +  gtk_list_item_set_child (listitem, button);
    +  g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
    +}
    +
    +static void
    +bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    +  if (data)
    +    le_data_set_listitem (data, listitem);
    +}
    +
    +static void
    +unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    +  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    +  if (data)
    +    le_data_set_listitem (data, NULL);
    +}
    +
      +
    • 8-13: setup1_cb is a setup signal handler on the +GtkSignalListItemFactory. This factory is inserted to the factory +property of the first GtkColumnViewColumn. It sets the child of +listitem to a newly created GtkButton instance. The +“clicked” signal on the button is connected to the handler +select_cb. When the listitem is destroyed, the child +(GtkButton) is also destroyed. At the same time, the connection of the +signal and handler is also destroyed. So, you don’t need teardown signal +handler.
    • +
    • 1-6: select_cb is a “clicked” signal handler. LeWindow +is defined in listeditor.c. It’s a child class of +GtkApplicationWindow. The handler just calls the +update_current function. The function will be explained +later.
    • +
    • 15-20: bind1_cb is a bind signal handler. It sets the +“listitem” element of the item (LeData) to point the +listitem (GtkListItem instance). It makes the item possible +to find the corresponding GtkListItem instance.
    • +
    • 22-27: unbind1_cb is an unbind signal handler. It +removes the listitem instance from the “listitem” element +of the item. The element becomes NULL, which tells no GtkListItem is +bound. When referring GtkListItem, it needs to check the “listitem” +element whether it points a GtkListItem or not (NULL). Otherwise bad +things will happen.
    • +
    +
    static void
    +update_current (LeWindow *win, int new) {
    +  char *s;
    +  LeData *data;
    +  GtkListItem *listitem;
    +  GtkButton *button;
    +  const char *non_current[1] = {NULL};
    +  const char *current[2] = {"current", NULL};
    +
    +  if (new >= 0)
    +    s = g_strdup_printf ("%d", new);
    +  else
    +    s = "";
    +  gtk_label_set_text (GTK_LABEL (win->position_label), s);
    +  if (*s) // s isn't an empty string
    +    g_free (s);
    +
    +  if (win->position >=0) {
    +    data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
    +    if ((listitem = le_data_get_listitem (data)) != NULL) {
    +      button = GTK_BUTTON (gtk_list_item_get_child (listitem));
    +      gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
    +      g_object_unref (listitem);
    +    }
    +    g_object_unref (data);
    +  }
    +  win->position = new;
    +  if (win->position >=0) {
    +    data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
    +    if ((listitem = le_data_get_listitem (data)) != NULL) {
    +      button = GTK_BUTTON (gtk_list_item_get_child (listitem));
    +      gtk_widget_set_css_classes (GTK_WIDGET (button), current);
    +      g_object_unref (listitem);
    +    }
    +    g_object_unref (data);
    +  }
    +}
    +

    The function update_current does several things.

    +
      +
    • It has two parameters. The first one is win, which is +an instance of LeWindow class. It has some elements. +
        +
      • win->position: an Integer. it is the current position. If no +current line exists, it is -1.
      • +
      • win->position_label: GtkLabel. It shows the current +position.
      • +
    • +
    • The second parameter is new, which is the new current +position. At the beginning of the function, win->position points the +old position.
    • +
    • 10-16: Update the text of GtkLabel.
    • +
    • 18-26: If the old position (win->position) is not negative, the +current line exists. It gets a GtkListItem instance via the item +(LeData) of the list. And it gets the GtkButton instance which is the +child of the GtkListItem. It clears the “css-classes” property of the +button.
    • +
    • 27: Updates win->position.
    • +
    • 28-36: If the new position is not negative (It’s possible to be +negative when the current line has been removed), the current line +exists. It sets the “css-classes” property of the button to +{"current", NULL}. It is a NULL-terminated array of +strings. Each string is a CSS class. Now the button has “current” style +class.
    • +
    +

    The color of buttons are determined by the “background” CSS style. +The following CSS is applied to the default GdkDisplay in advance (in +the startup handler of the application).

    +
    columnview listview row button.current {background: red;}
    +

    The selectors “columnview listview row” is needed before “button” +selector. Otherwise the buttons in the GtkColumnview won’t be found. The +button selector has “current” class. So, the only “current” class button +is colored with red. Other buttons are not colored, which means they are +white.

    +

    Gtk_widget_dispose_template +function

    +

    The function gtk_widget_dispose_template clears the +template children for the given widget. This is the opposite of +gtk_widget_init_template(). It is a new function of GTK 4.8 +version. If your GTK version is lower than 4.8, you need to modify the +program.

    +
    + + + diff --git a/gfm/sec21.md b/gfm/sec21.md index 4e13fec..4b3c89d 100644 --- a/gfm/sec21.md +++ b/gfm/sec21.md @@ -713,7 +713,7 @@ It defines the application and supports: 10 GtkCssProvider *provider; 11 }; 12 - 13 G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION); + 13 G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION) 14 15 /* gsettings changed::font signal handler */ 16 static void diff --git a/gfm/sec29.md b/gfm/sec29.md index bbdb25c..e736318 100644 --- a/gfm/sec29.md +++ b/gfm/sec29.md @@ -1,4 +1,4 @@ -Up: [README.md](../README.md), Prev: [Section 28](sec28.md) +Up: [README.md](../README.md), Prev: [Section 28](sec28.md), Next: [Section 30](sec30.md) # GtkColumnView @@ -471,4 +471,4 @@ If you click the header of another column, then the whole lists are sorted by th GtkColumnView is very useful and it can manage very big GListModel. It is possible to use it for file list, application list, database frontend and so on. -Up: [README.md](../README.md), Prev: [Section 28](sec28.md) +Up: [README.md](../README.md), Prev: [Section 28](sec28.md), Next: [Section 30](sec30.md) diff --git a/gfm/sec30.md b/gfm/sec30.md new file mode 100644 index 0000000..a738dce --- /dev/null +++ b/gfm/sec30.md @@ -0,0 +1,303 @@ +Up: [README.md](../README.md), Prev: [Section 29](sec29.md) + +# GtkSignalListItemFactory + +## GtkSignalListItemFactory and GtkBulderListItemFactory + +GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents of a list. +Its binding direction is always from an item of a list to a child of GtkListItem. + +When it comes to dynamic connection, it's not enough. +For example, you want to edit the contents of a list. +You set a child of GtkListItem to a GtkText instance so a user can edit a text with it. +You need to bind an item in the list with the buffer of the GtkText. +The direction is opposite from the one with GtkBuilderListItemFactory. +It is from the GtkText instance to the item in the list. +You can implement this with GtkSignalListItemFactory, which is more flexible than GtkBuilderListItemFactory. + +Two things are shown in this section. + +- Binding from a child of a GtkListItem instance to an item of a list. +- Access a child of GtkListItem dynamically. +This direction is the same as the one with GtkBulderListItemFactory. +But GtkBulderListItemFactory uses GtkExpression from the item property of the GtkListItem. +So, it updates its child widget only when the item property changes. +In this example the child reflects the change in the same item in the list dynamically. + +This section shows just a part of the source file `listeditor.c`. +If you want to see the whole codes, see `src/listeditor` directory of the [Gtk4 tutorial repository](https://github.com/ToshioCP/Gtk4-tutorial). + +## A list editor + +The sample program is a list editor and data of the list are strings. +It's the same as a line editor. +It reads a text file line by line. +Each line is an item of the list. +The list is displayed with GtkColumnView. +There are two columns. +The one is a button, which makes the line be a current line. +If the line is the current line, the button is colored with red. +The other is a string which is the contents of the corresponding item of the list. + +![List editor](../image/listeditor.png) + +The source files are located at `src/listeditor` directory. +You can compile end execute it as follows. + +- Download the program from the [repository](https://github.com/ToshioCP/Gtk4-tutorial). +- Change your current directory to `src/listeditor`. +- Type the following on your commandline. + +~~~ +$ meson _build +$ ninja -C _build +$ _build/listeditor +~~~ + +- Append button: appends a line after the current line, or at the last line if no current line exists. +- Insert button: inserts a line before the current line. +- Remove button: removes a current line. +- Read button: reads a file. +- Write button: writes the contents to a file. +- close button: close the contents. +- quit button: quit the application. +- Button on the select column: makes the line current. +- String column: GtkText. You can edit a string in the field. + +The current line number (zero-based) is shown at the left of the tool bar. +The file name is shown at the right of the write button. + +## Connect a GtkText instance and an item in the list + +The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory. +It uses three signals setup, bind and unbind. +The following is their sgnal handlers. + +~~~C + 1 static void + 2 setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + 3 GtkWidget *text = gtk_text_new (); + 4 gtk_list_item_set_child (listitem, GTK_WIDGET (text)); + 5 } + 6 + 7 static void + 8 bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + 9 GtkWidget *text = gtk_list_item_get_child (listitem); +10 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); +11 GtkEntryBuffer *buffer; +12 GBinding *bind; +13 +14 buffer = gtk_text_get_buffer (GTK_TEXT (text)); +15 gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1); +16 bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT); +17 g_object_set_data (G_OBJECT (listitem), "bind", bind); +18 } +19 +20 static void +21 unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { +22 GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind")); +23 +24 g_binding_unbind(bind); +25 g_object_set_data (G_OBJECT (listitem), "bind", NULL); +26 } +~~~ + +- 1-5: `setup2_cb` is a setup signal handler on the GtkSignalListItemFactory. +This factory is inserted to the factory property of the second GtkColumnViewColumn. +Te handler just creates a GtkText instance and sets the child of `listitem` to it. +The instance will be destroyed automatically when the `listitem` is destroyed. +So, teardown signal handler isn't necessary. +- 7-18: `bind2_cb` is a bind signal handler. +It is called when the `listitem` is bound to an item in the list. +The list items are LeData instances. +LeData is defined in the file `listeditor.c` (the C source file of the list editor). +It is a child class of GObject and has two data. +The one is `listitem` which points a GtkListItem instance when they are connected. +If no GtkListItem is connected, it is NULL. +The other is `string` which is a content of the line. + - 9-10: `text` is a child of the `listitem` and it is a GtkText instance. +`data` is an item pointed by the `listitem`. It is a LeData instance. + - 14: Gets the buffer of the `text`. + - 15: Sets the text of the buffer to `le_data_look_string (data)`. +le\_data\_look\_string returns the string of the data and the ownership of the string is taken by the `data`. +So, the caller don't need to free the string. + - 16: `g_object_bind_property` binds a property and another object property. +This line binds the "text" property of the `buffer` (source) and the "string" property of the `data` (destination). +It is a uni-directional binding (`G_BINDING_DEFAULT`). +When a user changes the GtkText text, the same string is immediately put into the `data`. +The function returns a GBinding instance. +This binding is different from bindings of GtkExpression. +This binding needs the existence of the two properties. + - 17: GObjec has a table. +The key is a string (or GQuark) and the value is a gpointer (pointer to any type). +The function `g_object_set_data` sets the association from the key to the value. +This line sets the association from "bind" to `bind` instance on the `listitem` instance. +It makes possible for the "unbind" handler to get the `bind` instance. +- 20-26: `unbind2_cb` is a unbind signal handler. + - 22: Retrieves the `bind` instance from the table in the `listitem` instance. + - 24: Unbind the binding. + - 25: Removes the value corresponds to the "bind" key. + +This technique is not so complicated. +You can use it when you make a cell editable application. + +## Change the cell of GtkColumnView dynamically + +Next topic is to change the GtkColumnView (or GtkListView) cells dynamically. +The example changes the color of the buttons, which are children of GtkListItem instances, as the current line position moves. + +The line editor has the current position of the list. + +- At first, no line is current. +- When a line is appended or inserted, the line is current. +- When the current line is deleted, no line will be current. +- When a button in the first column of GtkColumnView is clicked, the line will be current. + +The button of the current line is colored with red and otherwise white. + +The current line has no relationship to GtkSingleSelection object. +GtkSingleSelection selects a line on the display. +The current line doesn't need to be on the display. +It is possible to be on the line out of the Window (GtkScrolledWindow). +Actually, the program doesn't use GtkSingleSelection. + +It is necessary to know the corresponding GtkListItem instance from the item in the list. +It is the opposite direction from `gtk_list_item_get_item` function. +To accomplish this, we set a `listitem` element of LeData to point the corresponding GtkListItem instance. +Therefore, items (LeData) in the list always know the GtkListItem. +If there's no GtkListItem binded to the item, NULL is assigned. + +~~~C + 1 void + 2 select_cb (GtkButton *btn, GtkListItem *listitem) { + 3 LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW)); + 4 + 5 update_current (win, gtk_list_item_get_position (listitem)); + 6 } + 7 + 8 static void + 9 setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { +10 GtkWidget *button = gtk_button_new (); +11 gtk_list_item_set_child (listitem, button); +12 g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem); +13 } +14 +15 static void +16 bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { +17 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); +18 if (data) +19 le_data_set_listitem (data, listitem); +20 } +21 +22 static void +23 unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { +24 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); +25 if (data) +26 le_data_set_listitem (data, NULL); +27 } +~~~ + +- 8-13: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. +This factory is inserted to the factory property of the first GtkColumnViewColumn. +It sets the child of `listitem` to a newly created GtkButton instance. +The "clicked" signal on the button is connected to the handler `select_cb`. +When the listitem is destroyed, the child (GtkButton) is also destroyed. +At the same time, the connection of the signal and handler is also destroyed. +So, you don't need teardown signal handler. +- 1-6: `select_cb` is a "clicked" signal handler. +LeWindow is defined in `listeditor.c`. +It's a child class of GtkApplicationWindow. +The handler just calls the `update_current` function. +The function will be explained later. +- 15-20: `bind1_cb` is a bind signal handler. +It sets the "listitem" element of the item (LeData) to point the `listitem` (GtkListItem instance). +It makes the item possible to find the corresponding GtkListItem instance. +- 22-27: `unbind1_cb` is an unbind signal handler. +It removes the `listitem` instance from the "listitem" element of the item. +The element becomes NULL, which tells no GtkListItem is bound. +When referring GtkListItem, it needs to check the "listitem" element whether it points a GtkListItem or not (NULL). +Otherwise bad things will happen. + +~~~C + 1 static void + 2 update_current (LeWindow *win, int new) { + 3 char *s; + 4 LeData *data; + 5 GtkListItem *listitem; + 6 GtkButton *button; + 7 const char *non_current[1] = {NULL}; + 8 const char *current[2] = {"current", NULL}; + 9 +10 if (new >= 0) +11 s = g_strdup_printf ("%d", new); +12 else +13 s = ""; +14 gtk_label_set_text (GTK_LABEL (win->position_label), s); +15 if (*s) // s isn't an empty string +16 g_free (s); +17 +18 if (win->position >=0) { +19 data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position)); +20 if ((listitem = le_data_get_listitem (data)) != NULL) { +21 button = GTK_BUTTON (gtk_list_item_get_child (listitem)); +22 gtk_widget_set_css_classes (GTK_WIDGET (button), non_current); +23 g_object_unref (listitem); +24 } +25 g_object_unref (data); +26 } +27 win->position = new; +28 if (win->position >=0) { +29 data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position)); +30 if ((listitem = le_data_get_listitem (data)) != NULL) { +31 button = GTK_BUTTON (gtk_list_item_get_child (listitem)); +32 gtk_widget_set_css_classes (GTK_WIDGET (button), current); +33 g_object_unref (listitem); +34 } +35 g_object_unref (data); +36 } +37 } +~~~ + + +The function `update_current` does several things. + +- It has two parameters. +The first one is `win`, which is an instance of LeWindow class. +It has some elements. + - win->position: an Integer. it is the current position. If no current line exists, it is -1. + - win->position_label: GtkLabel. It shows the current position. +- The second parameter is `new`, which is the new current position. +At the beginning of the function, win->position points the old position. +- 10-16: Update the text of GtkLabel. +- 18-26: If the old position (win->position) is not negative, the current line exists. +It gets a GtkListItem instance via the item (LeData) of the list. +And it gets the GtkButton instance which is the child of the GtkListItem. +It clears the "css-classes" property of the button. +- 27: Updates win->position. +- 28-36: If the new position is not negative (It's possible to be negative when the current line has been removed), the current line exists. +It sets the "css-classes" property of the button to `{"current", NULL}`. +It is a NULL-terminated array of strings. +Each string is a CSS class. +Now the button has "current" style class. + +The color of buttons are determined by the "background" CSS style. +The following CSS is applied to the default GdkDisplay in advance (in the startup handler of the application). + +~~~css +columnview listview row button.current {background: red;} +~~~ + +The selectors "columnview listview row" is needed before "button" selector. +Otherwise the buttons in the GtkColumnview won't be found. +The button selector has "current" class. +So, the only "current" class button is colored with red. +Other buttons are not colored, which means they are white. + +## Gtk\_widget\_dispose\_template function + +The function `gtk_widget_dispose_template` clears the template children for the given widget. +This is the opposite of `gtk_widget_init_template()`. +It is a new function of GTK 4.8 version. +If your GTK version is lower than 4.8, you need to modify the program. + +Up: [README.md](../README.md), Prev: [Section 29](sec29.md) diff --git a/image/listeditor.png b/image/listeditor.png new file mode 100644 index 0000000000000000000000000000000000000000..7e99012f35084f4236a37cca08d025c23133a447 GIT binary patch literal 23695 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 bVs-qgzn1fsrLbwCHb~Ud)z4*}Q$iB}lYac` literal 0 HcmV?d00001 diff --git a/src/listeditor/.vscode/c_cpp_properties.json b/src/listeditor/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e25b981 --- /dev/null +++ b/src/listeditor/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64", + "compileCommands": "${workspaceFolder}/_build/compile_commands.json" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/src/listeditor/listeditor.c b/src/listeditor/listeditor.c new file mode 100644 index 0000000..8723831 --- /dev/null +++ b/src/listeditor/listeditor.c @@ -0,0 +1,530 @@ +#include + +#define LE_TYPE_DATA (le_data_get_type ()) +G_DECLARE_FINAL_TYPE (LeData, le_data, LE, DATA, GObject) + +enum { + PROP_0, + PROP_STRING, + N_PROPERTIES +}; + +static GParamSpec *ledata_properties[N_PROPERTIES] = {NULL, }; + +typedef struct _LeData { + GObject parent; + GtkListItem *listitem; + char *string; +} LeData; + +G_DEFINE_TYPE(LeData, le_data, G_TYPE_OBJECT) + +static void +le_data_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { + LeData *self = LE_DATA (object); + + if (property_id == PROP_STRING) + self->string = g_strdup (g_value_get_string (value)); + else + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +le_data_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { + LeData *self = LE_DATA (object); + + if (property_id == PROP_STRING) + g_value_set_string (value, g_strdup (self->string)); + else + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +le_data_init (LeData *self) { + self->listitem = NULL; + self->string = NULL; +} + +static void +le_data_dispose (GObject *object) { + LeData *self = LE_DATA (object); + + if (self->listitem) + g_clear_object (&self->listitem); + G_OBJECT_CLASS (le_data_parent_class)->dispose (object); +} + +static void +le_data_finalize (GObject *object) { + LeData *self = LE_DATA (object); + + if (self->string) + g_free (self->string); + G_OBJECT_CLASS (le_data_parent_class)->finalize (object); +} + +static void +le_data_class_init (LeDataClass *class) { + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = le_data_dispose; + gobject_class->finalize = le_data_finalize; + gobject_class->set_property = le_data_set_property; + gobject_class->get_property = le_data_get_property; + ledata_properties[PROP_STRING] = g_param_spec_string ("string", "string", "string", "", G_PARAM_READWRITE); + g_object_class_install_properties (gobject_class,N_PROPERTIES, ledata_properties); +} + +/* setter and getter */ +void +le_data_set_listitem (LeData *self, GtkListItem *listitem) { + g_return_if_fail (GTK_IS_LIST_ITEM (listitem) || listitem == NULL); + + if (self->listitem) + g_object_unref (self->listitem); + self->listitem = listitem ? g_object_ref (listitem) : NULL; +} + +void +le_data_set_string (LeData *self, const char *string) { + if (self->string) + g_free (self->string); + self->string = g_strdup (string); +} + +void +le_data_take_string (LeData *self, char *string) { + if (self->string) + g_free (self->string); + self->string = string; +} + +GtkListItem * +le_data_get_listitem (LeData *self) { + return g_object_ref (self->listitem); +} + +GtkListItem * +le_data_look_listitem (LeData *self) { + return self->listitem; +} + +char * +le_data_get_string (LeData *self) { + return g_strdup (self->string); +} + +const char * +le_data_look_string (LeData *self) { + return self->string; +} + +LeData * +le_data_new_with_data (GtkListItem *listitem, const char *string) { + g_return_val_if_fail (GTK_IS_LIST_ITEM (listitem) || listitem == NULL, NULL); + LeData *data; + + data = LE_DATA (g_object_new (LE_TYPE_DATA, NULL)); + data->listitem = listitem ? g_object_ref (listitem) : NULL; + data->string = g_strdup (string); + return data; +} + +LeData * +le_data_new (void) { + return LE_DATA (g_object_new (LE_TYPE_DATA, NULL)); +} + +/* ----- definition of LeWindow ----- */ +#define LE_TYPE_WINDOW (le_window_get_type ()) +G_DECLARE_FINAL_TYPE (LeWindow, le_window, LE, WINDOW, GtkApplicationWindow) +typedef struct _LeWindow { + GtkApplicationWindow parent; + int position; /* current position */ + GFile *file; + GtkWidget *position_label; + GtkWidget *filename; + GtkWidget *columnview; + GListStore *liststore; +} LeWindow; +G_DEFINE_TYPE (LeWindow, le_window, GTK_TYPE_APPLICATION_WINDOW) + +static void +update_current (LeWindow *win, int new) { + char *s; + LeData *data; + GtkListItem *listitem; + GtkButton *button; + const char *non_current[1] = {NULL}; + const char *current[2] = {"current", NULL}; + + if (new >= 0) + s = g_strdup_printf ("%d", new); + else + s = ""; + gtk_label_set_text (GTK_LABEL (win->position_label), s); + if (*s) // s isn't an empty string + g_free (s); + + if (win->position >=0) { + data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position)); + if ((listitem = le_data_get_listitem (data)) != NULL) { + button = GTK_BUTTON (gtk_list_item_get_child (listitem)); + gtk_widget_set_css_classes (GTK_WIDGET (button), non_current); + g_object_unref (listitem); + } + g_object_unref (data); + } + win->position = new; + if (win->position >=0) { + data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position)); + if ((listitem = le_data_get_listitem (data)) != NULL) { + button = GTK_BUTTON (gtk_list_item_get_child (listitem)); + gtk_widget_set_css_classes (GTK_WIDGET (button), current); + g_object_unref (listitem); + } + g_object_unref (data); + } +} + +/* ----- Button "clicled" signal handlers ----- */ +static void +app_cb (GtkButton *btn, LeWindow *win) { + LeData *data; + + data = le_data_new_with_data (NULL, ""); + if (win->position >= 0) { + g_list_store_insert (win->liststore, win->position + 1, data); + update_current (win, win->position + 1); + } else { + g_list_store_append (win->liststore, data); + update_current (win, g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) - 1); + } + g_object_unref (data); +} + +static void +ins_cb (GtkButton *btn, LeWindow *win) { + LeData *data; + + data = le_data_new_with_data (NULL, ""); + if (win->position >= 0) { + g_list_store_insert (win->liststore, win->position, data); + win->position += 1; + update_current (win, win->position - 1); + } + g_object_unref (data); +} + +static void +rm_cb (GtkButton *btn, LeWindow *win) { + if (win->position >= 0) { + g_list_store_remove (win->liststore, win->position); + win->position = -1; + update_current (win, -1); + } +} + +static void +open_dialog_response(GtkWidget *dialog, gint response, LeWindow *win) { + GFile *file; + char *contents; + char *s; + gsize length; + GFileInputStream *stream; + GDataInputStream *dstream; + GError *err = NULL; + LeData *data; + + if (response == GTK_RESPONSE_ACCEPT + && G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))) + && g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { + if (! (stream = g_file_read (file, NULL, &err))) { + g_warning ("%s\n", err->message); + g_error_free (err); + return; + } + dstream = g_data_input_stream_new (G_INPUT_STREAM (stream)); + g_object_unref (stream); + while ((contents = g_data_input_stream_read_line_utf8 (dstream, &length, NULL, &err)) != NULL) { + data = le_data_new_with_data (NULL, contents); + g_free (contents); + g_list_store_append (win->liststore, data); + g_object_unref (data); + } + if (err) { + g_warning ("%s\n", err->message); + if (g_list_model_get_n_items(G_LIST_MODEL (win->liststore)) > 0) + g_list_store_remove_all (win->liststore); + return; + } else if (! g_input_stream_close (G_INPUT_STREAM (dstream), NULL, &err)) { /* EOF */ + g_warning ("%s\n", err->message); + g_error_free (err); + return; + } + win->file = file; /* win->file is NULL (has already checked) and it take the ownership of 'file' */ + s = g_file_get_basename (file); + gtk_label_set_text (GTK_LABEL (win->filename), s); + g_free (s); + update_current (win, -1); + } + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +read_cb (GtkButton *btn, LeWindow *win) { + GtkWidget *dialog; + + if (win->file || g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) > 0) + return; + dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, + "Cancel", GTK_RESPONSE_CANCEL, + "Open", GTK_RESPONSE_ACCEPT, + NULL); + g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), win); + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +write_data (LeWindow *win) { + GFileOutputStream *ostream; + gssize size; + gsize length; + LeData *data; + int i, n_items; + GError *err = NULL; + + if (! (ostream = g_file_replace (win->file, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &err))) { + g_warning ("%s\n", err->message); + g_error_free (err); + return; + } + n_items = g_list_model_get_n_items (G_LIST_MODEL (win->liststore)); + for (i=0; iliststore), i)); + length = (gsize) strlen (le_data_look_string (data)); + size = g_output_stream_write (G_OUTPUT_STREAM (ostream), le_data_look_string (data), length, NULL, &err); + g_object_unref (data); + if (size < 0) { + g_warning ("%s\n", err->message); + g_error_free (err); + break; + } + size = g_output_stream_write (G_OUTPUT_STREAM (ostream), "\n", 1, NULL, &err); + if (size < 0) { + g_warning ("%s\n", err->message); + g_error_free (err); + break; + } + } + if (! (g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, &err))) { + g_warning ("%s\n", err->message); + g_error_free (err); + } +} + +static void +saveas_dialog_response (GtkWidget *dialog, gint response, LeWindow *win) { + GFile *file; + char *s; + + if (response == GTK_RESPONSE_ACCEPT) { + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (G_IS_FILE (file)) { + win->file = file; /* the ownership is taken by win */ + s = g_file_get_basename (file); + gtk_label_set_text (GTK_LABEL (win->filename), s); + g_free (s); + write_data (win); + } + else { + g_warning ("gtk_file_chooser_get_file returns non GFile.\n"); + } + } + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +show_saveas_dialog (LeWindow *win) { + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, + "Save", GTK_RESPONSE_ACCEPT, + NULL); + g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), win); + gtk_widget_show (dialog); +} + +static void +write_cb (GtkButton *btn, LeWindow *win) { + if (g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) == 0) + return; + if (win->file) + write_data (win); + else + show_saveas_dialog (win); +} + +static void +close_cb (GtkButton *btn, LeWindow *win) { + if (g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) > 0) + g_list_store_remove_all (win->liststore); + if (win->file) { + g_clear_object (&win->file); + gtk_label_set_text (GTK_LABEL (win->filename), ""); + } + win->position = -1; + gtk_label_set_text (GTK_LABEL (win->position_label), ""); +} + +static void +quit_cb (GtkButton *btn, LeWindow *win) { + GtkApplication *app = gtk_window_get_application (GTK_WINDOW (win)); + g_application_quit (G_APPLICATION (app)); +} + +void +select_cb (GtkButton *btn, GtkListItem *listitem) { + LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW)); + + update_current (win, gtk_list_item_get_position (listitem)); +} + +/* ----- Handlers on GtkSignalListItemFacory ----- */ +static void +setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + GtkWidget *button = gtk_button_new (); + gtk_list_item_set_child (listitem, button); + g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem); +} + +static void +bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); + if (data) + le_data_set_listitem (data, listitem); +} + +static void +unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); + if (data) + le_data_set_listitem (data, NULL); +} + +static void +setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + GtkWidget *text = gtk_text_new (); + gtk_list_item_set_child (listitem, GTK_WIDGET (text)); +} + +static void +bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + GtkWidget *text = gtk_list_item_get_child (listitem); + LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); + GtkEntryBuffer *buffer; + GBinding *bind; + + buffer = gtk_text_get_buffer (GTK_TEXT (text)); + gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1); + bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT); + g_object_set_data (G_OBJECT (listitem), "bind", bind); +} + +static void +unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind")); + + g_binding_unbind(bind); + g_object_set_data (G_OBJECT (listitem), "bind", NULL); +} + +static void +le_window_init (LeWindow *win) { + gtk_widget_init_template (GTK_WIDGET (win)); + + win->position = -1; + win->file =NULL; +} + +static void +le_window_dispose (GObject *object) { + LeWindow *win = LE_WINDOW (object); + + /* this function is available since GTK 4.8 */ + gtk_widget_dispose_template (GTK_WIDGET (win), LE_TYPE_WINDOW); + /* chain to the parent */ + G_OBJECT_CLASS (le_window_parent_class)->dispose (object); +} + +static void +le_window_class_init (LeWindowClass *class) { + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->dispose = le_window_dispose; + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/listeditor/listeditor.ui"); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, position_label); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, filename); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, columnview); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, liststore); + /* The followint macros are available since GTK 4.8 */ + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), app_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), ins_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), rm_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), read_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), write_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), close_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), quit_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), setup1_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), bind1_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), unbind1_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), setup2_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), bind2_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), unbind2_cb); +} + +GtkWidget * +le_window_new (GtkApplication *app) { + g_return_val_if_fail (GTK_IS_APPLICATION (app), NULL); + + return GTK_WIDGET (g_object_new (LE_TYPE_WINDOW, "application", app, NULL)); +} + +/* ----- activate, startup handlers ----- */ +static void +app_activate (GApplication *application) { + GtkApplication *app = GTK_APPLICATION (application); + gtk_window_present (gtk_application_get_active_window(app)); +} + +static void +app_startup (GApplication *application) { + GtkApplication *app = GTK_APPLICATION (application); + GtkCssProvider *provider; + + le_window_new (app); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, "text:focus {border: 1px solid gray;} columnview listview row button.current {background: red;}", -1); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); +} + +#define APPLICATION_ID "com.github.ToshioCP.listeditor" + +int +main (int argc, char **argv) { + GtkApplication *app; + int stat; + + app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS); + + g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + + stat =g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + return stat; +} diff --git a/src/listeditor/listeditor.gresource.xml b/src/listeditor/listeditor.gresource.xml new file mode 100644 index 0000000..03a370e --- /dev/null +++ b/src/listeditor/listeditor.gresource.xml @@ -0,0 +1,6 @@ + + + + listeditor.ui + + diff --git a/src/listeditor/listeditor.ui b/src/listeditor/listeditor.ui new file mode 100644 index 0000000..e8f187f --- /dev/null +++ b/src/listeditor/listeditor.ui @@ -0,0 +1,133 @@ + + + + + diff --git a/src/listeditor/meson.build b/src/listeditor/meson.build new file mode 100644 index 0000000..70d64c0 --- /dev/null +++ b/src/listeditor/meson.build @@ -0,0 +1,11 @@ +project('csv', 'c') + +glibdep = dependency('glib-2.0', version: '>=2.68.0') +gtkdep = dependency('gtk4', version: '>=4.8') + +gnome=import('gnome') +resources = gnome.compile_resources('resources','listeditor.gresource.xml') + +#sourcefiles=files('listeditor.c', 'tcsvapplication.c') + +executable('listeditor', ['listeditor.c'], resources, dependencies: gtkdep, export_dynamic: true, install: false) diff --git a/src/sec30.src.md b/src/sec30.src.md new file mode 100644 index 0000000..1621f07 --- /dev/null +++ b/src/sec30.src.md @@ -0,0 +1,212 @@ +# GtkSignalListItemFactory + +## GtkSignalListItemFactory and GtkBulderListItemFactory + +GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents of a list. +Its binding direction is always from an item of a list to a child of GtkListItem. + +When it comes to dynamic connection, it's not enough. +For example, you want to edit the contents of a list. +You set a child of GtkListItem to a GtkText instance so a user can edit a text with it. +You need to bind an item in the list with the buffer of the GtkText. +The direction is opposite from the one with GtkBuilderListItemFactory. +It is from the GtkText instance to the item in the list. +You can implement this with GtkSignalListItemFactory, which is more flexible than GtkBuilderListItemFactory. + +Two things are shown in this section. + +- Binding from a child of a GtkListItem instance to an item of a list. +- Access a child of GtkListItem dynamically. +This direction is the same as the one with GtkBulderListItemFactory. +But GtkBulderListItemFactory uses GtkExpression from the item property of the GtkListItem. +So, it updates its child widget only when the item property changes. +In this example the child reflects the change in the same item in the list dynamically. + +This section shows just a part of the source file `listeditor.c`. +If you want to see the whole codes, see `src/listeditor` directory of the [Gtk4 tutorial repository](https://github.com/ToshioCP/Gtk4-tutorial). + +## A list editor + +The sample program is a list editor and data of the list are strings. +It's the same as a line editor. +It reads a text file line by line. +Each line is an item of the list. +The list is displayed with GtkColumnView. +There are two columns. +The one is a button, which makes the line be a current line. +If the line is the current line, the button is colored with red. +The other is a string which is the contents of the corresponding item of the list. + +![List editor](../image/listeditor.png){width=12cm height=9cm} + +The source files are located at `src/listeditor` directory. +You can compile end execute it as follows. + +- Download the program from the [repository](https://github.com/ToshioCP/Gtk4-tutorial). +- Change your current directory to `src/listeditor`. +- Type the following on your commandline. + +~~~ +$ meson _build +$ ninja -C _build +$ _build/listeditor +~~~ + +- Append button: appends a line after the current line, or at the last line if no current line exists. +- Insert button: inserts a line before the current line. +- Remove button: removes a current line. +- Read button: reads a file. +- Write button: writes the contents to a file. +- close button: close the contents. +- quit button: quit the application. +- Button on the select column: makes the line current. +- String column: GtkText. You can edit a string in the field. + +The current line number (zero-based) is shown at the left of the tool bar. +The file name is shown at the right of the write button. + +## Connect a GtkText instance and an item in the list + +The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory. +It uses three signals setup, bind and unbind. +The following is their sgnal handlers. + +@@@include +listeditor/listeditor.c setup2_cb bind2_cb unbind2_cb +@@@ + +- 1-5: `setup2_cb` is a setup signal handler on the GtkSignalListItemFactory. +This factory is inserted to the factory property of the second GtkColumnViewColumn. +Te handler just creates a GtkText instance and sets the child of `listitem` to it. +The instance will be destroyed automatically when the `listitem` is destroyed. +So, teardown signal handler isn't necessary. +- 7-18: `bind2_cb` is a bind signal handler. +It is called when the `listitem` is bound to an item in the list. +The list items are LeData instances. +LeData is defined in the file `listeditor.c` (the C source file of the list editor). +It is a child class of GObject and has two data. +The one is `listitem` which points a GtkListItem instance when they are connected. +If no GtkListItem is connected, it is NULL. +The other is `string` which is a content of the line. + - 9-10: `text` is a child of the `listitem` and it is a GtkText instance. +`data` is an item pointed by the `listitem`. It is a LeData instance. + - 14: Gets the buffer of the `text`. + - 15: Sets the text of the buffer to `le_data_look_string (data)`. +le\_data\_look\_string returns the string of the data and the ownership of the string is taken by the `data`. +So, the caller don't need to free the string. + - 16: `g_object_bind_property` binds a property and another object property. +This line binds the "text" property of the `buffer` (source) and the "string" property of the `data` (destination). +It is a uni-directional binding (`G_BINDING_DEFAULT`). +When a user changes the GtkText text, the same string is immediately put into the `data`. +The function returns a GBinding instance. +This binding is different from bindings of GtkExpression. +This binding needs the existence of the two properties. + - 17: GObjec has a table. +The key is a string (or GQuark) and the value is a gpointer (pointer to any type). +The function `g_object_set_data` sets the association from the key to the value. +This line sets the association from "bind" to `bind` instance on the `listitem` instance. +It makes possible for the "unbind" handler to get the `bind` instance. +- 20-26: `unbind2_cb` is a unbind signal handler. + - 22: Retrieves the `bind` instance from the table in the `listitem` instance. + - 24: Unbind the binding. + - 25: Removes the value corresponds to the "bind" key. + +This technique is not so complicated. +You can use it when you make a cell editable application. + +## Change the cell of GtkColumnView dynamically + +Next topic is to change the GtkColumnView (or GtkListView) cells dynamically. +The example changes the color of the buttons, which are children of GtkListItem instances, as the current line position moves. + +The line editor has the current position of the list. + +- At first, no line is current. +- When a line is appended or inserted, the line is current. +- When the current line is deleted, no line will be current. +- When a button in the first column of GtkColumnView is clicked, the line will be current. + +The button of the current line is colored with red and otherwise white. + +The current line has no relationship to GtkSingleSelection object. +GtkSingleSelection selects a line on the display. +The current line doesn't need to be on the display. +It is possible to be on the line out of the Window (GtkScrolledWindow). +Actually, the program doesn't use GtkSingleSelection. + +It is necessary to know the corresponding GtkListItem instance from the item in the list. +It is the opposite direction from `gtk_list_item_get_item` function. +To accomplish this, we set a `listitem` element of LeData to point the corresponding GtkListItem instance. +Therefore, items (LeData) in the list always know the GtkListItem. +If there's no GtkListItem binded to the item, NULL is assigned. + +@@@include +listeditor/listeditor.c select_cb setup1_cb bind1_cb unbind1_cb +@@@ + +- 8-13: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. +This factory is inserted to the factory property of the first GtkColumnViewColumn. +It sets the child of `listitem` to a newly created GtkButton instance. +The "clicked" signal on the button is connected to the handler `select_cb`. +When the listitem is destroyed, the child (GtkButton) is also destroyed. +At the same time, the connection of the signal and handler is also destroyed. +So, you don't need teardown signal handler. +- 1-6: `select_cb` is a "clicked" signal handler. +LeWindow is defined in `listeditor.c`. +It's a child class of GtkApplicationWindow. +The handler just calls the `update_current` function. +The function will be explained later. +- 15-20: `bind1_cb` is a bind signal handler. +It sets the "listitem" element of the item (LeData) to point the `listitem` (GtkListItem instance). +It makes the item possible to find the corresponding GtkListItem instance. +- 22-27: `unbind1_cb` is an unbind signal handler. +It removes the `listitem` instance from the "listitem" element of the item. +The element becomes NULL, which tells no GtkListItem is bound. +When referring GtkListItem, it needs to check the "listitem" element whether it points a GtkListItem or not (NULL). +Otherwise bad things will happen. + +@@@include +listeditor/listeditor.c update_current +@@@ + + +The function `update_current` does several things. + +- It has two parameters. +The first one is `win`, which is an instance of LeWindow class. +It has some elements. + - win->position: an Integer. it is the current position. If no current line exists, it is -1. + - win->position_label: GtkLabel. It shows the current position. +- The second parameter is `new`, which is the new current position. +At the beginning of the function, win->position points the old position. +- 10-16: Update the text of GtkLabel. +- 18-26: If the old position (win->position) is not negative, the current line exists. +It gets a GtkListItem instance via the item (LeData) of the list. +And it gets the GtkButton instance which is the child of the GtkListItem. +It clears the "css-classes" property of the button. +- 27: Updates win->position. +- 28-36: If the new position is not negative (It's possible to be negative when the current line has been removed), the current line exists. +It sets the "css-classes" property of the button to `{"current", NULL}`. +It is a NULL-terminated array of strings. +Each string is a CSS class. +Now the button has "current" style class. + +The color of buttons are determined by the "background" CSS style. +The following CSS is applied to the default GdkDisplay in advance (in the startup handler of the application). + +~~~css +columnview listview row button.current {background: red;} +~~~ + +The selectors "columnview listview row" is needed before "button" selector. +Otherwise the buttons in the GtkColumnview won't be found. +The button selector has "current" class. +So, the only "current" class button is colored with red. +Other buttons are not colored, which means they are white. + +## Gtk\_widget\_dispose\_template function + +The function `gtk_widget_dispose_template` clears the template children for the given widget. +This is the opposite of `gtk_widget_init_template()`. +It is a new function of GTK 4.8 version. +If your GTK version is lower than 4.8, you need to modify the program. diff --git a/src/tfe7/tfeapplication.c b/src/tfe7/tfeapplication.c index 4c828dd..fe2ea5f 100644 --- a/src/tfe7/tfeapplication.c +++ b/src/tfe7/tfeapplication.c @@ -10,7 +10,7 @@ struct _TfeApplication { GtkCssProvider *provider; }; -G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION); +G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION) /* gsettings changed::font signal handler */ static void