[gtk/wip/otte/listview] listview: Implement ::section-factory property



commit e8427796be77aa1e2340f93ff59abaaac31576bb
Author: Benjamin Otte <otte redhat com>
Date:   Sat Feb 19 22:55:25 2022 +0100

    listview: Implement ::section-factory property
    
    Add a property to listview, pipe it to the listitemmanager and make sure
    the manager creates the sections so that listview can display it.

 gtk/gtklistitemmanager.c        | 217 ++++++++++++++++++++++++++++++++++++++--
 gtk/gtklistitemmanagerprivate.h |   4 +
 gtk/gtklistview.c               | 165 +++++++++++++++++++++++++-----
 gtk/gtklistview.h               |   7 ++
 4 files changed, 360 insertions(+), 33 deletions(-)
---
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index 146f589bcd..649ff6b68a 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -22,6 +22,7 @@
 #include "gtklistitemmanagerprivate.h"
 
 #include "gtklistitemwidgetprivate.h"
+#include "gtksectionmodelprivate.h"
 #include "gtkwidgetprivate.h"
 
 #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
@@ -33,6 +34,7 @@ struct _GtkListItemManager
   GtkWidget *widget;
   GtkSelectionModel *model;
   GtkListItemFactory *factory;
+  GtkListItemFactory *section_factory;
   gboolean single_click_activate;
   const char *item_css_name;
   GtkAccessibleRole item_role;
@@ -59,6 +61,7 @@ struct _GtkListItemTracker
 
 static GtkWidget *      gtk_list_item_manager_acquire_list_item (GtkListItemManager     *self,
                                                                  guint                   position,
+                                                                 gboolean                is_header,
                                                                  GtkWidget              *prev_sibling);
 static GtkWidget *      gtk_list_item_manager_try_reacquire_list_item
                                                                 (GtkListItemManager     *self,
@@ -145,6 +148,7 @@ gtk_list_item_manager_clear_node (gpointer _item)
   GtkListItemManagerItem *item G_GNUC_UNUSED = _item;
 
   g_assert (item->widget == NULL);
+  g_assert (item->section_header == NULL);
 }
 
 GtkListItemManager *
@@ -427,6 +431,141 @@ restart:
     }
 }
 
