diff --git a/README.md b/README.md index 52da027..7ad7cd0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ The table of contents is at the end of this abstract. - Section 3 to 23 describes the basics, with the example of a simple editor `tfe` (Text File Editor). - Section 24 to 27 describes GtkDrawingArea. -- Section 28 to 32 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView). +- Section 28 describes Drag and Drop. +- Section 29 to 33 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView). It also describes GtkExpression. The latest version of the tutorial is located at [GTK4-tutorial GitHub repository](https://github.com/ToshioCP/Gtk4-tutorial). diff --git a/docs/index.html b/docs/index.html index 0b7c87c..b75cf8b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -111,7 +111,8 @@ basics. The table of contents is at the end of this abstract.

  • Section 3 to 23 describes the basics, with the example of a simple editor tfe (Text File Editor).
  • Section 24 to 27 describes GtkDrawingArea.
  • -
  • Section 28 to 32 describes the list model and the list view +
  • Section 28 describes Drag and Drop.
  • +
  • Section 29 to 33 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView). It also describes GtkExpression.
  • diff --git a/docs/sec29.html b/docs/sec29.html index 918351b..1b5e97f 100644 --- a/docs/sec29.html +++ b/docs/sec29.html @@ -120,16 +120,17 @@ Reference – List Widget Overview.

    GtkTreeView which are took over from GTK 3. There’s an article in Gtk Development blog about list widgets by Matthias Clasen. He described -why GtkListView are developed to replace GtkListBox and GtkTreeView.

    +why GtkListView are developed to replace GtkTreeView. GtkTreeView is +deprecated since version 4.10.

    GtkListView, GtkGridView, GtkColumnView and related objects are -described in Section 26 to 29.

    +described in Section 29 to 33.

    Outline

    A list is a sequential data structure. For example, an ordered string sequence “one”, “two”, “three”, “four” is a list. Each element is called item. A list is like an array, but in many cases it is implemented with pointers which point to the next items of the list. And it has a start point. So, each item can be referred by the index of the item (first -item, second item, …, nth item, …). There are two cases. One is the +item, second item, …, nth item, …). There are two cases. The one is the index starts from one (one-based) and the other is from zero (zero-based).

    Gio provides GListModel interface. It is a zero-based list and its @@ -138,12 +139,12 @@ implement the same interface. An object implements GListModel is not a widget. So, the list is not displayed on the screen directly. There’s another object GtkListView which is a widget to display the list. The items in the list need to be connected to the items in GtkListView. -GtkListItemFactory instance maps items in the list to GListView.

    +GtkListItemFactory instance maps items in the list to GtkListView.

    List
    -

    GListModel

    +

    GListModel and GtkStringList

    If you want to make a list of strings with GListModel, for example, “one”, “two”, “three”, “four”, note that strings can’t be items of the list. Because GListModel is a list of GObject objects and strings aren’t @@ -356,7 +357,7 @@ instantiated.

    Therefore, GtkListItem instance is used as the this object of the lookup tag when it is evaluated. this object -will be explained in section 30.

    +will be explained in section 31.

    The C source code is as follows. Its name is list2.c and located under src/misc directory.

    g_file_enumerate_children_async() to get the GFileInfo
     objects. The list model is created by
     gtk_directory_list_new function.

    -
    GtkDirectoryList *gtk_directory_list_new (const char *attributes, GFile *file);
    +
    GtkDirectoryList *gtk_directory_list_new (const char *attributes, GFile *file);

    attributes is a comma separated list of file attributes. File attributes are key-value pairs. A key consists of a namespace and a name. For example, “standard::name” key is the name of a file. @@ -477,7 +478,7 @@ seconds since the UNIX epoch

    The current directory is “.”. The following program makes GtkDirectoryList dl and its contents are GFileInfo objects under the current directory.

    -
    GFile *file = g_file_new_for_path (".");
    +
    GFile *file = g_file_new_for_path (".");
     GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
     g_object_unref (file);

    It is not so difficult to make file listing program by changing @@ -486,7 +487,7 @@ GInfoFile doesn’t have properties. Lookup tag look for a property, so it is useless for looking for a filename from a GFileInfo object. Instead, closure tag is appropriate in this case. Closure tag specifies a function and the type of the return value of the function.

    -
    const char *
    +
    char *
     get_file_name (GtkListItem *item, GFileInfo *info) {
       return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
     }
    @@ -526,7 +527,7 @@ the function.
     the value of the item property of the GtkListItem. This will be the
     second argument of the function. The first parameter is always the
     GListItem instance, which is a ‘this’ object. The ‘this’ object is
    -explained in section 28.
    +explained in section 31.
     
  • gtk_file_name function is the callback function for the closure tag. It first checks the info parameter. Because it can be NULL when GListItem item is unbounded. If it’s diff --git a/docs/sec30.html b/docs/sec30.html index 5a47641..734391d 100644 --- a/docs/sec30.html +++ b/docs/sec30.html @@ -173,8 +173,7 @@ registered to IANA media types. Such “x-” subtype is not a standard mime type.) Content type is also used by desktop systems.
  • GtkGridView uses the same GtkSingleSelection instance -(singleselection). So, its model property is set with -it.

    +(singleselection). So, its model property is set to it.

    Ui file of the window

    The window is built with the following ui file. (See the screenshot at the beginning of this section).

    @@ -291,7 +290,7 @@ GtkListView or GtkGridView.

    The action view is created, connected to the “activate” signal handler and inserted to the window (action map) as follows.

    -
    act_view = g_simple_action_new_stateful ("view", g_variant_type_new("s"), g_variant_new_string ("list"));
    +
    act_view = g_simple_action_new_stateful ("view", g_variant_type_new("s"), g_variant_new_string ("list"));
     g_signal_connect (act_view, "activate", G_CALLBACK (view_activated), NULL);
     g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view));

    The signal handler view_activated will be explained @@ -464,7 +463,7 @@ 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.

    -
    static void
    +
    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);
    @@ -558,12 +557,12 @@ list and items.
     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 feature comes from desktop.

    +GNOME text editor with the file. Such feature comes from desktop.

    Compilation and execution

    The source files are located in src/list4 directory. To compile and execute list4, type as follows.

    $ cd list4 # or cd src/list4. It depends your current directory.
    -$ meson _build
    +$ meson setup _build
     $ ninja -C _build
     $ _build/list4

    Then a file list appears as a list style. Click on a button on the diff --git a/docs/sec31.html b/docs/sec31.html index 00ea9f9..d4564e3 100644 --- a/docs/sec31.html +++ b/docs/sec31.html @@ -191,13 +191,13 @@ value of the expression. The type of the value is int. G_TYPE_POINTER - +void * gpointer - +general pointer G_TYPE_STRING - +char * gchararray null-terminated Cstring @@ -250,7 +250,7 @@ instance to another expression.

    A property expression (GtkPropertyExpression) looks up a property in a GObject instance. For example, a property expression that refers “label” property in a GtkLabel object is created like this.

    -
    expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label");
    +
    expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label");

    The second parameter another_expression is one of:

    • An expression that gives a GtkLabel instance when it is @@ -259,14 +259,14 @@ evaluated.
    • is evaluated. The instance is called this object.

    For example,

    -
    label = gtk_label_new ("Hello");
    +
    label = gtk_label_new ("Hello");
     another_expression = gtk_constant_expression_new (GTK_TYPE_LABEL, label);
     expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label");

    If expression is evaluated, the second parameter another_expression is evaluated in advance. The value of another_expression is the label (GtkLabel instance). Then, expression looks up “label” property of -the label and the evaluation results “Hello”.

    +the label and the evaluation results in “Hello”.

    In the example above, the second argument of gtk_property_expression_new is another expression. But the second argument can be NULL. If it is NULL, this instance @@ -291,7 +291,7 @@ class="sourceCode numberSource C numberLines"> if (gtk_expression_evaluate (expression, label, &value)) g_print ("The value is %s.\n", g_value_get_string (&value)); else - g_print ("The constant expression wasn't evaluated correctly.\n"); + g_print ("The property expression wasn't evaluated correctly.\n"); gtk_expression_unref (expression); g_value_unset (&value); @@ -311,8 +311,8 @@ the expression with a ‘this’ instance label. The result is stored in the GValue value. The function g_value_get_string gets a string from the GValue. But the string is owned by the GValue so you must not free the string. -

  • 18-19: Release the expression and unset the GValue. At the same time -the string in the GValue is freed.
  • +
  • 18-19: Releases the expression and unset the GValue. At the same +time the string in the GValue is freed.
  • If the second argument of gtk_property_expression_new isn’t NULL, it is another expression. The expression is owned by a newly @@ -334,79 +334,44 @@ respectively. When you program in C language, GtkCClosureExpression and GCClosure are appropriate.

    A closure expression is created with gtk_cclosure_expression_new function.

    -
    GtkExpression *
    -gtk_cclosure_expression_new (GType value_type,
    -                             GClosureMarshal marshal,
    -                             guint n_params,
    -                             GtkExpression **params,
    -                             GCallback callback_func,
    -                             gpointer user_data,
    -                             GClosureNotify user_destroy);
    -
      -
    • value_type is the type of the value when it is -evaluated.
    • -
    • marshal is a marshaller. You can assign NULL. If it is -NULL, then g_cclosure_marshal_generic () is used as a -marshaller. It is a generic marshaller function implemented via -libffi.
    • -
    • n_params is the number of parameters.
    • -
    • params points expressions for each parameter of the -call back function.
    • -
    • callback_func is a callback function. It is given -arguments this and parameters above. So, if -n_params is 3, the number of arguments of the function is -4. (this and params. See below.)
    • -
    • user_data is user data. You can add it for the closure. -It is like user_data in g_signal_connect. If -it is not necessary, assign NULL.
    • -
    • user_destroy is a destroy notify for -user_data. It is called to destroy user_data -when it is no longer needed. If NULL is assigned to -user_data, assign NULL to user_destroy, -too.
    • -
    -

    Call back functions have the following format.

    -
    C-type
    -callback (this, param1, param2, ...)
    -

    For example,

    -
    int
    -callback (GObject *object, int x, const char *s)
    +
    int
    +callback (GObject *object, int x, const char *s)

    The following is exp_closure_simple.c in src/expression.

    -
    #include <gtk/gtk.h>
    -
    -static int
    -calc (GtkLabel *label) { /* this object */
    -  const char * s;
    -  int i, j;
    -
    -  s = gtk_label_get_text (label); /* s is owned by the label. */
    -  sscanf (s, "%d+%d", &i, &j);
    -  return i+j;
    -}
    -
    -int
    -main (int argc, char **argv) {
    -  GtkExpression *expression;
    -  GValue value = G_VALUE_INIT;
    -  GtkLabel *label;
    -
    -  gtk_init ();
    -  label = GTK_LABEL (gtk_label_new ("123+456"));
    -  g_object_ref_sink (label);
    -  expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 0, NULL,
    -                 G_CALLBACK (calc), NULL, NULL);
    -  if (gtk_expression_evaluate (expression, label, &value)) /* 'this' object is the label. */
    -    g_print ("%d\n", g_value_get_int (&value));
    -  else
    -    g_print ("The closure expression wasn't evaluated correctly.\n");
    -  gtk_expression_unref (expression);
    -  g_value_unset (&value);
    -  g_object_unref (label);
    -  
    -  return 0;
    -}
    +
    #include <gtk/gtk.h>
    +
    +static int
    +calc (GtkLabel *label) { /* this object */
    +  const char * s;
    +  int i, j;
    +
    +  s = gtk_label_get_text (label); /* s is owned by the label. */
    +  sscanf (s, "%d+%d", &i, &j);
    +  return i+j;
    +}
    +
    +int
    +main (int argc, char **argv) {
    +  GtkExpression *expression;
    +  GValue value = G_VALUE_INIT;
    +  GtkLabel *label;
    +
    +  gtk_init ();
    +  label = GTK_LABEL (gtk_label_new ("123+456"));
    +  g_object_ref_sink (label);
    +  expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 0, NULL,
    +                 G_CALLBACK (calc), NULL, NULL);
    +  if (gtk_expression_evaluate (expression, label, &value)) /* 'this' object is the label. */
    +    g_print ("%d\n", g_value_get_int (&value));
    +  else
    +    g_print ("The closure expression wasn't evaluated correctly.\n");
    +  gtk_expression_unref (expression);
    +  g_value_unset (&value);
    +  g_object_unref (label);
    +  
    +  return 0;
    +}
    • 3-11: A call back function. The parameter is only one and it is a ‘this’ object. It is a GtkLabel and its label is assumed to be @@ -419,16 +384,17 @@ argument of gtk_cclosure_expression_new is G_TYPE_POINTER. There is a sample program exp_closure_with_error_report in src/expression directory.
    • -
    • 19: gtk_init initializes GTK. It is necessary for GtkLabel.
    • +
    • 19: The function `gtk_init`` initializes GTK. It is necessary for +GtkLabel.
    • 20: A GtkLabel instance is created with “123+456”.
    • 21: The instance has floating reference. It is changed to an ordinary reference count.
    • -
    • 22-23: Create a closure expression. Its return value type is +
    • 22-23: Creates a closure expression. Its return value type is G_TYPE_INT and no parameters or ‘this’ object.
    • 24: Evaluates the expression with the label as a ‘this’ object.
    • -
    • 25: If the evaluation successes, show the sum of “123+456”. It’s -579.
    • -
    • 27: If it fails, show an error message.
    • +
    • 25: If the evaluation successes, the sum of “123+456”, which is 579, +is shown.
    • +
    • 27: If it fails, an error message appears.
    • 28-30: Releases the expression and the label. Unsets the value.

    Closure expression is flexible than other type of expression because @@ -436,18 +402,19 @@ you can specify your own callback function.

    GtkExpressionWatch

    GtkExpressionWatch is a structure, not an object. It represents a watched GtkExpression. Two functions create GtkExpressionWatch -structure.

    +structure. They are gtk_expression_bind and +gtk_expression_watch.

    gtk_expression_bind function

    This function binds the target object’s property to the expression. If the value of the expression changes, the property reflects the value immediately.

    -
    GtkExpressionWatch*
    -gtk_expression_bind (
    -  GtkExpression* self,
    -  GObject* target,
    -  const char* property,
    -  GObject* this_
    -)
    +
    GtkExpressionWatch*
    +gtk_expression_bind (
    +  GtkExpression* self,
    +  GObject* target,
    +  const char* property,
    +  GObject* this_
    +)

    This function takes the ownership of the expression. So, if you want to own the expression, call gtk_expression_ref () to increase the reference count of the expression. And you should unref it @@ -463,41 +430,41 @@ about releasing the expression.

    the scale value increases and the number on the label also increases. In the same way, if you move it to the left, the number on the label decreases. The label is bound to the scale value via an adjustment.

    -
    <?xml version='1.0' encoding='UTF-8'?>
    -<interface>
    -  <object class='GtkApplicationWindow' id='win'>
    -    <property name='default-width'>600</property>
    -    <child>
    -      <object class='GtkBox'>
    -        <property name='orientation'>GTK_ORIENTATION_VERTICAL</property>
    -        <child>
    -          <object class='GtkLabel' id='label'>
    -            <property name="label">10</property>
    -          </object>
    -        </child>
    -        <child>
    -          <object class='GtkScale'>
    -            <property name='adjustment'>
    -              <object class='GtkAdjustment' id='adjustment'>
    -                <property name='upper'>20.0</property>
    -                <property name='lower'>0.0</property>
    -                <property name='value'>10.0</property>
    -                <property name='step-increment'>1.0</property>
    -                <property name='page-increment'>5.0</property>
    -                <property name='page-size'>0.0</property>
    -              </object>
    -            </property>
    -            <property name='digits'>0</property>
    -            <property name='draw-value'>true</property>
    -            <property name='has-origin'>true</property>
    -            <property name='round-digits'>0</property>
    -          </object>
    -        </child>
    -      </object>
    -    </child>
    -  </object>
    -</interface>
    +
    <?xml version='1.0' encoding='UTF-8'?>
    +<interface>
    +  <object class='GtkApplicationWindow' id='win'>
    +    <property name='default-width'>600</property>
    +    <child>
    +      <object class='GtkBox'>
    +        <property name='orientation'>GTK_ORIENTATION_VERTICAL</property>
    +        <child>
    +          <object class='GtkLabel' id='label'>
    +            <property name="label">10</property>
    +          </object>
    +        </child>
    +        <child>
    +          <object class='GtkScale'>
    +            <property name='adjustment'>
    +              <object class='GtkAdjustment' id='adjustment'>
    +                <property name='upper'>20.0</property>
    +                <property name='lower'>0.0</property>
    +                <property name='value'>10.0</property>
    +                <property name='step-increment'>1.0</property>
    +                <property name='page-increment'>5.0</property>
    +                <property name='page-size'>0.0</property>
    +              </object>
    +            </property>
    +            <property name='digits'>0</property>
    +            <property name='draw-value'>true</property>
    +            <property name='has-origin'>true</property>
    +            <property name='round-digits'>0</property>
    +          </object>
    +        </child>
    +      </object>
    +    </child>
    +  </object>
    +</interface>

    The ui file describes the following parent-child relationship.

    GtkApplicationWindow (win) -- GtkBox -+- GtkLabel (label)
                                           +- GtkScale
    @@ -523,73 +490,73 @@ orange bar appears between the origin and the current point. changes. For example, if it is zero, the slider moves to an integer point. -
    #include <gtk/gtk.h>
    -
    -GtkExpressionWatch *watch;
    -
    -static int
    -f2i (GObject *object, double d) {
    -  return (int) d;
    -}
    -
    -static int
    -close_request_cb (GtkWindow *win) {
    -  gtk_expression_watch_unwatch (watch);
    -  return false;
    -}
    -
    -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);
    -  GtkBuilder *build;
    -  GtkWidget *win, *label;
    -  GtkAdjustment *adjustment;
    -  GtkExpression *expression, *params[1];
    -
    -  /* Builds a window with exp.ui resource */
    -  build = gtk_builder_new_from_file ("exp_bind.ui");
    -  win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
    -  label = GTK_WIDGET (gtk_builder_get_object (build, "label"));
    -  // scale = GTK_WIDGET (gtk_builder_get_object (build, "scale"));
    -  adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (build, "adjustment"));
    -  gtk_window_set_application (GTK_WINDOW (win), app);
    -  g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL);
    -  g_object_unref (build);
    -
    -  /* GtkExpressionWatch */
    -  params[0] = gtk_property_expression_new (GTK_TYPE_ADJUSTMENT, NULL, "value");
    -  expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 1, params, G_CALLBACK (f2i), NULL, NULL);
    -  watch = gtk_expression_bind (expression, label, "label", adjustment); /* watch takes the ownership of the expression. */
    -}
    -
    -#define APPLICATION_ID "com.github.ToshioCP.exp_watch"
    -
    -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;
    -}
    +
    #include <gtk/gtk.h>
    +
    +GtkExpressionWatch *watch;
    +
    +static int
    +f2i (GObject *object, double d) {
    +  return (int) d;
    +}
    +
    +static int
    +close_request_cb (GtkWindow *win) {
    +  gtk_expression_watch_unwatch (watch);
    +  return false;
    +}
    +
    +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);
    +  GtkBuilder *build;
    +  GtkWidget *win, *label;
    +  GtkAdjustment *adjustment;
    +  GtkExpression *expression, *params[1];
    +
    +  /* Builds a window with exp.ui resource */
    +  build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp_bind.ui");
    +  win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
    +  label = GTK_WIDGET (gtk_builder_get_object (build, "label"));
    +  // scale = GTK_WIDGET (gtk_builder_get_object (build, "scale"));
    +  adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (build, "adjustment"));
    +  gtk_window_set_application (GTK_WINDOW (win), app);
    +  g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL);
    +  g_object_unref (build);
    +
    +  /* GtkExpressionWatch */
    +  params[0] = gtk_property_expression_new (GTK_TYPE_ADJUSTMENT, NULL, "value");
    +  expression = gtk_cclosure_expression_new (G_TYPE_INT, NULL, 1, params, G_CALLBACK (f2i), NULL, NULL);
    +  watch = gtk_expression_bind (expression, label, "label", adjustment); /* watch takes the ownership of the expression. */
    +}
    +
    +#define APPLICATION_ID "com.github.ToshioCP.exp_watch"
    +
    +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;
    +}

    The point of the program is:

    • 41-42: Two expressions are defined. One is a property expression and -the other is a closure expression. The property expression look up the -“value”property of the adjustment instance. The closure expression just +the other is a closure expression. The property expression looks up the +“value” property of the adjustment instance. The closure expression just converts the double into an integer.
    • 43: gtk_expression_bind binds the label property of the GtkLabel instance to the integer returned by the closure expression. It @@ -603,24 +570,23 @@ is destroyed.
    • close_request_cb. This signal is emitted when the close button is clicked. The handler is called just before the window closes. It is the right moment to make the GtkExpressionWatch unwatched. -
    • 10-14: “close-request” signal handler. +
    • 10-14: “close-request” signal handler. The function gtk_expression_watch_unwatch (watch) makes the watch stop -watching the expression. It releases the expression and calls -gtk_expression_watch_unref (watch) in it.
    • +watching the expression. It also releases the expression.

    If you want to bind a property to an expression, gtk_expression_bind is the best choice. You can do it with gtk_expression_watch function, but it is less suitable.

    gtk_expression_watch function

    -
    GtkExpressionWatch*
    -gtk_expression_watch (
    -  GtkExpression* self,
    -  GObject* this_,
    -  GtkExpressionNotify notify,
    -  gpointer user_data,
    -  GDestroyNotify user_destroy
    -)
    +
    GtkExpressionWatch*
    +gtk_expression_watch (
    +  GtkExpression* self,
    +  GObject* this_,
    +  GtkExpressionNotify notify,
    +  gpointer user_data,
    +  GDestroyNotify user_destroy
    +)

    The function doesn’t take the ownership of the expression. It differs from gtk_expression_bind. So, you need to release the expression when it is useless. It creates a GtkExpressionWatch @@ -630,10 +596,10 @@ to give it to the callback. The last parameter is a function to destroy the user_data when the watch is unwatched. Put NULL if you don’t need them.

    Notify callback has the following format.

    -
    void
    -notify (
    -  gpointer user_data
    -)
    +
    void
    +notify (
    +  gpointer user_data
    +)

    This function is used to do something when the value of the expression changes. But if you want to bind a property to the value, use gtk_expression_bind instead.

    @@ -646,63 +612,63 @@ window to the standard output.

    When you resize the window, the width is displayed in the terminal.

    -
    #include <gtk/gtk.h>
    -
    -GtkExpression *expression;
    -GtkExpressionWatch *watch;
    -
    -static void
    -notify (gpointer user_data) {
    -  GValue value = G_VALUE_INIT;
    -
    -  if (gtk_expression_watch_evaluate (watch, &value))
    -    g_print ("width = %d\n", g_value_get_int (&value));
    -  else
    -    g_print ("evaluation failed.\n");
    -}
    -
    -static int
    -close_request_cb (GtkWindow *win) {
    -  gtk_expression_watch_unwatch (watch);
    -  gtk_expression_unref (expression);
    -  return false;
    -}
    -
    -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);
    -  GtkWidget *win;
    -
    -  win = GTK_WIDGET (gtk_application_window_new (app));
    -  g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL);
    -
    -  expression = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-width");
    -  watch = gtk_expression_watch (expression, win, notify, NULL, NULL);
    -}
    -
    -#define APPLICATION_ID "com.github.ToshioCP.exp_watch"
    -
    -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;
    -}
    +
    #include <gtk/gtk.h>
    +
    +GtkExpression *expression;
    +GtkExpressionWatch *watch;
    +
    +static void
    +notify (gpointer user_data) {
    +  GValue value = G_VALUE_INIT;
    +
    +  if (gtk_expression_watch_evaluate (watch, &value))
    +    g_print ("width = %d\n", g_value_get_int (&value));
    +  else
    +    g_print ("evaluation failed.\n");
    +}
    +
    +static int
    +close_request_cb (GtkWindow *win) {
    +  gtk_expression_watch_unwatch (watch);
    +  gtk_expression_unref (expression);
    +  return false;
    +}
    +
    +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);
    +  GtkWidget *win;
    +
    +  win = GTK_WIDGET (gtk_application_window_new (app));
    +  g_signal_connect (win, "close-request", G_CALLBACK (close_request_cb), NULL);
    +
    +  expression = gtk_property_expression_new (GTK_TYPE_WINDOW, NULL, "default-width");
    +  watch = gtk_expression_watch (expression, win, notify, NULL, NULL);
    +}
    +
    +#define APPLICATION_ID "com.github.ToshioCP.exp_watch"
    +
    +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;
    +}
    • 37: A property expression looks up the “default-width” property of the window.
    • @@ -738,37 +704,36 @@ expressions. content of an object tag. Name attribute specifies the property name of the object. The content is an expression.
    -
    <constant type="gchararray">Hello world</constant>
    -<lookup name="label" type="GtkLabel">label</lookup>
    -<closure type="gint" function="callback_function"></closure>
    -<bind name="label">
    -  <lookup name="default-width">win</lookup>
    -</bind>
    +
    <constant type="gchararray">Hello world</constant>
    +<lookup name="label" type="GtkLabel">label</lookup>
    +<closure type="gint" function="callback_function"></closure>
    +<bind name="label">
    +  <lookup name="default-width">win</lookup>
    +</bind>

    These tags are usually used for GtkBuilderListItemFactory.

    -
    <interface>
    -  <template class="GtkListItem">
    -    <property name="child">
    -      <object class="GtkLabel">
    -        <binding name="label">
    -          <lookup name="name" type="string">
    -            <lookup name="item">GtkListItem</lookup>
    -          </lookup>
    -        </binding>
    -      </object>
    -    </property>
    -  </template>
    -</interface>
    +
    <interface>
    +  <template class="GtkListItem">
    +    <property name="child">
    +      <object class="GtkLabel">
    +        <binding name="label">
    +          <lookup name="string" type="GtkStringObject">
    +            <lookup name="item">GtkListItem</lookup>
    +          </lookup>
    +        </binding>
    +      </object>
    +    </property>
    +  </template>
    +</interface>
    +

    GtkBuilderListItemFactory uses GtkBuilder to extends the GtkListItem +with the XML data.

    In the xml file above, “GtkListItem” is an instance of the GtkListItem template. It is the ‘this’ object given to the expressions. (The information is in the GTK Development Blog).

    -

    GtkBuilderListItemFactory uses GtkBuilder to build the XML data. It -sets the current object of the GtkBuilder to the GtkListItem -instance.

    -

    GtkBuilder calls gtk_expression_bind function in the -binding tag analysis. The function sets the ‘this’ object like this:

    +

    GtkBuilder calls gtk_expression_bind function when it +finds a binding tag. The function sets the ‘this’ object like this:

    1. If the binding tag has object attribute, the object will be the ‘this’ object.
    2. @@ -794,41 +759,41 @@ constant tags are not used so often.

      window. If you type characters in the entry, the same characters appear on the label.

      The ui file is as follows.

      -
      <?xml version="1.0" encoding="UTF-8"?>
      -<interface>
      -  <object class="GtkApplicationWindow" id="win">
      -    <binding name="title">
      -      <closure type="gchararray" function="set_title">
      -        <lookup name="default-width" type="GtkWindow"></lookup>
      -        <lookup name="default-height" type="GtkWindow"></lookup>
      -      </closure>
      -    </binding>
      -    <property name="default-width">600</property>
      -    <property name="default-height">400</property>
      -    <child>
      -      <object class="GtkBox">
      -        <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
      -        <child>
      -          <object class="GtkLabel">
      -            <binding name="label">
      -              <lookup name="text">
      -                buffer
      -              </lookup>
      -            </binding>
      -          </object>
      -        </child>
      -        <child>
      -          <object class="GtkEntry">
      -            <property name="buffer">
      -              <object class="GtkEntryBuffer" id="buffer"></object>
      -            </property>
      -          </object>
      -        </child>
      -      </object>
      -    </child>
      -  </object>
      -</interface>
      +
      <?xml version="1.0" encoding="UTF-8"?>
      +<interface>
      +  <object class="GtkApplicationWindow" id="win">
      +    <binding name="title">
      +      <closure type="gchararray" function="set_title">
      +        <lookup name="default-width" type="GtkWindow"></lookup>
      +        <lookup name="default-height" type="GtkWindow"></lookup>
      +      </closure>
      +    </binding>
      +    <property name="default-width">600</property>
      +    <property name="default-height">400</property>
      +    <child>
      +      <object class="GtkBox">
      +        <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
      +        <child>
      +          <object class="GtkLabel">
      +            <binding name="label">
      +              <lookup name="text">
      +                buffer
      +              </lookup>
      +            </binding>
      +          </object>
      +        </child>
      +        <child>
      +          <object class="GtkEntry">
      +            <property name="buffer">
      +              <object class="GtkEntryBuffer" id="buffer"></object>
      +            </property>
      +          </object>
      +        </child>
      +      </object>
      +    </child>
      +  </object>
      +</interface>
      • 4-9: The title property of the main window is bound to a closure expression. Its callback function set_title is defined in @@ -844,48 +809,48 @@ defined in line 25. If a user types characters in the entry, the same characters appear on the label.

      The C source file is as follows.

      -
      #include <gtk/gtk.h>
      -
      -char *
      -set_title (GtkWidget *win, int width, int height) {
      -  return g_strdup_printf ("%d x %d", width, height);
      -}
      -
      -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);
      -  GtkBuilder *build;
      -  GtkWidget *win;
      -
      -  build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp.ui");
      -  win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
      -  gtk_window_set_application (GTK_WINDOW (win), app);
      -  g_object_unref (build);
      -}
      -
      -#define APPLICATION_ID "com.github.ToshioCP.exp"
      -
      -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;
      -}
      +
      #include <gtk/gtk.h>
      +
      +char *
      +set_title (GtkWidget *win, int width, int height) {
      +  return g_strdup_printf ("%d x %d", width, height);
      +}
      +
      +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);
      +  GtkBuilder *build;
      +  GtkWidget *win;
      +
      +  build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp.ui");
      +  win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
      +  gtk_window_set_application (GTK_WINDOW (win), app);
      +  g_object_unref (build);
      +}
      +
      +#define APPLICATION_ID "com.github.ToshioCP.exp"
      +
      +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;
      +}
      • 4-6: The callback function. It returns a string (w)x(h), where the w and h are the width and height of the window. String duplication is @@ -893,18 +858,18 @@ necessary.

      The C source file is very simple because almost everything is done in the ui file.

      -

      Conversion between GValues

      +

      Conversion between GValues

      If you bind different type properties, type conversion is automatically done. Suppose a label property (string) is bound to default-width property (int).

      -
      <object class="GtkLabel">
      -  <binding name="label">
      -    <lookup name="default-width">
      -      win
      -    </lookup>
      -  </binding>
      -</object>
      +
      <object class="GtkLabel">
      +  <binding name="label">
      +    <lookup name="default-width">
      +      win
      +    </lookup>
      +  </binding>
      +</object>

      The expression created by the lookup tag returns a int type GValue. On the other hand “label” property holds a string type GValue. When a GValue is copied to another GValue, the type is automatically converted @@ -912,6 +877,37 @@ if possible. If the current width is 100, an int 100 is converted to a string "100".

      If you use g_object_get and g_object_set to copy properties, the value is automatically converted.

      +

      Meson.build

      +

      The source files are in src/expression directory. You +can build all the files at once.

      +
      $ cd src/expression
      +$ meson setup _build
      +$ ninja -C _build
      +

      For example, if you want to run “exp”, which is the executable file +from “exp.c”, type _build/exp. You can run other programs +as well.

      +

      The file meson.build is as follows.

      +
      project('exp', 'c')
      +
      +gtkdep = dependency('gtk4')
      +
      +gnome=import('gnome')
      +resources = gnome.compile_resources('resources','exp.gresource.xml')
      +
      +sourcefiles=files('exp.c')
      +
      +executable('exp', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_constant', 'exp_constant.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_constant_simple', 'exp_constant_simple.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_property_simple', 'exp_property_simple.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('closure', 'closure.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('closure_each', 'closure_each.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_closure_simple', 'exp_closure_simple.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_closure_with_error_report', 'exp_closure_with_error_report.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_bind', 'exp_bind.c', resources, dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_watch', 'exp_watch.c', dependencies: gtkdep, export_dynamic: true, install: false)
      +executable('exp_test', 'exp_test.c', resources, dependencies: gtkdep, export_dynamic: true, install: false)
    diff --git a/docs/sec32.html b/docs/sec32.html index 5026a1f..42f6898 100644 --- a/docs/sec32.html +++ b/docs/sec32.html @@ -328,10 +328,9 @@ header of the column. as much as possible. (See the image above).
  • 33- 69: Sets the “factory” property to GtkBuilderListItemFactory. The factory has “bytes” property which holds a ui string to define a -template to build GtkListItem composite widget. The CDATA section (line -36-66) is the ui string to put into the “bytes” property. The contents -are the same as the ui file factory_list.ui in the section -27.
  • +template to extend GtkListItem class. The CDATA section (line 36-66) is +the ui string to put into the “bytes” property. The contents are the +same as the ui file factory_list.ui in the section 30.
  • 70-77: Sets the “sorter” property to GtkStringSorter object. This object provides a sorter that compares strings. It has “expression” property. A closure tag with a string type function @@ -542,7 +541,8 @@ class="sourceCode numberSource C numberLines">Compilation and execution.

    All the source files are in src/column directory. Change your current directory to the directory and type the following.

    -
    $ meson _build
    +
    $ cd src/colomn
    +$ meson setup _build
     $ ninja -C _build
     $ _build/column

    Then, a window appears.

    diff --git a/docs/sec33.html b/docs/sec33.html index 3788a8d..aad14ea 100644 --- a/docs/sec33.html +++ b/docs/sec33.html @@ -116,25 +116,14 @@ and GtkBulderListItemFactory 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 +suppose 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 +

    This section shows just some parts of the source file listeditor.c. If you want to see the whole codes, see src/listeditor directory of the Gtk4 tutorial @@ -143,7 +132,7 @@ repository.

    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 +There are two columns. The one is a button, which shows if the line is 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.

    @@ -159,18 +148,19 @@ href="https://github.com/ToshioCP/Gtk4-tutorial">repository.
  • Change your current directory to src/listeditor.
  • Type the following on your commandline.
  • -
    $ meson _build
    +
    $ meson setup _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.
    • +
    • Insert button: inserts a line before the current line, or at the top +line if no current line exists.
    • 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.
    • +
    • close button: closes the contents.
    • +
    • quit button: quits the application.
    • Button on the select column: makes the line current.
    • String column: GtkText. You can edit a string in the field.
    @@ -180,7 +170,7 @@ bar. The file name is shown at the right of the write button.

    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.

    +The following shows the signal handlers.

    static void
     setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    @@ -193,7 +183,7 @@ class="sourceCode numberSource C numberLines">bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
       GtkWidget *text = gtk_list_item_get_child (listitem);
       GtkEntryBuffer *buffer = gtk_text_get_buffer (GTK_TEXT (text));
    -  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    +  LeData *data = LE_DATA (gtk_list_item_get_item(listitem));
       GBinding *bind;
     
       gtk_editable_set_text (GTK_EDITABLE (text), le_data_look_string (data));
    @@ -207,9 +197,10 @@ class="sourceCode numberSource C numberLines">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);
    -}
    + if (bind) + g_binding_unbind(bind); + g_object_set_data (G_OBJECT (listitem), "bind", NULL); +}
    • 1-6: setup2_cb is a setup signal handler on the GtkSignalListItemFactory. This factory is inserted to the factory @@ -221,22 +212,18 @@ is destroyed. So, teardown signal handler isn’t necessary.
    • 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 first column GtkListItem instance -when they are connected. Be careful that the GtkListItem instance is -not the listitem in this handler. If no -GtkListItem is connected, it is NULL. The other is string -which is a content of the line. +a child class of GObject and has string data which is the content of the +line.
      • 10-11: text is a child of the listitem and -it is a GtkText instance. And buffer is a GtkTextBuffer +it is a GtkText instance. And buffer is a GtkEntryBuffer instance of the text.
      • 12: The LeData instance data is an item pointed by the listitem.
      • 15-16: Sets the text of text to le_data_look_string (data). le_data_look_string returns the string of the data and the ownership of the string is still -taken by the data. So, the caller don’t need to free the +taken by the data. So, the caller doesn’t need to free the string.
      • 18: g_object_bind_property binds a property and another object property. This line binds the “text” property of the @@ -254,16 +241,22 @@ value. This line sets the association from “bind” to bind instance. It makes possible for the “unbind” handler to get the bind instance.
      -
    • 22-28: unbind2_cb is a unbind signal handler. +
    • 22-29: unbind2_cb is a unbind signal handler.
      • 24: Retrieves the bind instance from the table in the listitem instance.
      • -
      • 26: Unbind the binding.
      • -
      • 27: Removes the value corresponds to the “bind” key.
      • +
      • 26-27: Unbind the binding.
      • +
      • 28: 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.

    +

    If it is impossible to use g_object_bind_property, use a +notify signal on the GtkEntryBuffer instance. You can use “deleted-text” +and “inserted-text” signal instead. The handler of the signals above +copies the text in the GtkEntryBuffer instance to the LeData string. +Connect the notify signal handler in bind2_cb and +disconnect it in unbind2_cb.

    Change the cell of GtkColumnView dynamically

    Next topic is to change the GtkColumnView (or GtkListView) cells @@ -289,164 +282,148 @@ 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 bound to the item, NULL is -assigned.

    +

    The LeWindow instance has two instance variables for recording the +current line.

    +
      +
    • win->position: An int type variable. It is the +position of the current line. It is zero-based. If no current line +exists, it is -1.
    • +
    • win->current_button: A variable points the button, +located at the first column, on the current line. If no current line +exists, it is NULL.
    • +
    +

    If the current line moves, the following two functions are called. +They updates the two varables.

    void
    -select_cb (GtkButton *btn, GtkListItem *listitem) {
    -  LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW));
    +class="sourceCode numberSource C numberLines">static void
    +update_current_position (LeWindow *win, int new) {
    +  char *s;
     
    -  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);
    -  gtk_widget_set_focusable (GTK_WIDGET (button), FALSE);
    -  g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
    -}
    -
    -static void
    -bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) {
    -  LeWindow *win = LE_WINDOW (user_data);
    -  GtkWidget *button = gtk_list_item_get_child (listitem);
    -  const char *non_current[1] = {NULL};
    -  const char *current[2] = {"current", NULL};
    -  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    -
    -  if (data) {
    -    le_data_set_listitem (data, listitem);
    -    if (win->position == gtk_list_item_get_position (listitem))
    -      gtk_widget_set_css_classes (GTK_WIDGET (button), current);
    -    else
    -      gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
    -  }
    -}
    -
    -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-14: 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 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 the 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.
    • -
    • 16-31: 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. If the item is the -current line, the CSS class of the button includes “current” class. -Otherwise it has no CSS class. This is necessary because the button may -be recycled and it has had former CSS class. The class need to be -updated.
    • -
    • 33-38: 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.
    • -
    + win->position = new; + if (win->position >= 0) + s = g_strdup_printf ("%d", win->position); + else + s = ""; + gtk_label_set_text (GTK_LABEL (win->position_label), s); + if (*s) // s isn't an empty string + g_free (s); +} + +static void +update_current_button (LeWindow *win, GtkButton *new_button) { + const char *non_current[1] = {NULL}; + const char *current[2] = {"current", NULL}; + + if (win->current_button) { + gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), non_current); + g_object_unref (win->current_button); + } + win->current_button = new_button; + if (win->current_button) { + g_object_ref (win->current_button); + gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), current); + } +}
    +

    The varable win->position_label points a GtkLabel +instance. The label shows the current line position.

    +

    The current button has CSS “current” class. The button is colored red +through the CSS “button.current {background: red;}”.

    +

    The order of the call for these two functions is important. The first +function, which updates the position, is usually called first. After +that, a new line is appended or inserted. Then, the second function is +called.

    +

    The following functions call the two functions above. Be careful +about the order of the call.

    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.

    +class="sourceCode numberSource C numberLines">void +select_cb (GtkButton *btn, GtkListItem *listitem) { + LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW)); + + update_current_position (win, gtk_list_item_get_position (listitem)); + update_current_button (win, btn); +} + +static void +setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { + GtkWidget *button = gtk_button_new (); + gtk_list_item_set_child (listitem, button); + gtk_widget_set_focusable (GTK_WIDGET (button), FALSE); + g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem); +} + +static void +bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { + LeWindow *win = LE_WINDOW (user_data); + GtkWidget *button = gtk_list_item_get_child (listitem); + + if (win->position == gtk_list_item_get_position (listitem)) + update_current_button (win, GTK_BUTTON (button)); +}
      -
    • 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 +
    • 1-7: select_cb is a “clicked” signal handler. The +handler just calls the two functions and update the position and 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.
    • +
    • 9-15: setup1_cb is a setup signal handler on the +GtkSignalListItemFactory. It sets the child of listitem to +a 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 the handler is also destroyed. So, you don’t need +teardown signal handler.
    • +
    • 17-24: bind1_cb is a bind signal handler. Usually, the +position moves before this handler is called. If the item is on the +current line, the button is updated. No unbind handler is +necessary.
    -

    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).

    +

    When a line is added, the current position is updated in advance.

    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.

    +class="sourceCode numberSource C numberLines">static void +app_cb (GtkButton *btn, LeWindow *win) { + LeData *data = le_data_new_with_data (""); + + if (win->position >= 0) { + update_current_position (win, win->position + 1); + g_list_store_insert (win->liststore, win->position, data); + } else { + update_current_position (win, g_list_model_get_n_items (G_LIST_MODEL (win->liststore))); + g_list_store_append (win->liststore, data); + } + g_object_unref (data); +} + +static void +ins_cb (GtkButton *btn, LeWindow *win) { + LeData *data = le_data_new_with_data (""); + + if (win->position >= 0) + g_list_store_insert (win->liststore, win->position, data); + else { + update_current_position (win, 0); + g_list_store_insert (win->liststore, 0, data); + } + g_object_unref (data); +}
    +

    When a line is removed, the current position becomes -1 and no button +is current.

    +
    static void
    +rm_cb (GtkButton *btn, LeWindow *win) {
    +  if (win->position >= 0) {
    +    g_list_store_remove (win->liststore, win->position);
    +    update_current_position (win, -1);
    +    update_current_button (win, NULL);
    +  }
    +}
    +

    The color of buttons are determined by the “background” CSS style. +The following CSS node is a bit complicated. CSS node +column view has listview child node. It covers +the rows in the GtkColumnView. The listview node is the +same as the one for GtkListView. It has row child node, +which is for each child widget. Therefore, the following node +corresponds buttons on the GtkColumnView widget. In addition, it is +applied to the “current” class.

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

    A waring from GtkText

    If your program has the following two, a warning message can be issued.

    @@ -462,24 +439,24 @@ probably comes from focus and scroll.

    window. When scroll begins, the “value-changed” signal on the vertical adjustment of the scrolled window is emitted.

    The following is extracted from the ui file and C source file.

    -
    ... ... ...
    -<object class="GtkScrolledWindow">
    -  <property name="hexpand">TRUE</property>
    -  <property name="vexpand">TRUE</property>
    -  <property name="vadjustment">
    -    <object class="GtkAdjustment">
    -      <signal name="value-changed" handler="adjustment_value_changed_cb" swapped="no" object="LeWindow"/>
    -    </object>
    -  </property>
    -... ... ...  
    -
    static void
    -adjustment_value_changed_cb (GtkAdjustment *adjustment, gpointer user_data) {
    -  GtkWidget *win = GTK_WIDGET (user_data);
    -
    -  gtk_window_set_focus (GTK_WINDOW (win), NULL);
    -}
    +
    ... ... ...
    +<object class="GtkScrolledWindow">
    +  <property name="hexpand">TRUE</property>
    +  <property name="vexpand">TRUE</property>
    +  <property name="vadjustment">
    +    <object class="GtkAdjustment">
    +      <signal name="value-changed" handler="adjustment_value_changed_cb" swapped="no" object="LeWindow"/>
    +    </object>
    +  </property>
    +... ... ...  
    +
    static void
    +adjustment_value_changed_cb (GtkAdjustment *adjustment, gpointer user_data) {
    +  GtkWidget *win = GTK_WIDGET (user_data);
    +
    +  gtk_window_set_focus (GTK_WINDOW (win), NULL);
    +}
    diff --git a/docs/sec34.html b/docs/sec34.html deleted file mode 100644 index 1dbb942..0000000 --- a/docs/sec34.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - 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));
    -  gtk_editable_set_alignment (GTK_EDITABLE (text), 0.0);
    -}
    -
    -static void
    -bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
    -  GtkWidget *text = gtk_list_item_get_child (listitem);
    -  GtkEntryBuffer *buffer = gtk_text_get_buffer (GTK_TEXT (text));
    -  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    -  GBinding *bind;
    -
    -  gtk_editable_set_text (GTK_EDITABLE (text), le_data_look_string (data));
    -  gtk_editable_set_position (GTK_EDITABLE (text), 0);
    -
    -  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-6: setup2_cb is a setup signal handler on the -GtkSignalListItemFactory. This factory is inserted to the factory -property of the second GtkColumnViewColumn. The 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.
    • -
    • 8-20: 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 first column GtkListItem instance -when they are connected. Be careful that the GtkListItem instance is -not the listitem in this handler. If no -GtkListItem is connected, it is NULL. The other is string -which is a content of the line. -
        -
      • 10-11: text is a child of the listitem and -it is a GtkText instance. And buffer is a GtkTextBuffer -instance of the text.
      • -
      • 12: The LeData instance data is an item pointed by the -listitem.
      • -
      • 15-16: Sets the text of text to -le_data_look_string (data). le_data_look_string returns the -string of the data and the ownership of the string is still -taken by the data. So, the caller don’t need to free the -string.
      • -
      • 18: 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.
      • -
      • 19: 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. It makes possible for the “unbind” handler to get the -bind instance.
      • -
    • -
    • 22-28: unbind2_cb is a unbind signal handler. -
        -
      • 24: Retrieves the bind instance from the table in the -listitem instance.
      • -
      • 26: Unbind the binding.
      • -
      • 27: 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.
    • -
    • It is necessary to set the line status (whether current or not) when -a GtkListItem is bound to an item in the list. It is because GtkListItem -is recycled. A GtkListItem was possibly current line before but not -current after recycled. The opposite can also be happen.
    • -
    -

    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 bound 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);
    -  gtk_widget_set_focusable (GTK_WIDGET (button), FALSE);
    -  g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
    -}
    -
    -static void
    -bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) {
    -  LeWindow *win = LE_WINDOW (user_data);
    -  GtkWidget *button = gtk_list_item_get_child (listitem);
    -  const char *non_current[1] = {NULL};
    -  const char *current[2] = {"current", NULL};
    -  LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
    -
    -  if (data) {
    -    le_data_set_listitem (data, listitem);
    -    if (win->position == gtk_list_item_get_position (listitem))
    -      gtk_widget_set_css_classes (GTK_WIDGET (button), current);
    -    else
    -      gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
    -  }
    -}
    -
    -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-14: 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 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 the 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.
    • -
    • 16-31: 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. If the item is the -current line, the CSS class of the button includes “current” class. -Otherwise it has no CSS class. This is necessary because the button may -be recycled and it has had former CSS class. The class need to be -updated.
    • -
    • 33-38: 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.

    -

    A waring from GtkText

    -

    If your program has the following two, a warning message can be -issued.

    -
      -
    • The list has many items and it needs to be scrolled.
    • -
    • A GtkText instance is the focus widget.
    • -
    -
    GtkText - unexpected blinking selection. Removing
    -

    I don’t have an exact idea why this happens. But if GtkText -“focusable” property is FALSE, the warning doesn’t happen. So it -probably comes from focus and scroll.

    -

    You can avoid this by unsetting any focus widget under the main -window. When scroll begins, the “value-changed” signal on the vertical -adjustment of the scrolled window is emitted.

    -

    The following is extracted from the ui file and C source file.

    -
    ... ... ...
    -<object class="GtkScrolledWindow">
    -  <property name="hexpand">TRUE</property>
    -  <property name="vexpand">TRUE</property>
    -  <property name="vadjustment">
    -    <object class="GtkAdjustment">
    -      <signal name="value-changed" handler="adjustment_value_changed_cb" swapped="no" object="LeWindow"/>
    -    </object>
    -  </property>
    -... ... ...  
    -
    static void
    -adjustment_value_changed_cb (GtkAdjustment *adjustment, gpointer user_data) {
    -  GtkWidget *win = GTK_WIDGET (user_data);
    -
    -  gtk_window_set_focus (GTK_WINDOW (win), NULL);
    -}
    -
    - - - diff --git a/gfm/sec29.md b/gfm/sec29.md index 9bd726c..f1d7e9e 100644 --- a/gfm/sec29.md +++ b/gfm/sec29.md @@ -8,9 +8,10 @@ The new feature is described in [Gtk API Reference -- List Widget Overview](http GTK 4 has other means to implement lists. They are GtkListBox and GtkTreeView which are took over from GTK 3. There's an article in [Gtk Development blog](https://blog.gtk.org/2020/06/07/scalable-lists-in-gtk-4/) about list widgets by Matthias Clasen. -He described why GtkListView are developed to replace GtkListBox and GtkTreeView. +He described why GtkListView are developed to replace GtkTreeView. +GtkTreeView is deprecated since version 4.10. -GtkListView, GtkGridView, GtkColumnView and related objects are described in Section 26 to 29. +GtkListView, GtkGridView, GtkColumnView and related objects are described in Section 29 to 33. ## Outline @@ -21,7 +22,7 @@ A list is like an array, but in many cases it is implemented with pointers which And it has a start point. So, each item can be referred by the index of the item (first item, second item, ..., nth item, ...). There are two cases. -One is the index starts from one (one-based) and the other is from zero (zero-based). +The one is the index starts from one (one-based) and the other is from zero (zero-based). Gio provides GListModel interface. It is a zero-based list and its items are the same type of GObject descendants, or objects that implement the same interface. @@ -29,11 +30,11 @@ An object implements GListModel is not a widget. So, the list is not displayed on the screen directly. There's another object GtkListView which is a widget to display the list. The items in the list need to be connected to the items in GtkListView. -GtkListItemFactory instance maps items in the list to GListView. +GtkListItemFactory instance maps items in the list to GtkListView. ![List](../image/list.png) -## GListModel +## GListModel and GtkStringList If you want to make a list of strings with GListModel, for example, "one", "two", "three", "four", note that strings can't be items of the list. Because GListModel is a list of GObject objects and strings aren't GObject objects. @@ -253,7 +254,7 @@ There is an explanation in the [GTK Development Blog](https://blog.gtk.org/2020/ > Remember that the classname (GtkListItem) in a ui template is used as the “this” pointer referring to the object that is being instantiated. Therefore, GtkListItem instance is used as the `this` object of the lookup tag when it is evaluated. -`this` object will be explained in [section 30](../src/sec30). +`this` object will be explained in [section 31](../src/sec31). The C source code is as follows. Its name is `list2.c` and located under [src/misc](../src/misc) directory. @@ -370,7 +371,7 @@ Instead, closure tag is appropriate in this case. Closure tag specifies a function and the type of the return value of the function. ~~~C -const char * +char * get_file_name (GtkListItem *item, GFileInfo *info) { return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL; } @@ -406,7 +407,7 @@ The contents of closure tag (it is between \ and\) is pa `GtkListItem` gives the value of the item property of the GtkListItem. This will be the second argument of the function. The first parameter is always the GListItem instance, which is a 'this' object. -The 'this' object is explained in section 28. +The 'this' object is explained in section 31. - `gtk_file_name` function is the callback function for the closure tag. It first checks the `info` parameter. Because it can be NULL when GListItem `item` is unbounded. @@ -531,5 +532,4 @@ $ ./a.out ![screenshot list3](../image/list3.png) - 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 index 13128a1..78bcd80 100644 --- a/gfm/sec30.md +++ b/gfm/sec30.md @@ -64,7 +64,7 @@ Such "x-" subtype is not a standard mime type.) Content type is also used by desktop systems. GtkGridView uses the same GtkSingleSelection instance (`singleselection`). -So, its model property is set with it. +So, its model property is set to it. ## Ui file of the window @@ -467,7 +467,7 @@ The last parameter is the pointer to the pointer to a GError. If your distribution supports GTK 4, using `g_app_info_launch_default_for_uri` is convenient. The function automatically determines the default application from the file and launches it. -For example, if the file is text, then it launches gedit with the file. +For example, if the file is text, then it launches GNOME text editor with the file. Such feature comes from desktop. ## Compilation and execution @@ -477,7 +477,7 @@ To compile and execute list4, type as follows. ~~~ $ cd list4 # or cd src/list4. It depends your current directory. -$ meson _build +$ meson setup _build $ ninja -C _build $ _build/list4 ~~~ diff --git a/gfm/sec31.md b/gfm/sec31.md index 09aa1b2..ff7dcee 100644 --- a/gfm/sec31.md +++ b/gfm/sec31.md @@ -58,8 +58,8 @@ The type of the value is int. |G\_TYPE\_INT |int |gint | | |G\_TYPE\_FLOAT |float |gfloat | | |G\_TYPE\_DOUBLE |double|gdouble | | -|G\_TYPE\_POINTER | |gpointer | | -|G\_TYPE\_STRING | |gchararray|null-terminated Cstring| +|G\_TYPE\_POINTER |void *|gpointer |general pointer | +|G\_TYPE\_STRING |char *|gchararray|null-terminated Cstring| |G\_TYPE\_OBJECT | |GObject | | |GTK\_TYPE\_WINDOW| |GtkWindow | | @@ -119,7 +119,7 @@ expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "l If `expression` is evaluated, the second parameter `another_expression` is evaluated in advance. The value of `another_expression` is the `label` (GtkLabel instance). -Then, `expression` looks up "label" property of the label and the evaluation results "Hello". +Then, `expression` looks up "label" property of the label and the evaluation results in "Hello". In the example above, the second argument of `gtk_property_expression_new` is another expression. But the second argument can be NULL. @@ -145,7 +145,7 @@ There's a simple program `exp_property_simple.c` in `src/expression` directory. 14 if (gtk_expression_evaluate (expression, label, &value)) 15 g_print ("The value is %s.\n", g_value_get_string (&value)); 16 else -17 g_print ("The constant expression wasn't evaluated correctly.\n"); +17 g_print ("The property expression wasn't evaluated correctly.\n"); 18 gtk_expression_unref (expression); 19 g_value_unset (&value); 20 @@ -164,7 +164,7 @@ The expression just knows how to take the property from a future-given GtkLabel The result is stored in the GValue `value`. The function `g_value_get_string` gets a string from the GValue. But the string is owned by the GValue so you must not free the string. -- 18-19: Release the expression and unset the GValue. +- 18-19: Releases the expression and unset the GValue. At the same time the string in the GValue is freed. If the second argument of `gtk_property_expression_new` isn't NULL, it is another expression. @@ -195,6 +195,18 @@ gtk_cclosure_expression_new (GType value_type, gpointer user_data, GClosureNotify user_destroy); ~~~ +@else +~~~{.C} +GtkExpression * +gtk_cclosure_expression_new (GType value_type, + GClosureMarshal marshal, + guint n_params, + GtkExpression **params, + GCallback callback_func, + gpointer user_data, + GClosureNotify user_destroy); +~~~ +@end - `value_type` is the type of the value when it is evaluated. - `marshal` is a marshaller. @@ -224,6 +236,7 @@ callback (this, param1, param2, ...) For example, +@@@if gfm ~~~C int callback (GObject *object, int x, const char *s) @@ -276,13 +289,13 @@ If you want to return error report, change the return value type to be a pointer One for error and the other for the sum. The first argument of `gtk_cclosure_expression_new` is `G_TYPE_POINTER`. There is a sample program `exp_closure_with_error_report` in `src/expression` directory. -- 19: gtk\_init initializes GTK. It is necessary for GtkLabel. +- 19: The function `gtk_init`` initializes GTK. It is necessary for GtkLabel. - 20: A GtkLabel instance is created with "123+456". - 21: The instance has floating reference. It is changed to an ordinary reference count. -- 22-23: Create a closure expression. Its return value type is `G_TYPE_INT` and no parameters or 'this' object. +- 22-23: Creates a closure expression. Its return value type is `G_TYPE_INT` and no parameters or 'this' object. - 24: Evaluates the expression with the label as a 'this' object. -- 25: If the evaluation successes, show the sum of "123+456". It's 579. -- 27: If it fails, show an error message. +- 25: If the evaluation successes, the sum of "123+456", which is 579, is shown. +- 27: If it fails, an error message appears. - 28-30: Releases the expression and the label. Unsets the value. Closure expression is flexible than other type of expression because you can specify your own callback function. @@ -292,6 +305,7 @@ Closure expression is flexible than other type of expression because you can spe GtkExpressionWatch is a structure, not an object. It represents a watched GtkExpression. Two functions create GtkExpressionWatch structure. +They are `gtk_expression_bind` and `gtk_expression_watch`. ### gtk\_expression\_bind function @@ -411,7 +425,7 @@ For example, if it is zero, the slider moves to an integer point. 28 GtkExpression *expression, *params[1]; 29 30 /* Builds a window with exp.ui resource */ -31 build = gtk_builder_new_from_file ("exp_bind.ui"); +31 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp_bind.ui"); 32 win = GTK_WIDGET (gtk_builder_get_object (build, "win")); 33 label = GTK_WIDGET (gtk_builder_get_object (build, "label")); 34 // scale = GTK_WIDGET (gtk_builder_get_object (build, "scale")); @@ -448,7 +462,7 @@ The point of the program is: - 41-42: Two expressions are defined. One is a property expression and the other is a closure expression. -The property expression look up the "value"property of the adjustment instance. +The property expression looks up the "value" property of the adjustment instance. The closure expression just converts the double into an integer. - 43: `gtk_expression_bind` binds the label property of the GtkLabel instance to the integer returned by the closure expression. It creates a GtkExpressionWatch structure. @@ -462,8 +476,8 @@ This signal is emitted when the close button is clicked. The handler is called just before the window closes. It is the right moment to make the GtkExpressionWatch unwatched. - 10-14: "close-request" signal handler. -`gtk_expression_watch_unwatch (watch)` makes the watch stop watching the expression. -It releases the expression and calls `gtk_expression_watch_unref (watch)` in it. +The function `gtk_expression_watch_unwatch (watch)` makes the watch stop watching the expression. +It also releases the expression. If you want to bind a property to an expression, `gtk_expression_bind` is the best choice. You can do it with `gtk_expression_watch` function, but it is less suitable. @@ -619,7 +633,7 @@ These tags are usually used for GtkBuilderListItemFactory. - + GtkListItem @@ -629,14 +643,13 @@ These tags are usually used for GtkBuilderListItemFactory. ~~~ +GtkBuilderListItemFactory uses GtkBuilder to extends the GtkListItem with the XML data. + In the xml file above, "GtkListItem" is an instance of the GtkListItem template. It is the 'this' object given to the expressions. (The information is in the [GTK Development Blog](https://blog.gtk.org/2020/09/)). -GtkBuilderListItemFactory uses GtkBuilder to build the XML data. -It sets the current object of the GtkBuilder to the GtkListItem instance. - -GtkBuilder calls `gtk_expression_bind` function in the binding tag analysis. +GtkBuilder calls `gtk_expression_bind` function when it finds a binding tag. The function sets the 'this' object like this: 1. If the binding tag has object attribute, the object will be the 'this' object. @@ -760,7 +773,7 @@ String duplication is necessary. The C source file is very simple because almost everything is done in the ui file. -### Conversion between GValues +## Conversion between GValues If you bind different type properties, type conversion is automatically done. Suppose a label property (string) is bound to default-width property (int). @@ -782,4 +795,43 @@ If the current width is 100, an int `100` is converted to a string `"100"`. If you use `g_object_get` and `g_object_set` to copy properties, the value is automatically converted. +## Meson.build + +The source files are in `src/expression` directory. +You can build all the files at once. + +~~~ +$ cd src/expression +$ meson setup _build +$ ninja -C _build +~~~ + +For example, if you want to run "exp", which is the executable file from "exp.c", type `_build/exp`. +You can run other programs as well. + +The file `meson.build` is as follows. + +~~~meson + 1 project('exp', 'c') + 2 + 3 gtkdep = dependency('gtk4') + 4 + 5 gnome=import('gnome') + 6 resources = gnome.compile_resources('resources','exp.gresource.xml') + 7 + 8 sourcefiles=files('exp.c') + 9 +10 executable('exp', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: false) +11 executable('exp_constant', 'exp_constant.c', dependencies: gtkdep, export_dynamic: true, install: false) +12 executable('exp_constant_simple', 'exp_constant_simple.c', dependencies: gtkdep, export_dynamic: true, install: false) +13 executable('exp_property_simple', 'exp_property_simple.c', dependencies: gtkdep, export_dynamic: true, install: false) +14 executable('closure', 'closure.c', dependencies: gtkdep, export_dynamic: true, install: false) +15 executable('closure_each', 'closure_each.c', dependencies: gtkdep, export_dynamic: true, install: false) +16 executable('exp_closure_simple', 'exp_closure_simple.c', dependencies: gtkdep, export_dynamic: true, install: false) +17 executable('exp_closure_with_error_report', 'exp_closure_with_error_report.c', dependencies: gtkdep, export_dynamic: true, install: false) +18 executable('exp_bind', 'exp_bind.c', resources, dependencies: gtkdep, export_dynamic: true, install: false) +19 executable('exp_watch', 'exp_watch.c', dependencies: gtkdep, export_dynamic: true, install: false) +20 executable('exp_test', 'exp_test.c', resources, dependencies: gtkdep, export_dynamic: true, install: false) +~~~ + Up: [README.md](../README.md), Prev: [Section 30](sec30.md), Next: [Section 32](sec32.md) diff --git a/gfm/sec32.md b/gfm/sec32.md index e088294..a71c746 100644 --- a/gfm/sec32.md +++ b/gfm/sec32.md @@ -218,9 +218,9 @@ This is the title on the header of the column. - 32: Sets the "expand" property to TRUE to allow the column to expand as much as possible. (See the image above). - 33- 69: Sets the "factory" property to GtkBuilderListItemFactory. -The factory has "bytes" property which holds a ui string to define a template to build GtkListItem composite widget. +The factory has "bytes" property which holds a ui string to define a template to extend GtkListItem class. The CDATA section (line 36-66) is the ui string to put into the "bytes" property. -The contents are the same as the ui file `factory_list.ui` in the section 27. +The contents are the same as the ui file `factory_list.ui` in the section 30. - 70-77: Sets the "sorter" property to GtkStringSorter object. This object provides a sorter that compares strings. It has "expression" property. @@ -456,7 +456,8 @@ All the source files are in [`src/column`](../src/column) directory. Change your current directory to the directory and type the following. ~~~ -$ meson _build +$ cd src/colomn +$ meson setup _build $ ninja -C _build $ _build/column ~~~ diff --git a/gfm/sec33.md b/gfm/sec33.md index ea175da..9b033e5 100644 --- a/gfm/sec33.md +++ b/gfm/sec33.md @@ -8,23 +8,14 @@ GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents 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. +For example, suppose 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`. +This section shows just some parts 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 @@ -35,7 +26,7 @@ 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. +The one is a button, which shows if the line is 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. @@ -49,18 +40,18 @@ You can compile end execute it as follows. - Type the following on your commandline. ~~~ -$ meson _build +$ meson setup _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. +- Insert button: inserts a line before the current line, or at the top line if no current line exists. - 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. +- close button: closes the contents. +- quit button: quits the application. - Button on the select column: makes the line current. - String column: GtkText. You can edit a string in the field. @@ -71,7 +62,7 @@ The file name is shown at the right of the write button. The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory. It uses three signals setup, bind and unbind. -The following is their sgnal handlers. +The following shows the signal handlers. ~~~C 1 static void @@ -85,7 +76,7 @@ The following is their sgnal handlers. 9 bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { 10 GtkWidget *text = gtk_list_item_get_child (listitem); 11 GtkEntryBuffer *buffer = gtk_text_get_buffer (GTK_TEXT (text)); -12 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); +12 LeData *data = LE_DATA (gtk_list_item_get_item(listitem)); 13 GBinding *bind; 14 15 gtk_editable_set_text (GTK_EDITABLE (text), le_data_look_string (data)); @@ -99,9 +90,10 @@ The following is their sgnal handlers. 23 unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { 24 GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind")); 25 -26 g_binding_unbind(bind); -27 g_object_set_data (G_OBJECT (listitem), "bind", NULL); -28 } +26 if (bind) +27 g_binding_unbind(bind); +28 g_object_set_data (G_OBJECT (listitem), "bind", NULL); +29 } ~~~ - 1-6: `setup2_cb` is a setup signal handler on the GtkSignalListItemFactory. @@ -113,17 +105,13 @@ So, teardown signal handler isn't necessary. 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 first column GtkListItem instance when they are connected. -Be careful that the GtkListItem instance is *not* the `listitem` in this handler. -If no GtkListItem is connected, it is NULL. -The other is `string` which is a content of the line. +It is a child class of GObject and has string data which is the content of the line. - 10-11: `text` is a child of the `listitem` and it is a GtkText instance. -And `buffer` is a GtkTextBuffer instance of the `text`. +And `buffer` is a GtkEntryBuffer instance of the `text`. - 12: The LeData instance `data` is an item pointed by the `listitem`. - 15-16: Sets the text of `text` to `le_data_look_string (data)`. le\_data\_look\_string returns the string of the `data` and the ownership of the string is still taken by the `data`. -So, the caller don't need to free the string. +So, the caller doesn't need to free the string. - 18: `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`). @@ -136,14 +124,19 @@ 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. It makes possible for the "unbind" handler to get the `bind` instance. -- 22-28: `unbind2_cb` is a unbind signal handler. +- 22-29: `unbind2_cb` is a unbind signal handler. - 24: Retrieves the `bind` instance from the table in the `listitem` instance. - - 26: Unbind the binding. - - 27: Removes the value corresponds to the "bind" key. + - 26-27: Unbind the binding. + - 28: 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. +If it is impossible to use `g_object_bind_property`, use a notify signal on the GtkEntryBuffer instance. +You can use "deleted-text" and "inserted-text" signal instead. +The handler of the signals above copies the text in the GtkEntryBuffer instance to the LeData string. +Connect the notify signal handler in `bind2_cb` and disconnect it in `unbind2_cb`. + ## Change the cell of GtkColumnView dynamically Next topic is to change the GtkColumnView (or GtkListView) cells dynamically. @@ -168,159 +161,157 @@ 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 bound to the item, NULL is assigned. +The LeWindow instance has two instance variables for recording the current line. + +- `win->position`: An int type variable. It is the position of the current line. It is zero-based. If no current line exists, it is -1. +- `win->current_button`: A variable points the button, located at the first column, on the current line. If no current line exists, it is NULL. + +If the current line moves, the following two functions are called. +They updates the two varables. + +~~~C + 1 static void + 2 update_current_position (LeWindow *win, int new) { + 3 char *s; + 4 + 5 win->position = new; + 6 if (win->position >= 0) + 7 s = g_strdup_printf ("%d", win->position); + 8 else + 9 s = ""; +10 gtk_label_set_text (GTK_LABEL (win->position_label), s); +11 if (*s) // s isn't an empty string +12 g_free (s); +13 } +14 +15 static void +16 update_current_button (LeWindow *win, GtkButton *new_button) { +17 const char *non_current[1] = {NULL}; +18 const char *current[2] = {"current", NULL}; +19 +20 if (win->current_button) { +21 gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), non_current); +22 g_object_unref (win->current_button); +23 } +24 win->current_button = new_button; +25 if (win->current_button) { +26 g_object_ref (win->current_button); +27 gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), current); +28 } +29 } +~~~ + +The varable `win->position_label` points a GtkLabel instance. +The label shows the current line position. + +The current button has CSS "current" class. +The button is colored red through the CSS "button.current {background: red;}". + +The order of the call for these two functions is important. +The first function, which updates the position, is usually called first. +After that, a new line is appended or inserted. +Then, the second function is called. + +The following functions call the two functions above. +Be careful about the order of the call. ~~~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 gtk_widget_set_focusable (GTK_WIDGET (button), FALSE); -13 g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem); -14 } -15 -16 static void -17 bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { -18 LeWindow *win = LE_WINDOW (user_data); -19 GtkWidget *button = gtk_list_item_get_child (listitem); -20 const char *non_current[1] = {NULL}; -21 const char *current[2] = {"current", NULL}; -22 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); -23 -24 if (data) { -25 le_data_set_listitem (data, listitem); -26 if (win->position == gtk_list_item_get_position (listitem)) -27 gtk_widget_set_css_classes (GTK_WIDGET (button), current); -28 else -29 gtk_widget_set_css_classes (GTK_WIDGET (button), non_current); -30 } -31 } -32 -33 static void -34 unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { -35 LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); -36 if (data) -37 le_data_set_listitem (data, NULL); -38 } + 5 update_current_position (win, gtk_list_item_get_position (listitem)); + 6 update_current_button (win, btn); + 7 } + 8 + 9 static void +10 setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) { +11 GtkWidget *button = gtk_button_new (); +12 gtk_list_item_set_child (listitem, button); +13 gtk_widget_set_focusable (GTK_WIDGET (button), FALSE); +14 g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem); +15 } +16 +17 static void +18 bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { +19 LeWindow *win = LE_WINDOW (user_data); +20 GtkWidget *button = gtk_list_item_get_child (listitem); +21 +22 if (win->position == gtk_list_item_get_position (listitem)) +23 update_current_button (win, GTK_BUTTON (button)); +24 } ~~~ -- 8-14: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. -This factory is inserted to the factory property of the first GtkColumnViewColumn. +- 1-7: `select_cb` is a "clicked" signal handler. +The handler just calls the two functions and update the position and button. +- 9-15: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. It sets the child of `listitem` to a 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 the 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. -- 16-31: `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. -If the item is the current line, the CSS class of the button includes "current" class. -Otherwise it has no CSS class. -This is necessary because the button may be recycled and it has had former CSS class. -The class need to be updated. -- 33-38: `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. +- 17-24: `bind1_cb` is a bind signal handler. +Usually, the position moves before this handler is called. +If the item is on the current line, the button is updated. +No unbind handler is necessary. + +When a line is added, the current position is updated in advance. ~~~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 } + 2 app_cb (GtkButton *btn, LeWindow *win) { + 3 LeData *data = le_data_new_with_data (""); + 4 + 5 if (win->position >= 0) { + 6 update_current_position (win, win->position + 1); + 7 g_list_store_insert (win->liststore, win->position, data); + 8 } else { + 9 update_current_position (win, g_list_model_get_n_items (G_LIST_MODEL (win->liststore))); +10 g_list_store_append (win->liststore, data); +11 } +12 g_object_unref (data); +13 } +14 +15 static void +16 ins_cb (GtkButton *btn, LeWindow *win) { +17 LeData *data = le_data_new_with_data (""); +18 +19 if (win->position >= 0) +20 g_list_store_insert (win->liststore, win->position, data); +21 else { +22 update_current_position (win, 0); +23 g_list_store_insert (win->liststore, 0, data); +24 } +25 g_object_unref (data); +26 } ~~~ -The function `update_current` does several things. +When a line is removed, the current position becomes -1 and no button is current. -- 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. +~~~C +1 static void +2 rm_cb (GtkButton *btn, LeWindow *win) { +3 if (win->position >= 0) { +4 g_list_store_remove (win->liststore, win->position); +5 update_current_position (win, -1); +6 update_current_button (win, NULL); +7 } +8 } +~~~ 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). +The following CSS node is a bit complicated. +CSS node `column view` has `listview` child node. +It covers the rows in the GtkColumnView. +The `listview` node is the same as the one for GtkListView. +It has `row` child node, which is for each child widget. +Therefore, the following node corresponds buttons on the GtkColumnView widget. +In addition, it is applied to the "current" class. ~~~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. - ## A waring from GtkText If your program has the following two, a warning message can be issued. diff --git a/src/abstract.src.md b/src/abstract.src.md index a0803f2..7080a80 100644 --- a/src/abstract.src.md +++ b/src/abstract.src.md @@ -6,7 +6,8 @@ The table of contents is at the end of this abstract. - Section 3 to 23 describes the basics, with the example of a simple editor `tfe` (Text File Editor). - Section 24 to 27 describes GtkDrawingArea. -- Section 28 to 32 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView). +- Section 28 describes Drag and Drop. +- Section 29 to 33 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView). It also describes GtkExpression. The latest version of the tutorial is located at [GTK4-tutorial GitHub repository](https://github.com/ToshioCP/Gtk4-tutorial). diff --git a/src/expression/exp.gresource.xml b/src/expression/exp.gresource.xml index d93de0d..75c4b71 100644 --- a/src/expression/exp.gresource.xml +++ b/src/expression/exp.gresource.xml @@ -2,5 +2,7 @@ exp.ui + exp_bind.ui + exp_test.ui diff --git a/src/expression/exp_bind.c b/src/expression/exp_bind.c index 0959d33..5ad0f50 100644 --- a/src/expression/exp_bind.c +++ b/src/expression/exp_bind.c @@ -28,7 +28,7 @@ app_startup (GApplication *application) { GtkExpression *expression, *params[1]; /* Builds a window with exp.ui resource */ - build = gtk_builder_new_from_file ("exp_bind.ui"); + build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp_bind.ui"); win = GTK_WIDGET (gtk_builder_get_object (build, "win")); label = GTK_WIDGET (gtk_builder_get_object (build, "label")); // scale = GTK_WIDGET (gtk_builder_get_object (build, "scale")); diff --git a/src/expression/exp_property_simple.c b/src/expression/exp_property_simple.c index 49a299d..02e8fff 100644 --- a/src/expression/exp_property_simple.c +++ b/src/expression/exp_property_simple.c @@ -14,7 +14,7 @@ main (int argc, char **argv) { if (gtk_expression_evaluate (expression, label, &value)) g_print ("The value is %s.\n", g_value_get_string (&value)); else - g_print ("The constant expression wasn't evaluated correctly.\n"); + g_print ("The property expression wasn't evaluated correctly.\n"); gtk_expression_unref (expression); g_value_unset (&value); diff --git a/src/expression/exp_test.c b/src/expression/exp_test.c index 3be1b27..6750445 100644 --- a/src/expression/exp_test.c +++ b/src/expression/exp_test.c @@ -23,7 +23,7 @@ app_startup (GApplication *application) { GtkBuilder *build; GtkWidget *win; - build = gtk_builder_new_from_file ("exp_test.ui"); + build = gtk_builder_new_from_resource ("/com/github/ToshioCP/exp/exp_test.ui"); win = GTK_WIDGET (gtk_builder_get_object (build, "win")); gtk_window_set_application (GTK_WINDOW (win), app); g_object_unref (build); diff --git a/src/expression/meson.build b/src/expression/meson.build index fc5df11..f034d31 100644 --- a/src/expression/meson.build +++ b/src/expression/meson.build @@ -15,6 +15,6 @@ executable('closure', 'closure.c', dependencies: gtkdep, export_dynamic: true, i executable('closure_each', 'closure_each.c', dependencies: gtkdep, export_dynamic: true, install: false) executable('exp_closure_simple', 'exp_closure_simple.c', dependencies: gtkdep, export_dynamic: true, install: false) executable('exp_closure_with_error_report', 'exp_closure_with_error_report.c', dependencies: gtkdep, export_dynamic: true, install: false) -executable('exp_bind', 'exp_bind.c', dependencies: gtkdep, export_dynamic: true, install: false) +executable('exp_bind', 'exp_bind.c', resources, dependencies: gtkdep, export_dynamic: true, install: false) executable('exp_watch', 'exp_watch.c', dependencies: gtkdep, export_dynamic: true, install: false) -executable('exp_test', 'exp_test.c', dependencies: gtkdep, export_dynamic: true, install: false) +executable('exp_test', 'exp_test.c', resources, dependencies: gtkdep, export_dynamic: true, install: false) diff --git a/src/listeditor/listeditor.c b/src/listeditor/listeditor.c index 5125486..29095f5 100644 --- a/src/listeditor/listeditor.c +++ b/src/listeditor/listeditor.c @@ -13,7 +13,6 @@ static GParamSpec *ledata_properties[N_PROPERTIES] = {NULL, }; typedef struct _LeData { GObject parent; - GtkListItem *listitem; char *string; } LeData; @@ -41,19 +40,9 @@ le_data_get_property (GObject *object, guint property_id, GValue *value, GParamS 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); @@ -67,7 +56,6 @@ 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; @@ -75,14 +63,16 @@ le_data_class_init (LeDataClass *class) { 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); +/* getter and setter */ - if (self->listitem) - g_object_unref (self->listitem); - self->listitem = listitem ? g_object_ref (listitem) : NULL; +char * +le_data_get_string (LeData *self) { + return g_strdup (self->string); +} + +const char * +le_data_look_string (LeData *self) { + return self->string; } void @@ -99,35 +89,9 @@ le_data_take_string (LeData *self, char *string) { self->string = string; } -GtkListItem * -le_data_get_listitem (LeData *self) { - return self->listitem ? g_object_ref (self->listitem) : NULL; -} - -GtkListItem * -le_data_look_listitem (LeData *self) { - return self->listitem ? self->listitem : NULL; -} - -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; +le_data_new_with_data (const char *string) { + return LE_DATA (g_object_new (LE_TYPE_DATA, "string", string, NULL)); } LeData * @@ -141,151 +105,149 @@ G_DECLARE_FINAL_TYPE (LeWindow, le_window, LE, WINDOW, GtkApplicationWindow) typedef struct _LeWindow { GtkApplicationWindow parent; int position; /* current position */ + GtkButton *current_button; GFile *file; GtkWidget *position_label; GtkWidget *filename; GtkWidget *columnview; + GtkNoSelection *noselection; GListStore *liststore; } LeWindow; -G_DEFINE_TYPE (LeWindow, le_window, GTK_TYPE_APPLICATION_WINDOW) +G_DEFINE_FINAL_TYPE (LeWindow, le_window, GTK_TYPE_APPLICATION_WINDOW) static void -update_current (LeWindow *win, int new) { +update_current_position (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); + win->position = new; + if (win->position >= 0) + s = g_strdup_printf ("%d", win->position); 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); +static void +update_current_button (LeWindow *win, GtkButton *new_button) { + const char *non_current[1] = {NULL}; + const char *current[2] = {"current", NULL}; + + if (win->current_button) { + gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), non_current); + g_object_unref (win->current_button); } - 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); + win->current_button = new_button; + if (win->current_button) { + g_object_ref (win->current_button); + gtk_widget_set_css_classes (GTK_WIDGET (win->current_button), current); } } /* ----- Button "clicled" signal handlers ----- */ static void app_cb (GtkButton *btn, LeWindow *win) { - LeData *data; + LeData *data = le_data_new_with_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); + update_current_position (win, win->position + 1); + g_list_store_insert (win->liststore, win->position, data); } else { + update_current_position (win, g_list_model_get_n_items (G_LIST_MODEL (win->liststore))); 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; + LeData *data = le_data_new_with_data (""); - data = le_data_new_with_data (NULL, ""); - if (win->position >= 0) { + if (win->position >= 0) g_list_store_insert (win->liststore, win->position, data); - win->position += 1; - update_current (win, win->position - 1); + else { + update_current_position (win, 0); + g_list_store_insert (win->liststore, 0, data); } g_object_unref (data); } static void rm_cb (GtkButton *btn, LeWindow *win) { - int position; - if (win->position >= 0) { - position = win->position; - win->position = -1; - g_list_store_remove (win->liststore, position); - update_current (win, -1); + g_list_store_remove (win->liststore, win->position); + update_current_position (win, -1); + update_current_button (win, NULL); } } static void -open_dialog_response(GtkWidget *dialog, gint response, LeWindow *win) { +open_dialog_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { + GtkFileDialog *dialog = GTK_FILE_DIALOG (source_object); + LeWindow *win = LE_WINDOW (user_data); GFile *file; char *contents; + GListStore *liststore; + GListStore *liststore_old; + LeData *data; 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); + if ((file = gtk_file_dialog_open_finish (dialog, res, &err)) == NULL) { + g_warning ("%s\n", err->message); + g_error_free (err); + return; } - gtk_window_destroy (GTK_WINDOW (dialog)); + if ((stream = g_file_read (file, NULL, &err)) == NULL) { + 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); + liststore = g_list_store_new (LE_TYPE_DATA); + while ((contents = g_data_input_stream_read_line_utf8 (dstream, NULL, NULL, &err)) != NULL) { + data = le_data_new_with_data (contents); + g_list_store_append (liststore, data); + g_free (contents); + g_object_unref (data); + } + if (err) { + g_warning ("%s\n", err->message); + g_error_free (err); + g_object_unref (liststore); + return; + } + if (! g_input_stream_close (G_INPUT_STREAM (dstream), NULL, &err)) { + g_warning ("%s\n", err->message); + g_error_free (err); + g_object_unref (liststore); + return; + } + /* Now the file has successfully read. */ + if (win->file) + g_object_unref (file); + win->file = file; /* The ownership of the GFile moves to the window. */ + s = g_file_get_basename (file); + gtk_label_set_text (GTK_LABEL (win->filename), s); + g_free (s); + liststore_old = win->liststore; + gtk_no_selection_set_model (win->noselection, G_LIST_MODEL (liststore)); + win->liststore = liststore; /* The ownership of the stringlist moves to the window. */ + g_object_unref (liststore_old); + update_current_position (win, -1); } static void read_cb (GtkButton *btn, LeWindow *win) { - GtkWidget *dialog; + GtkFileDialog *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)); + dialog = gtk_file_dialog_new (); + gtk_file_dialog_open (dialog, GTK_WINDOW (win), NULL, open_dialog_cb, win); + g_object_unref (dialog); } static void @@ -293,8 +255,9 @@ write_data (LeWindow *win) { GFileOutputStream *ostream; gssize size; gsize length; - LeData *data; int i, n_items; + LeData *data; + const char *s; GError *err = NULL; if (! (ostream = g_file_replace (win->file, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &err))) { @@ -305,18 +268,19 @@ write_data (LeWindow *win) { 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); + s = le_data_look_string (data); + length = (gsize) strlen (s); + size = g_output_stream_write (G_OUTPUT_STREAM (ostream), s, length, NULL, &err); g_object_unref (data); if (size < 0) { g_warning ("%s\n", err->message); - g_error_free (err); + g_clear_error (&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); + g_clear_error (&err); break; } } @@ -327,36 +291,34 @@ write_data (LeWindow *win) { } static void -saveas_dialog_response (GtkWidget *dialog, gint response, LeWindow *win) { +saveas_dialog_cb (GObject* source_object, GAsyncResult* res, gpointer user_data) { + GtkFileDialog *dialog = GTK_FILE_DIALOG (source_object); + LeWindow *win = LE_WINDOW (user_data); GFile *file; + GError *err = NULL; 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"); - } + if ((file = gtk_file_dialog_save_finish (dialog, res, &err)) == NULL) { + g_warning ("%s\n", err->message); + g_error_free (err); + return; } - gtk_window_destroy (GTK_WINDOW (dialog)); + if (win->file) + g_object_unref (win->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); } static void show_saveas_dialog (LeWindow *win) { - GtkWidget *dialog; + GtkFileDialog *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); + dialog = gtk_file_dialog_new (); + gtk_file_dialog_save (dialog, GTK_WINDOW (win), NULL, saveas_dialog_cb, win); + g_object_unref (dialog); } static void @@ -371,14 +333,15 @@ write_cb (GtkButton *btn, LeWindow *win) { static void close_cb (GtkButton *btn, LeWindow *win) { - if (g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) > 0) + guint n_items; + + if ((n_items = 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), ""); + update_current_position (win, -1); } static void @@ -391,7 +354,8 @@ 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)); + update_current_position (win, gtk_list_item_get_position (listitem)); + update_current_button (win, btn); } /* ----- Handlers on GtkSignalListItemFacory ----- */ @@ -407,24 +371,9 @@ static void bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) { LeWindow *win = LE_WINDOW (user_data); GtkWidget *button = gtk_list_item_get_child (listitem); - const char *non_current[1] = {NULL}; - const char *current[2] = {"current", NULL}; - LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); - if (data) { - le_data_set_listitem (data, listitem); - if (win->position == gtk_list_item_get_position (listitem)) - gtk_widget_set_css_classes (GTK_WIDGET (button), current); - else - gtk_widget_set_css_classes (GTK_WIDGET (button), non_current); - } -} - -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); + if (win->position == gtk_list_item_get_position (listitem)) + update_current_button (win, GTK_BUTTON (button)); } static void @@ -438,7 +387,7 @@ static void bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { GtkWidget *text = gtk_list_item_get_child (listitem); GtkEntryBuffer *buffer = gtk_text_get_buffer (GTK_TEXT (text)); - LeData *data = LE_DATA (gtk_list_item_get_item (listitem)); + LeData *data = LE_DATA (gtk_list_item_get_item(listitem)); GBinding *bind; gtk_editable_set_text (GTK_EDITABLE (text), le_data_look_string (data)); @@ -452,7 +401,8 @@ static void unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) { GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind")); - g_binding_unbind(bind); + if (bind) + g_binding_unbind(bind); g_object_set_data (G_OBJECT (listitem), "bind", NULL); } @@ -463,24 +413,29 @@ adjustment_value_changed_cb (GtkAdjustment *adjustment, gpointer user_data) { gtk_window_set_focus (GTK_WINDOW (win), 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); + if (win->current_button) + g_clear_object (&win->current_button); + if (win->file) + g_clear_object (&win->file); /* 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_init (LeWindow *win) { + gtk_widget_init_template (GTK_WIDGET (win)); + + win->position = -1; + win->current_button = NULL; + win->file =NULL; +} + static void le_window_class_init (LeWindowClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); @@ -491,6 +446,7 @@ le_window_class_init (LeWindowClass *class) { 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, noselection); 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); @@ -502,7 +458,6 @@ le_window_class_init (LeWindowClass *class) { 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); diff --git a/src/listeditor/listeditor.ui b/src/listeditor/listeditor.ui index 7cd1d84..91877c6 100644 --- a/src/listeditor/listeditor.ui +++ b/src/listeditor/listeditor.ui @@ -2,8 +2,8 @@ " "" ~~~ +@@@else +~~~{.C} +char * +get_file_name (GtkListItem *item, GFileInfo *info) { + return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL; +} +... ... +... ... + +"" + "" +"" +~~~ +@@@end - The string "gchararray" is a type name. The type "gchar" is a type name and it is the same as C type "char". @@ -301,7 +359,7 @@ The contents of closure tag (it is between \ and\) is pa `GtkListItem` gives the value of the item property of the GtkListItem. This will be the second argument of the function. The first parameter is always the GListItem instance, which is a 'this' object. -The 'this' object is explained in section 28. +The 'this' object is explained in section 31. - `gtk_file_name` function is the callback function for the closure tag. It first checks the `info` parameter. Because it can be NULL when GListItem `item` is unbounded. @@ -341,9 +399,15 @@ $ gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` list3.c `pkg-config --libs You can also make a shell script to compile `list3.c` +@@@if gfm ~~~bash gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4` ~~~ +@@@else +~~~{.bash} +gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4` +~~~ +@@@end Save this one liner to a file `comp`. Then, copy it to `$HOME/bin` and give it executable permission. @@ -361,4 +425,3 @@ $ ./a.out ~~~ ![screenshot list3](../image/list3.png){width=10cm height=7.3cm} - diff --git a/src/sec30.src.md b/src/sec30.src.md index 751a6eb..4313d93 100644 --- a/src/sec30.src.md +++ b/src/sec30.src.md @@ -32,6 +32,7 @@ GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirect The following is a part of the ui file `list4.ui`. +@@@if gfm ~~~xml @@ -48,6 +49,24 @@ The following is a part of the ui file `list4.ui`. singleselection ~~~ +@@@else +~~~{.xml} + + + + + + standard::name,standard::icon,standard::content-type + + + + + + + singleselection + +~~~ +@@@end GtkDirectoryList has an "attributes" property. It is attributes of GFileInfo such as "standard::name", "standard::icon" and "standard::content-type". @@ -62,7 +81,7 @@ Such "x-" subtype is not a standard mime type.) Content type is also used by desktop systems. GtkGridView uses the same GtkSingleSelection instance (`singleselection`). -So, its model property is set with it. +So, its model property is set to it. ## Ui file of the window @@ -112,11 +131,19 @@ Its child widget will be set with GtkListView or GtkGridView. The action `view` is created, connected to the "activate" signal handler and inserted to the window (action map) as follows. +@@@if gfm ~~~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), NULL); g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view)); ~~~ +@@@else +~~~{.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), NULL); +g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_view)); +~~~ +@@@end The signal handler `view_activated` will be explained later. @@ -198,6 +225,7 @@ 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. +@@@if gfm ~~~C static void list_activate (GtkListView *list, int position, gpointer user_data) { @@ -217,6 +245,27 @@ grid_activate (GtkGridView *grid, int position, gpointer user_data) { 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); ~~~ +@@@else +~~~{.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); +~~~ +@@@end The second parameter of each handler is the position of the item (GFileInfo) of the GListModel. So you can get the item with `g_list_model_get_item` function. @@ -254,7 +303,7 @@ The last parameter is the pointer to the pointer to a GError. If your distribution supports GTK 4, using `g_app_info_launch_default_for_uri` is convenient. The function automatically determines the default application from the file and launches it. -For example, if the file is text, then it launches gedit with the file. +For example, if the file is text, then it launches GNOME text editor with the file. Such feature comes from desktop. ## Compilation and execution @@ -264,7 +313,7 @@ To compile and execute list4, type as follows. ~~~ $ cd list4 # or cd src/list4. It depends your current directory. -$ meson _build +$ meson setup _build $ ninja -C _build $ _build/list4 ~~~ diff --git a/src/sec31.src.md b/src/sec31.src.md index d7a66a8..c0eae56 100644 --- a/src/sec31.src.md +++ b/src/sec31.src.md @@ -56,8 +56,8 @@ The type of the value is int. |G\_TYPE\_INT |int |gint | | |G\_TYPE\_FLOAT |float |gfloat | | |G\_TYPE\_DOUBLE |double|gdouble | | -|G\_TYPE\_POINTER | |gpointer | | -|G\_TYPE\_STRING | |gchararray|null-terminated Cstring| +|G\_TYPE\_POINTER |void *|gpointer |general pointer | +|G\_TYPE\_STRING |char *|gchararray|null-terminated Cstring| |G\_TYPE\_OBJECT | |GObject | | |GTK\_TYPE\_WINDOW| |GtkWindow | | @@ -79,9 +79,15 @@ Constant expression is usually used to give a constant value or instance to anot A property expression (GtkPropertyExpression) looks up a property in a GObject instance. For example, a property expression that refers "label" property in a GtkLabel object is created like this. +@@@if gfm ~~~C expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label"); ~~~ +@@@else +~~~{.C} +expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label"); +~~~ +@@@end The second parameter `another_expression` is one of: @@ -91,15 +97,23 @@ The instance is called `this` object. For example, +@@@if gfm ~~~C label = gtk_label_new ("Hello"); another_expression = gtk_constant_expression_new (GTK_TYPE_LABEL, label); expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label"); ~~~ +@@@else +~~~{.C} +label = gtk_label_new ("Hello"); +another_expression = gtk_constant_expression_new (GTK_TYPE_LABEL, label); +expression = gtk_property_expression_new (GTK_TYPE_LABEL, another_expression, "label"); +~~~ +@@@end If `expression` is evaluated, the second parameter `another_expression` is evaluated in advance. The value of `another_expression` is the `label` (GtkLabel instance). -Then, `expression` looks up "label" property of the label and the evaluation results "Hello". +Then, `expression` looks up "label" property of the label and the evaluation results in "Hello". In the example above, the second argument of `gtk_property_expression_new` is another expression. But the second argument can be NULL. @@ -123,7 +137,7 @@ The expression just knows how to take the property from a future-given GtkLabel The result is stored in the GValue `value`. The function `g_value_get_string` gets a string from the GValue. But the string is owned by the GValue so you must not free the string. -- 18-19: Release the expression and unset the GValue. +- 18-19: Releases the expression and unset the GValue. At the same time the string in the GValue is freed. If the second argument of `gtk_property_expression_new` isn't NULL, it is another expression. @@ -144,6 +158,7 @@ When you program in C language, GtkCClosureExpression and GCClosure are appropri A closure expression is created with `gtk_cclosure_expression_new` function. +@@@if gfm ~~~C GtkExpression * gtk_cclosure_expression_new (GType value_type, @@ -154,6 +169,18 @@ gtk_cclosure_expression_new (GType value_type, gpointer user_data, GClosureNotify user_destroy); ~~~ +@else +~~~{.C} +GtkExpression * +gtk_cclosure_expression_new (GType value_type, + GClosureMarshal marshal, + guint n_params, + GtkExpression **params, + GCallback callback_func, + gpointer user_data, + GClosureNotify user_destroy); +~~~ +@end - `value_type` is the type of the value when it is evaluated. - `marshal` is a marshaller. @@ -183,10 +210,17 @@ callback (this, param1, param2, ...) For example, +@@@if gfm ~~~C int callback (GObject *object, int x, const char *s) ~~~ +@@@else +~~~{.C} +int +callback (GObject *object, int x, const char *s) +~~~ +@@@end The following is `exp_closure_simple.c` in `src/expression`. @@ -203,13 +237,13 @@ If you want to return error report, change the return value type to be a pointer One for error and the other for the sum. The first argument of `gtk_cclosure_expression_new` is `G_TYPE_POINTER`. There is a sample program `exp_closure_with_error_report` in `src/expression` directory. -- 19: gtk\_init initializes GTK. It is necessary for GtkLabel. +- 19: The function `gtk_init`` initializes GTK. It is necessary for GtkLabel. - 20: A GtkLabel instance is created with "123+456". - 21: The instance has floating reference. It is changed to an ordinary reference count. -- 22-23: Create a closure expression. Its return value type is `G_TYPE_INT` and no parameters or 'this' object. +- 22-23: Creates a closure expression. Its return value type is `G_TYPE_INT` and no parameters or 'this' object. - 24: Evaluates the expression with the label as a 'this' object. -- 25: If the evaluation successes, show the sum of "123+456". It's 579. -- 27: If it fails, show an error message. +- 25: If the evaluation successes, the sum of "123+456", which is 579, is shown. +- 27: If it fails, an error message appears. - 28-30: Releases the expression and the label. Unsets the value. Closure expression is flexible than other type of expression because you can specify your own callback function. @@ -219,12 +253,14 @@ Closure expression is flexible than other type of expression because you can spe GtkExpressionWatch is a structure, not an object. It represents a watched GtkExpression. Two functions create GtkExpressionWatch structure. +They are `gtk_expression_bind` and `gtk_expression_watch`. ### gtk\_expression\_bind function This function binds the target object's property to the expression. If the value of the expression changes, the property reflects the value immediately. +@@@if gfm ~~~C GtkExpressionWatch* gtk_expression_bind ( @@ -234,6 +270,17 @@ gtk_expression_bind ( GObject* this_ ) ~~~ +@@@else +~~~{.C} +GtkExpressionWatch* +gtk_expression_bind ( + GtkExpression* self, + GObject* target, + const char* property, + GObject* this_ +) +~~~ +@@@end This function takes the ownership of the expression. So, if you want to own the expression, call `gtk_expression_ref ()` to increase the reference count of the expression. @@ -282,7 +329,7 @@ The point of the program is: - 41-42: Two expressions are defined. One is a property expression and the other is a closure expression. -The property expression look up the "value"property of the adjustment instance. +The property expression looks up the "value" property of the adjustment instance. The closure expression just converts the double into an integer. - 43: `gtk_expression_bind` binds the label property of the GtkLabel instance to the integer returned by the closure expression. It creates a GtkExpressionWatch structure. @@ -296,14 +343,15 @@ This signal is emitted when the close button is clicked. The handler is called just before the window closes. It is the right moment to make the GtkExpressionWatch unwatched. - 10-14: "close-request" signal handler. -`gtk_expression_watch_unwatch (watch)` makes the watch stop watching the expression. -It releases the expression and calls `gtk_expression_watch_unref (watch)` in it. +The function `gtk_expression_watch_unwatch (watch)` makes the watch stop watching the expression. +It also releases the expression. If you want to bind a property to an expression, `gtk_expression_bind` is the best choice. You can do it with `gtk_expression_watch` function, but it is less suitable. ### gtk\_expression\_watch function +@@@if gfm ~~~C GtkExpressionWatch* gtk_expression_watch ( @@ -314,6 +362,18 @@ gtk_expression_watch ( GDestroyNotify user_destroy ) ~~~ +@@@else +~~~{.C} +GtkExpressionWatch* +gtk_expression_watch ( + GtkExpression* self, + GObject* this_, + GtkExpressionNotify notify, + gpointer user_data, + GDestroyNotify user_destroy +) +~~~ +@@@end The function doesn't take the ownership of the expression. It differs from `gtk_expression_bind`. @@ -326,12 +386,21 @@ Put NULL if you don't need them. Notify callback has the following format. +@@@if gfm ~~~C void notify ( gpointer user_data ) ~~~ +@@@else +~~~{.C} +void +notify ( + gpointer user_data +) +~~~ +@@@end This function is used to do something when the value of the expression changes. But if you want to bind a property to the value, use `gtk_expression_bind` instead. @@ -381,6 +450,7 @@ It is put in the content of an object tag. Name attribute specifies the property name of the object. The content is an expression. +@@@if gfm ~~~xml Hello world label @@ -389,16 +459,27 @@ The content is an expression. win ~~~ +@@@else +~~~{.xml} +Hello world +label + + + win + +~~~ +@@@end These tags are usually used for GtkBuilderListItemFactory. +@@@if gfm ~~~xml ~~~ +@@@else +~~~{.xml} + + + +~~~ +@@@end + +GtkBuilderListItemFactory uses GtkBuilder to extends the GtkListItem with the XML data. In the xml file above, "GtkListItem" is an instance of the GtkListItem template. It is the 'this' object given to the expressions. (The information is in the [GTK Development Blog](https://blog.gtk.org/2020/09/)). -GtkBuilderListItemFactory uses GtkBuilder to build the XML data. -It sets the current object of the GtkBuilder to the GtkListItem instance. - -GtkBuilder calls `gtk_expression_bind` function in the binding tag analysis. +GtkBuilder calls `gtk_expression_bind` function when it finds a binding tag. The function sets the 'this' object like this: 1. If the binding tag has object attribute, the object will be the 'this' object. @@ -466,11 +563,12 @@ String duplication is necessary. The C source file is very simple because almost everything is done in the ui file. -### Conversion between GValues +## Conversion between GValues If you bind different type properties, type conversion is automatically done. Suppose a label property (string) is bound to default-width property (int). +@@@if gfm ~~~xml @@ -480,6 +578,17 @@ Suppose a label property (string) is bound to default-width property (int). ~~~ +@@@else +~~~{.xml} + + + + win + + + +~~~ +@@@end The expression created by the lookup tag returns a int type GValue. On the other hand "label" property holds a string type GValue. @@ -487,3 +596,23 @@ When a GValue is copied to another GValue, the type is automatically converted i If the current width is 100, an int `100` is converted to a string `"100"`. If you use `g_object_get` and `g_object_set` to copy properties, the value is automatically converted. + +## Meson.build + +The source files are in `src/expression` directory. +You can build all the files at once. + +~~~ +$ cd src/expression +$ meson setup _build +$ ninja -C _build +~~~ + +For example, if you want to run "exp", which is the executable file from "exp.c", type `_build/exp`. +You can run other programs as well. + +The file `meson.build` is as follows. + +@@@include +expression/meson.build +@@@ diff --git a/src/sec32.src.md b/src/sec32.src.md index e31b80e..3f55527 100644 --- a/src/sec32.src.md +++ b/src/sec32.src.md @@ -60,9 +60,9 @@ This is the title on the header of the column. - 32: Sets the "expand" property to TRUE to allow the column to expand as much as possible. (See the image above). - 33- 69: Sets the "factory" property to GtkBuilderListItemFactory. -The factory has "bytes" property which holds a ui string to define a template to build GtkListItem composite widget. +The factory has "bytes" property which holds a ui string to define a template to extend GtkListItem class. The CDATA section (line 36-66) is the ui string to put into the "bytes" property. -The contents are the same as the ui file `factory_list.ui` in the section 27. +The contents are the same as the ui file `factory_list.ui` in the section 30. - 70-77: Sets the "sorter" property to GtkStringSorter object. This object provides a sorter that compares strings. It has "expression" property. @@ -196,7 +196,8 @@ All the source files are in [`src/column`](column) directory. Change your current directory to the directory and type the following. ~~~ -$ meson _build +$ cd src/colomn +$ meson setup _build $ ninja -C _build $ _build/column ~~~ diff --git a/src/sec33.src.md b/src/sec33.src.md index a04ae0f..9075c27 100644 --- a/src/sec33.src.md +++ b/src/sec33.src.md @@ -6,23 +6,14 @@ GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents 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. +For example, suppose 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`. +This section shows just some parts 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 @@ -33,7 +24,7 @@ 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. +The one is a button, which shows if the line is 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. @@ -47,18 +38,18 @@ You can compile end execute it as follows. - Type the following on your commandline. ~~~ -$ meson _build +$ meson setup _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. +- Insert button: inserts a line before the current line, or at the top line if no current line exists. - 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. +- close button: closes the contents. +- quit button: quits the application. - Button on the select column: makes the line current. - String column: GtkText. You can edit a string in the field. @@ -69,7 +60,7 @@ The file name is shown at the right of the write button. The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory. It uses three signals setup, bind and unbind. -The following is their sgnal handlers. +The following shows the signal handlers. @@@include listeditor/listeditor.c setup2_cb bind2_cb unbind2_cb @@ -84,17 +75,13 @@ So, teardown signal handler isn't necessary. 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 first column GtkListItem instance when they are connected. -Be careful that the GtkListItem instance is *not* the `listitem` in this handler. -If no GtkListItem is connected, it is NULL. -The other is `string` which is a content of the line. +It is a child class of GObject and has string data which is the content of the line. - 10-11: `text` is a child of the `listitem` and it is a GtkText instance. -And `buffer` is a GtkTextBuffer instance of the `text`. +And `buffer` is a GtkEntryBuffer instance of the `text`. - 12: The LeData instance `data` is an item pointed by the `listitem`. - 15-16: Sets the text of `text` to `le_data_look_string (data)`. le\_data\_look\_string returns the string of the `data` and the ownership of the string is still taken by the `data`. -So, the caller don't need to free the string. +So, the caller doesn't need to free the string. - 18: `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`). @@ -107,14 +94,19 @@ 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. It makes possible for the "unbind" handler to get the `bind` instance. -- 22-28: `unbind2_cb` is a unbind signal handler. +- 22-29: `unbind2_cb` is a unbind signal handler. - 24: Retrieves the `bind` instance from the table in the `listitem` instance. - - 26: Unbind the binding. - - 27: Removes the value corresponds to the "bind" key. + - 26-27: Unbind the binding. + - 28: 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. +If it is impossible to use `g_object_bind_property`, use a notify signal on the GtkEntryBuffer instance. +You can use "deleted-text" and "inserted-text" signal instead. +The handler of the signals above copies the text in the GtkEntryBuffer instance to the LeData string. +Connect the notify signal handler in `bind2_cb` and disconnect it in `unbind2_cb`. + ## Change the cell of GtkColumnView dynamically Next topic is to change the GtkColumnView (or GtkListView) cells dynamically. @@ -139,85 +131,79 @@ 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 bound to the item, NULL is assigned. +The LeWindow instance has two instance variables for recording the current line. + +- `win->position`: An int type variable. It is the position of the current line. It is zero-based. If no current line exists, it is -1. +- `win->current_button`: A variable points the button, located at the first column, on the current line. If no current line exists, it is NULL. + +If the current line moves, the following two functions are called. +They updates the two varables. @@@include -listeditor/listeditor.c select_cb setup1_cb bind1_cb unbind1_cb +listeditor/listeditor.c update_current_position update_current_button @@@ -- 8-14: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. -This factory is inserted to the factory property of the first GtkColumnViewColumn. +The varable `win->position_label` points a GtkLabel instance. +The label shows the current line position. + +The current button has CSS "current" class. +The button is colored red through the CSS "button.current {background: red;}". + +The order of the call for these two functions is important. +The first function, which updates the position, is usually called first. +After that, a new line is appended or inserted. +Then, the second function is called. + +The following functions call the two functions above. +Be careful about the order of the call. + +@@@include +listeditor/listeditor.c select_cb setup1_cb bind1_cb +@@@ + +- 1-7: `select_cb` is a "clicked" signal handler. +The handler just calls the two functions and update the position and button. +- 9-15: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory. It sets the child of `listitem` to a 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 the 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. -- 16-31: `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. -If the item is the current line, the CSS class of the button includes "current" class. -Otherwise it has no CSS class. -This is necessary because the button may be recycled and it has had former CSS class. -The class need to be updated. -- 33-38: `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. +- 17-24: `bind1_cb` is a bind signal handler. +Usually, the position moves before this handler is called. +If the item is on the current line, the button is updated. +No unbind handler is necessary. + +When a line is added, the current position is updated in advance. @@@include -listeditor/listeditor.c update_current +listeditor/listeditor.c app_cb ins_cb @@@ -The function `update_current` does several things. +When a line is removed, the current position becomes -1 and no button is current. -- 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. +@@@include +listeditor/listeditor.c rm_cb +@@@ 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). +The following CSS node is a bit complicated. +CSS node `column view` has `listview` child node. +It covers the rows in the GtkColumnView. +The `listview` node is the same as the one for GtkListView. +It has `row` child node, which is for each child widget. +Therefore, the following node corresponds buttons on the GtkColumnView widget. +In addition, it is applied to the "current" class. +@@@if gfm ~~~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. +@@@else +~~~{.css} +columnview listview row button.current {background: red;} +~~~ +@@@end ## A waring from GtkText