+static gboolean
+gtk_list_item_manager_has_sections (GtkListItemManager *self)
+{
+  return self->section_factory != NULL;
+}
+
+static void
+gtk_list_item_manager_clear_sections (GtkListItemManager *self)
+{
+  GtkListItemManagerItem *item;
+
+  for (item = gtk_rb_tree_get_first (self->items);
+       item != NULL;
+       item = gtk_rb_tree_node_get_next (item))
+    {
+      if (item->section_header)
+        {
+          gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+          item->section_header = NULL;
+        }
+    }
+}
+
+typedef struct _SectionIter SectionIter;
+struct _SectionIter
+{
+  GtkListItemManagerItem *item;
+  guint                   item_pos;
+  GtkWidget              *insert_after;
+};
+
+static void
+section_iter_init (SectionIter        *siter,
+                   GtkListItemManager *self)
+{
+  siter->item = gtk_rb_tree_get_first (self->items);
+  siter->item_pos = 0;
+  siter->insert_after = NULL;
+}
+
+static void
+section_iter_next (SectionIter *siter)
+{
+  siter->item_pos += siter->item->n_items;
+  if (siter->item->widget)
+    siter->insert_after = siter->item->widget;
+  siter->item = gtk_rb_tree_node_get_next (siter->item);
+}
+
+static void
+section_iter_put_at (SectionIter        *siter,
+                     GtkListItemManager *self,
+                     guint               pos)
+{
+  /* We already put an item there */
+  if (siter->item_pos == pos + 1)
+    return;
+
+  g_assert (pos >= siter->item_pos);
+
+  while (siter->item_pos < pos)
+    {
+      if (siter->item->section_header)
+        {
+          gtk_list_item_manager_release_list_item (self, NULL, siter->item->section_header);
+          siter->item->section_header = NULL;
+        }
+
+      if (siter->item->n_items + siter->item_pos > pos)
+        gtk_list_item_manager_split_item (self, siter->item, pos - siter->item_pos);
+
+      section_iter_next (siter);
+    }
+
+  g_assert (siter->item_pos == pos);
+
+  while (siter->item->n_items == 0)
+    section_iter_next (siter);
+
+  if (siter->item->n_items > 1)
+    gtk_list_item_manager_split_item (self, siter->item, 1);
+
+  if (siter->item->section_header == NULL)
+    {
+      siter->item->section_header = gtk_list_item_manager_acquire_list_item (self,
+                                                                             pos,
+                                                                             TRUE,
+                                                                             siter->insert_after);
+    }
+
+  section_iter_next (siter);
+}
+
+static void
+section_iter_finish (SectionIter        *siter,
+                     GtkListItemManager *self)
+{
+  while (siter->item)
+    section_iter_next (siter);
+}
+                     
+static void
+gtk_list_item_manager_ensure_sections (GtkListItemManager *self)
+{
+  SectionIter siter;
+  guint pos, n_items, query_n_items, section_start, section_end;
+  gboolean tracked;
+
+  if (!gtk_list_item_manager_has_sections (self) || self->model == NULL)
+    return;
+
+  pos = 0;
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+  section_iter_init (&siter, self);
+
+  while (pos < n_items)
+    {
+      gtk_list_item_query_tracked_range (self, n_items, pos, &query_n_items, &tracked);
+      if (tracked)
+        {
+          gtk_list_model_get_section (G_LIST_MODEL (self->model), pos, &section_start, &section_end);
+          section_iter_put_at (&siter, self, section_start);
+
+          while (section_end < pos + query_n_items)
+            {
+              gtk_list_model_get_section (G_LIST_MODEL (self->model), section_end, &section_start, 
&section_end);
+              section_iter_put_at (&siter, self, section_start);
+            }
+        }
+      pos += query_n_items;
+    }
+
+  section_iter_finish (&siter, self);
+}
+
 static void
 gtk_list_item_manager_remove_items (GtkListItemManager *self,
                                     GHashTable         *change,
@@ -452,8 +591,15 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
         {
           GtkListItemManagerItem *next = gtk_rb_tree_node_get_next (item);
           if (item->widget)
-            gtk_list_item_manager_release_list_item (self, change, item->widget);
-          item->widget = NULL;
+            {
+              gtk_list_item_manager_release_list_item (self, change, item->widget);
+              item->widget = NULL;
+            }
+          if (item->section_header)
+            {
+              gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+              item->section_header = NULL;
+            }
           n_items -= item->n_items;
           item->n_items = 0;
           gtk_rb_tree_node_mark_dirty (item);
@@ -509,7 +655,8 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager     *self,
                                         GtkListItemManagerItem *first,
                                         GtkListItemManagerItem *second)
 {
-  if (first->widget || second->widget)
+  if (first->widget || first->section_header ||
+      second->widget || second->section_header)
     return FALSE;
 
   first->n_items += second->n_items;
@@ -575,6 +722,11 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
               g_queue_push_tail (released, item->widget);
               item->widget = NULL;
             }
+          if (item->section_header)
+            {
+              gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+              item->section_header = NULL;
+            }
           i += item->n_items;
           item = gtk_rb_tree_node_get_next (item);
         }
@@ -668,6 +820,7 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
                     {
                       new_item->widget = gtk_list_item_manager_acquire_list_item (self,
                                                                                   position + i,
+                                                                                  FALSE,
                                                                                   insert_after);
                     }
                 }
@@ -675,7 +828,11 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
           else
             {
               if (update_start <= position + i)
-                gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
+                {
+                  gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
+                  if (new_item->section_header)
+                    gtk_list_item_manager_update_list_item (self, new_item->section_header, position + i);
+                }
             }
           insert_after = new_item->widget;
         }
@@ -685,6 +842,8 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
   while ((widget = g_queue_pop_head (&released)))
     gtk_list_item_manager_release_list_item (self, NULL, widget);
 
+  gtk_list_item_manager_ensure_sections (self);
+
   dump_items (self);
 }
 
@@ -976,6 +1135,31 @@ gtk_list_item_manager_get_factory (GtkListItemManager *self)
   return self->factory;
 }
 
+void
+gtk_list_item_manager_set_section_factory (GtkListItemManager *self,
+                                           GtkListItemFactory *factory)
+{
+  g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
+  g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory));
+
+  if (self->section_factory == factory)
+    return;
+
+  gtk_list_item_manager_clear_sections (self);
+
+  g_set_object (&self->section_factory, factory);
+
+  gtk_list_item_manager_ensure_sections (self);
+}
+
+GtkListItemFactory *
+gtk_list_item_manager_get_section_factory (GtkListItemManager *self)
+{
+  g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
+
+  return self->factory;
+}
+
 void
 gtk_list_item_manager_set_model (GtkListItemManager *self,
                                  GtkSelectionModel  *model)
@@ -1017,6 +1201,8 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
  * gtk_list_item_manager_acquire_list_item:
  * @self: a `GtkListItemManager`
  * @position: the row in the model to create a list item for
+ * @is_header: %TRUE if this is for acquiring a header, %FALSE for regular
+ *   list items
  * @prev_sibling: the widget this widget should be inserted before or %NULL
  *   if it should be the first widget
  *
@@ -1034,6 +1220,7 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
 static GtkWidget *
 gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
                                          guint               position,
+                                         gboolean            is_header,
                                          GtkWidget          *prev_sibling)
 {
   GtkWidget *result;
@@ -1043,14 +1230,24 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
   g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
   g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
 
-  result = gtk_list_item_widget_new (self->factory,
-                                     self->item_css_name,
-                                     self->item_role);
-
-  gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), 
self->single_click_activate);
+  if (is_header)
+    {
+      result = gtk_list_item_widget_new (self->section_factory,
+                                         "header",
+                                         GTK_ACCESSIBLE_ROLE_ROW_HEADER);
+      gtk_list_item_widget_set_activatable (GTK_LIST_ITEM_WIDGET (result), FALSE);
+      selected = FALSE;
+    }
+  else
+    {
+      result = gtk_list_item_widget_new (self->factory,
+                                         self->item_css_name,
+                                         self->item_role);
+      selected = gtk_selection_model_is_selected (self->model, position);
+      gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), 
self->single_click_activate);
+    }
 
   item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
-  selected = gtk_selection_model_is_selected (self->model, position);
   gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, item, selected);
   g_object_unref (item);
   gtk_widget_insert_after (result, self->widget, prev_sibling);
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index 57f760bd09..5ba3b7bc98 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -46,6 +46,7 @@ typedef struct _GtkListItemTracker GtkListItemTracker;
 struct _GtkListItemManagerItem
 {
   GtkWidget *widget;
+  GtkWidget *section_header;
   guint n_items;
 };
 
@@ -90,6 +91,9 @@ void                    gtk_list_item_manager_gc                (GtkListItemMana
 void                    gtk_list_item_manager_set_factory       (GtkListItemManager     *self,
                                                                  GtkListItemFactory     *factory);
 GtkListItemFactory *    gtk_list_item_manager_get_factory       (GtkListItemManager     *self);
+void                    gtk_list_item_manager_set_section_factory (GtkListItemManager     *self,
+                                                                 GtkListItemFactory     *factory);
+GtkListItemFactory *    gtk_list_item_manager_get_section_factory (GtkListItemManager     *self);
 void                    gtk_list_item_manager_set_model         (GtkListItemManager     *self,
                                                                  GtkSelectionModel      *model);
 GtkSelectionModel *     gtk_list_item_manager_get_model         (GtkListItemManager     *self);
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 4d69d43f83..7e04e6ea8e 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -148,6 +148,7 @@ typedef struct _ListRowAugment ListRowAugment;
 struct _ListRow
 {
   GtkListItemManagerItem parent;
+  guint section_height;
   guint height; /* per row */
 };
 
@@ -164,6 +165,7 @@ enum
   PROP_FOCUS_ITEM,
   PROP_FOCUS_POSITION,
   PROP_MODEL,
+  PROP_SECTION_FACTORY,
   PROP_SHOW_SEPARATORS,
   PROP_SINGLE_CLICK_ACTIVATE,
   PROP_ENABLE_RUBBERBAND,
@@ -215,7 +217,7 @@ list_row_augment (GtkRbTree *tree,
 
   gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
 
-  aug->height = row->height;
+  aug->height = row->height + row->section_height;
 
   if (left)
     {
@@ -255,9 +257,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
           y -= aug->height;
         }
 
-      if (y < row->height)
+      if (y < row->height + row->section_height)
         break;
-      y -= row->height;
+      y -= row->height + row->section_height;
 
       row = gtk_rb_tree_node_get_right (row);
     }
@@ -297,7 +299,7 @@ list_row_get_y (GtkListView *self,
               ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
               y += aug->height;
             }
-          y += parent->height;
+          y += parent->height + parent->section_height;
         }
 
       row = parent;
@@ -329,12 +331,13 @@ gtk_list_view_get_allocation_along (GtkListBase *base,
 
   y = list_row_get_y (self, row);
   g_assert (row->parent.n_items > 0);
-  y += skip * row->height / row->parent.n_items;
+  if (skip)
+    y += row->section_height + skip * row->height / row->parent.n_items;
 
   if (offset)
     *offset = y;
   if (size)
-    *size = row->height / row->parent.n_items;
+    *size = row->height / row->parent.n_items + skip ? 0 : row->section_height;
 
   return TRUE;
 }
@@ -363,6 +366,7 @@ gtk_list_view_get_items_in_rect (GtkListBase                 *base,
   guint first, last, n_items;
   GtkBitset *result;
   ListRow *row;
+  int remaining;
 
   result = gtk_bitset_new_empty ();
 
@@ -375,9 +379,13 @@ gtk_list_view_get_items_in_rect (GtkListBase                 *base,
     first = gtk_list_item_manager_get_item_position (self->item_manager, row);
   else
     first = rect->y < 0 ? 0 : n_items - 1;
-  row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
+  row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, &remaining);
   if (row)
-    last = gtk_list_item_manager_get_item_position (self->item_manager, row);
+    {
+      last = gtk_list_item_manager_get_item_position (self->item_manager, row);
+      if (remaining < row->section_height && last > 0)
+        last--;
+    }
   else
     last = rect->y < 0 ? 0 : n_items - 1;
 
@@ -428,7 +436,7 @@ gtk_list_view_get_position_from_allocation (GtkListBase           *base,
   else
     one_item_height = row->height / row->parent.n_items;
 
-  g_assert (remaining < row->height);
+  g_assert (remaining < row->height + row->section_height);
   *pos += remaining / one_item_height;
 
   if (area)
@@ -499,6 +507,14 @@ gtk_list_view_measure_across (GtkWidget      *widget,
                           &child_min, &child_nat, NULL, NULL);
       min = MAX (min, child_min);
       nat = MAX (nat, child_nat);
+      if (row->parent.section_header)
+        {
+          gtk_widget_measure (row->parent.section_header,
+                              orientation, for_size,
+                              &child_min, &child_nat, NULL, NULL);
+          min = MAX (min, child_min);
+          nat = MAX (nat, child_nat);
+        }
     }
 
   *minimum = min;
@@ -535,6 +551,15 @@ gtk_list_view_measure_list (GtkWidget      *widget,
                               &child_min, &child_nat, NULL, NULL);
           g_array_append_val (min_heights, child_min);
           g_array_append_val (nat_heights, child_nat);
+          if (row->parent.section_header)
+            {
+              int section_min, section_nat;
+              gtk_widget_measure (row->parent.section_header,
+                                  orientation, for_size,
+                                  &section_min, &section_nat, NULL, NULL);
+              child_min += section_min;
+              child_nat += section_nat;
+            }
           min += child_min;
           nat += child_nat;
         }
@@ -617,23 +642,42 @@ gtk_list_view_size_allocate (GtkWidget *widget,
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
     {
-      if (row->parent.widget == NULL)
-        continue;
-
-      gtk_widget_measure (row->parent.widget, orientation,
-                          self->list_width,
-                          &min, &nat, NULL, NULL);
-      if (scroll_policy == GTK_SCROLL_MINIMUM)
-        row_height = min;
+      if (row->parent.widget)
+        {
+          gtk_widget_measure (row->parent.widget, orientation,
+                              self->list_width,
+                              &min, &nat, NULL, NULL);
+          if (scroll_policy == GTK_SCROLL_MINIMUM)
+            row_height = min;
+          else
+            row_height = nat;
+          g_array_append_val (heights, row_height);
+          if (row->height != row_height)
+            {
+              row->height = row_height;
+              gtk_rb_tree_node_mark_dirty (row);
+            }
+          total_height += row->height;
+        }
+      if (row->parent.section_header)
+        {
+          gtk_widget_measure (row->parent.section_header, orientation,
+                              self->list_width,
+                              &min, &nat, NULL, NULL);
+          if (scroll_policy == GTK_SCROLL_MINIMUM)
+            row_height = min;
+          else
+            row_height = nat;
+        }
       else
-        row_height = nat;
-      if (row->height != row_height)
+        row_height = 0;
+
+      if (row->section_height != row_height)
         {
-          row->height = row_height;
+          row->section_height = row_height;
           gtk_rb_tree_node_mark_dirty (row);
         }
-      total_height += row->height;
-      g_array_append_val (heights, row_height);
+      total_height += row_height;
     }
 
   /* step 5: determine height of unknown items */
@@ -670,6 +714,16 @@ gtk_list_view_size_allocate (GtkWidget *widget,
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
     {
+      if (row->parent.section_header)
+        {
+          gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
+                                             row->parent.section_header,
+                                             x,
+                                             y,
+                                             self->list_width,
+                                             row->section_height);
+          y += row->section_height;
+        }
       if (row->parent.widget)
         {
           gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
@@ -679,7 +733,6 @@ gtk_list_view_size_allocate (GtkWidget *widget,
                                              self->list_width,
                                              row->height);
         }
-
       y += row->height;
     }
 
@@ -722,6 +775,10 @@ gtk_list_view_get_property (GObject    *object,
       g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
       break;
 
+    case PROP_SECTION_FACTORY:
+      g_value_set_object (value, gtk_list_item_manager_get_section_factory (self->item_manager));
+      break;
+
     case PROP_SHOW_SEPARATORS:
       g_value_set_boolean (value, self->show_separators);
       break;
@@ -762,6 +819,10 @@ gtk_list_view_set_property (GObject      *object,
       gtk_list_view_set_model (self, g_value_get_object (value));
       break;
 
+    case PROP_SECTION_FACTORY:
+      gtk_list_view_set_section_factory (self, g_value_get_object (value));
+      break;
+
     case PROP_SHOW_SEPARATORS:
       gtk_list_view_set_show_separators (self, g_value_get_boolean (value));
       break;
@@ -877,6 +938,20 @@ gtk_list_view_class_init (GtkListViewClass *klass)
                          GTK_TYPE_SELECTION_MODEL,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
+  /**
+   * GtkListView:section-factory: (attributes org.gtk.Property.get=gtk_list_view_get_section_factory 
org.gtk.Property.set=gtk_list_view_set_section_factory)
+   *
+   * Factory for populating section headers.
+   *
+   * Since: 4.8
+   */
+  properties[PROP_SECTION_FACTORY] =
+    g_param_spec_object ("section-factory",
+                         P_("Section factory"),
+                         P_("Factory for populating secion headers"),
+                         GTK_TYPE_LIST_ITEM_FACTORY,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
   /**
    * GtkListView:show-separators: (attributes org.gtk.Property.get=gtk_list_view_get_show_separators 
org.gtk.Property.set=gtk_list_view_set_show_separators)
    *
@@ -1086,6 +1161,50 @@ gtk_list_view_set_factory (GtkListView        *self,
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
 }
 
+/**
+ * gtk_list_view_get_section_factory: (attributes org.gtk.Method.get_property=section-factory)
+ * @self: a `GtkListView`
+ *
+ * Gets the factory that's currently used to populate section headers.
+ *
+ * Returns: (nullable) (transfer none): The factory in use
+ *
+ * Since: 4.8
+ */
+GtkListItemFactory *
+gtk_list_view_get_section_factory (GtkListView *self)
+{
+  g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
+
+  return gtk_list_item_manager_get_section_factory (self->item_manager);
+}
+
+/**
+ * gtk_list_view_set_section_factory: (attributes org.gtk.Method.set_property=section-factory)
+ * @self: a `GtkListView`
+ * @factory: (nullable) (transfer none): the factory to use
+ *
+ * Sets the `GtkListItemFactory` to use for populating section headers.
+ *
+ * If this factory is set to %NULL, the list will not use section.
+ *
+ * Since: 4.8
+ */
+void
+gtk_list_view_set_section_factory (GtkListView        *self,
+                                   GtkListItemFactory *factory)
+{
+  g_return_if_fail (GTK_IS_LIST_VIEW (self));
+  g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
+
+  if (factory == gtk_list_item_manager_get_section_factory (self->item_manager))
+    return;
+
+  gtk_list_item_manager_set_section_factory (self->item_manager, factory);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_FACTORY]);
+}
+
 /**
  * gtk_list_view_set_focus_position: (attributes org.gtk.Method.set_property=focus-position)
  * @self: a `GtkListView`
diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h
index 8b1bdf7663..0c601fa8a9 100644
--- a/gtk/gtklistview.h
+++ b/gtk/gtklistview.h
@@ -58,6 +58,13 @@ GDK_AVAILABLE_IN_ALL
 GtkListItemFactory *
                 gtk_list_view_get_factory                       (GtkListView            *self);
 
+GDK_AVAILABLE_IN_4_8
+void            gtk_list_view_set_section_factory               (GtkListView            *self,
+                                                                 GtkListItemFactory     *factory);
+GDK_AVAILABLE_IN_4_8
+GtkListItemFactory *
+                gtk_list_view_get_section_factory               (GtkListView            *self);
+
 GDK_AVAILABLE_IN_4_8
 void            gtk_list_view_set_focus_position                (GtkListView            *self,
                                                                  guint                   position);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]