From 53bfcf7f0ae8098c1ae3a29a322c32d545667849 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 10 Apr 2023 14:32:24 +0200 Subject: [PATCH] listview: GtkListHeader and gtk_list_view_set_header_factory() Adds infrastructure for handling factories for list headers. For now, listitemmanager doesn't instantiate them, so they aren't used. --- gtk/gtk.h | 1 + gtk/gtklistheader.c | 381 +++++++++++++++++++++++++++++++ gtk/gtklistheader.h | 50 ++++ gtk/gtklistheaderbase.c | 7 + gtk/gtklistheaderprivate.h | 52 +++++ gtk/gtklistheaderwidget.c | 295 ++++++++++++++++++++++++ gtk/gtklistheaderwidgetprivate.h | 61 +++++ gtk/gtklistview.c | 173 +++++++++++--- gtk/gtklistview.h | 7 + gtk/gtklistviewprivate.h | 1 + gtk/meson.build | 3 + 11 files changed, 999 insertions(+), 32 deletions(-) create mode 100644 gtk/gtklistheader.c create mode 100644 gtk/gtklistheader.h create mode 100644 gtk/gtklistheaderprivate.h create mode 100644 gtk/gtklistheaderwidget.c create mode 100644 gtk/gtklistheaderwidgetprivate.h diff --git a/gtk/gtk.h b/gtk/gtk.h index 009c5353b7..8ad4e38902 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -174,6 +174,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtklistheader.c b/gtk/gtklistheader.c new file mode 100644 index 0000000000..62cfd5b4c9 --- /dev/null +++ b/gtk/gtklistheader.c @@ -0,0 +1,381 @@ +/* + * Copyright © 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistheaderprivate.h" + +/** + * GtkListHeader: + * + * `GtkListHeader` is used by list widgets to represent the headers they + * display. + * + * The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via + * their factory, but provide a different set of properties suitable for + * managing the header instead of individual items. + * + * Since: 4.12 + */ + +enum +{ + PROP_0, + PROP_CHILD, + PROP_END, + PROP_ITEM, + PROP_N_ITEMS, + PROP_START, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkListHeader, gtk_list_header, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_list_header_dispose (GObject *object) +{ + GtkListHeader *self = GTK_LIST_HEADER (object); + + g_assert (self->owner == NULL); /* would hold a reference */ + g_clear_object (&self->child); + + G_OBJECT_CLASS (gtk_list_header_parent_class)->dispose (object); +} + +static void +gtk_list_header_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkListHeader *self = GTK_LIST_HEADER (object); + + switch (property_id) + { + case PROP_CHILD: + g_value_set_object (value, self->child); + break; + + case PROP_END: + if (self->owner) + g_value_set_uint (value, gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner))); + else + g_value_set_uint (value, GTK_INVALID_LIST_POSITION); + break; + + case PROP_ITEM: + if (self->owner) + g_value_set_object (value, gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner))); + break; + + case PROP_N_ITEMS: + g_value_set_uint (value, gtk_list_header_get_n_items (self)); + break; + + case PROP_START: + if (self->owner) + g_value_set_uint (value, gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner))); + else + g_value_set_uint (value, GTK_INVALID_LIST_POSITION); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_header_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkListHeader *self = GTK_LIST_HEADER (object); + + switch (property_id) + { + case PROP_CHILD: + gtk_list_header_set_child (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_header_class_init (GtkListHeaderClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = gtk_list_header_dispose; + gobject_class->get_property = gtk_list_header_get_property; + gobject_class->set_property = gtk_list_header_set_property; + + /** + * GtkListHeader:child: (attributes org.gtk.Property.get=gtk_list_header_get_child org.gtk.Property.set=gtk_list_header_set_child) + * + * Widget used for display. + * + * Since: 4.12 + */ + properties[PROP_CHILD] = + g_param_spec_object ("child", NULL, NULL, + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListHeader:end: (attributes org.gtk.Property.get=gtk_list_header_get_end) + * + * The first position no longer part of this section. + * + * Since: 4.12 + */ + properties[PROP_END] = + g_param_spec_uint ("end", NULL, NULL, + 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListHeader:item: (attributes org.gtk.Property.get=gtk_list_header_get_item) + * + * The item at the start of the section. + * + * Since: 4.12 + */ + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListHeader:n-items: (attributes org.gtk.Property.get=gtk_list_header_get_n_items) + * + * Number of items in this section. + * + * Since: 4.12 + */ + properties[PROP_N_ITEMS] = + g_param_spec_uint ("n-items", NULL, NULL, + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkListHeader:start: (attributes org.gtk.Property.get=gtk_list_header_get_start) + * + * First position of items in this section. + * + * Since: 4.12 + */ + properties[PROP_START] = + g_param_spec_uint ("start", NULL, NULL, + 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_list_header_init (GtkListHeader *self) +{ +} + +GtkListHeader * +gtk_list_header_new (void) +{ + return g_object_new (GTK_TYPE_LIST_HEADER, NULL); +} + +void +gtk_list_header_do_notify (GtkListHeader *list_header, + gboolean notify_item, + gboolean notify_start, + gboolean notify_end, + gboolean notify_n_items) +{ + GObject *object = G_OBJECT (list_header); + + if (notify_item) + g_object_notify_by_pspec (object, properties[PROP_ITEM]); + if (notify_start) + g_object_notify_by_pspec (object, properties[PROP_START]); + if (notify_end) + g_object_notify_by_pspec (object, properties[PROP_END]); + if (notify_n_items) + g_object_notify_by_pspec (object, properties[PROP_N_ITEMS]); +} + +/** + * gtk_list_header_get_item: (attributes org.gtk.Method.get_property=item) + * @self: a `GtkListHeader` + * + * Gets the model item at the start of the section. + * This is the item that occupies the list model at position + * [property@Gtk.ListHeader:start]. + * + * If @self is unbound, this function returns %NULL. + * + * Returns: (nullable) (transfer none) (type GObject): The item displayed + * + * Since: 4.12 + **/ +gpointer +gtk_list_header_get_item (GtkListHeader *self) +{ + g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL); + + if (self->owner) + return gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner)); + else + return NULL; +} + +/** + * gtk_list_header_get_child: (attributes org.gtk.Method.get_property=child) + * @self: a `GtkListHeader` + * + * Gets the child previously set via gtk_list_header_set_child() or + * %NULL if none was set. + * + * Returns: (transfer none) (nullable): The child + * + * Since: 4.12 + */ +GtkWidget * +gtk_list_header_get_child (GtkListHeader *self) +{ + g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL); + + return self->child; +} + +/** + * gtk_list_header_set_child: (attributes org.gtk.Method.set_property=child) + * @self: a `GtkListHeader` + * @child: (nullable): The list item's child or %NULL to unset + * + * Sets the child to be used for this listitem. + * + * This function is typically called by applications when + * setting up a header so that the widget can be reused when + * binding it multiple times. + * + * Since: 4.12 + */ +void +gtk_list_header_set_child (GtkListHeader *self, + GtkWidget *child) +{ + g_return_if_fail (GTK_IS_LIST_HEADER (self)); + g_return_if_fail (child == NULL || gtk_widget_get_parent (child) == NULL); + + if (self->child == child) + return; + + g_clear_object (&self->child); + + if (child) + { + g_object_ref_sink (child); + self->child = child; + } + + if (self->owner) + gtk_list_header_widget_set_child (self->owner, child); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]); +} + +/** + * gtk_list_header_get_start: (attributes org.gtk.Method.get_property=start) + * @self: a `GtkListHeader` + * + * Gets the start position in the model of the section that @self is + * currently the header for. + * + * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned. + * + * Returns: The start position of the section + * + * Since: 4.12 + */ +guint +gtk_list_header_get_start (GtkListHeader *self) +{ + g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION); + + if (self->owner) + return gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner)); + else + return GTK_INVALID_LIST_POSITION; +} + +/** + * gtk_list_header_get_end: (attributes org.gtk.Method.get_property=end) + * @self: a `GtkListHeader` + * + * Gets the end position in the model of the section that @self is + * currently the header for. + * + * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned. + * + * Returns: The end position of the section + * + * Since: 4.12 + */ +guint +gtk_list_header_get_end (GtkListHeader *self) +{ + g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION); + + if (self->owner) + return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)); + else + return GTK_INVALID_LIST_POSITION; +} + +/** + * gtk_list_header_get_n_items: (attributes org.gtk.Method.get_property=n-items) + * @self: a `GtkListHeader` + * + * Gets the the number of items in the section. + * + * If @self is unbound, 0 is returned. + * + * Returns: The number of items in the section + * + * Since: 4.12 + */ +guint +gtk_list_header_get_n_items (GtkListHeader *self) +{ + g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION); + + if (self->owner) + return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)) - + gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner)); + else + return 0; +} + diff --git a/gtk/gtklistheader.h b/gtk/gtklistheader.h new file mode 100644 index 0000000000..87862bab59 --- /dev/null +++ b/gtk/gtklistheader.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_HEADER (gtk_list_header_get_type ()) +GDK_AVAILABLE_IN_4_12 +GDK_DECLARE_INTERNAL_TYPE (GtkListHeader, gtk_list_header, GTK, LIST_HEADER, GObject) + +GDK_AVAILABLE_IN_4_12 +gpointer gtk_list_header_get_item (GtkListHeader *self); +GDK_AVAILABLE_IN_4_12 +guint gtk_list_header_get_start (GtkListHeader *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +guint gtk_list_header_get_end (GtkListHeader *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_12 +guint gtk_list_header_get_n_items (GtkListHeader *self) G_GNUC_PURE; + +GDK_AVAILABLE_IN_4_12 +void gtk_list_header_set_child (GtkListHeader *self, + GtkWidget *child); +GDK_AVAILABLE_IN_4_12 +GtkWidget * gtk_list_header_get_child (GtkListHeader *self); + +G_END_DECLS + diff --git a/gtk/gtklistheaderbase.c b/gtk/gtklistheaderbase.c index 2ba119e665..a2b5b72e1e 100644 --- a/gtk/gtklistheaderbase.c +++ b/gtk/gtklistheaderbase.c @@ -76,6 +76,13 @@ gtk_list_header_base_update (GtkListHeaderBase *self, guint start, guint end) { + GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self); + + if (priv->item == item && + priv->start == start && + priv->end == end) + return; + GTK_LIST_HEADER_BASE_GET_CLASS (self)->update (self, item, start, end); } diff --git a/gtk/gtklistheaderprivate.h b/gtk/gtklistheaderprivate.h new file mode 100644 index 0000000000..81aa850c8a --- /dev/null +++ b/gtk/gtklistheaderprivate.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#include "gtklistheader.h" + +#include "gtklistheaderwidgetprivate.h" + +G_BEGIN_DECLS + +struct _GtkListHeader +{ + GObject parent_instance; + + GtkListHeaderWidget *owner; /* has a reference */ + + GtkWidget *child; +}; + +struct _GtkListHeaderClass +{ + GObjectClass parent_class; +}; + +GtkListHeader * gtk_list_header_new (void); + +void gtk_list_header_do_notify (GtkListHeader *list_header, + gboolean notify_item, + gboolean notify_start, + gboolean notify_end, + gboolean notify_n_items); + + +G_END_DECLS + diff --git a/gtk/gtklistheaderwidget.c b/gtk/gtklistheaderwidget.c new file mode 100644 index 0000000000..5a1a2559ce --- /dev/null +++ b/gtk/gtklistheaderwidget.c @@ -0,0 +1,295 @@ +/* + * Copyright © 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtklistheaderwidgetprivate.h" + +#include "gtkbinlayout.h" +#include "gtklistheaderprivate.h" +#include "gtklistitemfactoryprivate.h" +#include "gtklistbaseprivate.h" +#include "gtkwidget.h" + +typedef struct _GtkListHeaderWidgetPrivate GtkListHeaderWidgetPrivate; +struct _GtkListHeaderWidgetPrivate +{ + GtkListItemFactory *factory; + + GtkListHeader *header; +}; + +enum { + PROP_0, + PROP_FACTORY, + + N_PROPS +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderWidget, gtk_list_header_widget, GTK_TYPE_LIST_HEADER_BASE) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_list_header_widget_setup_func (gpointer object, + gpointer data) +{ + GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data); + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + GtkListHeader *header = object; + + priv->header = header; + header->owner = self; + + gtk_list_header_widget_set_child (self, header->child); + + gtk_list_header_do_notify (header, + gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL, + gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self))); +} + +static void +gtk_list_header_widget_setup_factory (GtkListHeaderWidget *self) +{ + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + GtkListHeader *header; + + header = gtk_list_header_new (); + + gtk_list_item_factory_setup (priv->factory, + G_OBJECT (header), + gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL, + gtk_list_header_widget_setup_func, + self); + + g_assert (priv->header == header); +} + +static void +gtk_list_header_widget_teardown_func (gpointer object, + gpointer data) +{ + GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data); + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + GtkListHeader *header = object; + + header->owner = NULL; + priv->header = NULL; + + gtk_list_header_widget_set_child (self, NULL); + + gtk_list_header_do_notify (header, + gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL, + gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION, + gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self))); +} + +static void +gtk_list_header_widget_teardown_factory (GtkListHeaderWidget *self) +{ + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + gpointer header = priv->header; + + gtk_list_item_factory_teardown (priv->factory, + header, + gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL, + gtk_list_header_widget_teardown_func, + self); + + g_assert (priv->header == NULL); + g_object_unref (header); +} + +typedef struct { + GtkListHeaderWidget *widget; + gpointer item; + guint start; + guint end; +} GtkListHeaderWidgetUpdate; + +static void +gtk_list_header_widget_update_func (gpointer object, + gpointer data) +{ + GtkListHeaderWidgetUpdate *update = data; + GtkListHeaderWidget *self = update->widget; + GtkListHeaderBase *base = GTK_LIST_HEADER_BASE (self); + /* Track notify manually instead of freeze/thaw_notify for performance reasons. */ + gboolean notify_item, notify_start, notify_end, notify_n_items; + + /* FIXME: It's kinda evil to notify external objects from here... */ + notify_item = gtk_list_header_base_get_item (base) != update->item; + notify_start = gtk_list_header_base_get_start (base) != update->start; + notify_end = gtk_list_header_base_get_end (base) != update->end; + notify_n_items = gtk_list_header_base_get_end (base) - gtk_list_header_base_get_start (base) != update->end - update->start; + + GTK_LIST_HEADER_BASE_CLASS (gtk_list_header_widget_parent_class)->update (base, + update->item, + update->start, + update->end); + + if (object) + gtk_list_header_do_notify (object, notify_item, notify_start, notify_end, notify_n_items); +} + +static void +gtk_list_header_widget_update (GtkListHeaderBase *base, + gpointer item, + guint start, + guint end) +{ + GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (base); + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + GtkListHeaderWidgetUpdate update = { self, item, start, end }; + + if (priv->header) + { + gtk_list_item_factory_update (priv->factory, + G_OBJECT (priv->header), + gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL, + item != NULL, + gtk_list_header_widget_update_func, + &update); + } + else + { + gtk_list_header_widget_update_func (NULL, &update); + } +} + +static void +gtk_list_header_widget_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object); + + switch (property_id) + { + case PROP_FACTORY: + gtk_list_header_widget_set_factory (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_list_header_widget_clear_factory (GtkListHeaderWidget *self) +{ + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + + if (priv->factory == NULL) + return; + + if (priv->header) + gtk_list_header_widget_teardown_factory (self); + + g_clear_object (&priv->factory); +} + +static void +gtk_list_header_widget_dispose (GObject *object) +{ + GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object); + + gtk_list_header_widget_clear_factory (self); + + G_OBJECT_CLASS (gtk_list_header_widget_parent_class)->dispose (object); +} + +static void +gtk_list_header_widget_class_init (GtkListHeaderWidgetClass *klass) +{ + GtkListHeaderBaseClass *base_class = GTK_LIST_HEADER_BASE_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + base_class->update = gtk_list_header_widget_update; + + gobject_class->set_property = gtk_list_header_widget_set_property; + gobject_class->dispose = gtk_list_header_widget_dispose; + + properties[PROP_FACTORY] = + g_param_spec_object ("factory", NULL, NULL, + GTK_TYPE_LIST_ITEM_FACTORY, + G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, I_("header")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_ROW_HEADER); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +gtk_list_header_widget_init (GtkListHeaderWidget *self) +{ +} + +void +gtk_list_header_widget_set_factory (GtkListHeaderWidget *self, + GtkListItemFactory *factory) +{ + GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self); + + if (priv->factory == factory) + return; + + gtk_list_header_widget_clear_factory (self); + + if (factory) + { + priv->factory = g_object_ref (factory); + + gtk_list_header_widget_setup_factory (self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]); +} + +GtkWidget * +gtk_list_header_widget_new (GtkListItemFactory *factory) +{ + return g_object_new (GTK_TYPE_LIST_HEADER_WIDGET, + "factory", factory, + NULL); +} + +void +gtk_list_header_widget_set_child (GtkListHeaderWidget *self, + GtkWidget *child) +{ + GtkWidget *cur_child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + if (cur_child == child) + return; + + g_clear_pointer (&cur_child, gtk_widget_unparent); + + if (child) + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} + diff --git a/gtk/gtklistheaderwidgetprivate.h b/gtk/gtklistheaderwidgetprivate.h new file mode 100644 index 0000000000..54f3c666d8 --- /dev/null +++ b/gtk/gtklistheaderwidgetprivate.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#include "gtklistheaderbaseprivate.h" + +#include "gtklistitemfactory.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_HEADER_WIDGET (gtk_list_header_widget_get_type ()) +#define GTK_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidget)) +#define GTK_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass)) +#define GTK_IS_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_WIDGET)) +#define GTK_IS_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_WIDGET)) +#define GTK_LIST_HEADER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass)) + +typedef struct _GtkListHeaderWidget GtkListHeaderWidget; +typedef struct _GtkListHeaderWidgetClass GtkListHeaderWidgetClass; + +struct _GtkListHeaderWidget +{ + GtkListHeaderBase parent_instance; +}; + +struct _GtkListHeaderWidgetClass +{ + GtkListHeaderBaseClass parent_class; +}; + +GType gtk_list_header_widget_get_type (void) G_GNUC_CONST; + +GtkWidget * gtk_list_header_widget_new (GtkListItemFactory *factory); + +void gtk_list_header_widget_set_factory (GtkListHeaderWidget *self, + GtkListItemFactory *factory); +GtkListItemFactory * gtk_list_header_widget_get_factory (GtkListHeaderWidget *self); + +void gtk_list_header_widget_set_child (GtkListHeaderWidget *self, + GtkWidget *child); + + +G_END_DECLS + diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f6709be181..0283ec8b4c 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -23,6 +23,7 @@ #include "gtkbitset.h" #include "gtklistbaseprivate.h" +#include "gtklistheaderwidgetprivate.h" #include "gtklistitemmanagerprivate.h" #include "gtklistitemwidgetprivate.h" #include "gtkmultiselection.h" @@ -145,6 +146,7 @@ enum PROP_0, PROP_ENABLE_RUBBERBAND, PROP_FACTORY, + PROP_HEADER_FACTORY, PROP_MODEL, PROP_SHOW_SEPARATORS, PROP_SINGLE_CLICK_ACTIVATE, @@ -163,29 +165,6 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE) static GParamSpec *properties[N_PROPS] = { NULL, }; static guint signals[LAST_SIGNAL] = { 0 }; -static void G_GNUC_UNUSED -dump (GtkListView *self) -{ - GtkListTile *tile; - guint n_widgets, n_list_rows; - - n_widgets = 0; - n_list_rows = 0; - //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end); - for (tile = gtk_list_item_manager_get_first (self->item_manager); - tile; - tile = gtk_rb_tree_node_get_next (tile)) - { - if (tile->widget) - n_widgets++; - n_list_rows++; - g_print (" %4u%s %d,%d,%d,%d\n", tile->n_items, tile->widget ? " (widget)" : "", - tile->area.x, tile->area.y, tile->area.width, tile->area.height); - } - - g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); -} - static GtkListTile * gtk_list_view_split (GtkListBase *base, GtkListTile *tile, @@ -215,6 +194,13 @@ gtk_list_view_split (GtkListBase *base, return new_tile; } +static void +gtk_list_view_prepare_section (GtkListBase *base, + GtkListTile *tile, + guint position) +{ +} + /* We define the listview as **inert** when the factory isn't used. */ static gboolean gtk_list_view_is_inert (GtkListView *self) @@ -228,7 +214,8 @@ gtk_list_view_is_inert (GtkListView *self) static void gtk_list_view_update_factories_with (GtkListView *self, - GtkListItemFactory *factory) + GtkListItemFactory *factory, + GtkListItemFactory *header_factory) { GtkListTile *tile; @@ -236,8 +223,27 @@ gtk_list_view_update_factories_with (GtkListView *self, tile != NULL; tile = gtk_rb_tree_node_get_next (tile)) { - if (tile->widget) - gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory); + switch (tile->type) + { + case GTK_LIST_TILE_ITEM: + if (tile->widget) + gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory); + break; + case GTK_LIST_TILE_HEADER: + if (tile->widget) + gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), header_factory); + break; + case GTK_LIST_TILE_UNMATCHED_HEADER: + case GTK_LIST_TILE_FOOTER: + case GTK_LIST_TILE_UNMATCHED_FOOTER: + case GTK_LIST_TILE_FILLER: + case GTK_LIST_TILE_REMOVED: + g_assert (tile->widget == NULL); + break; + default: + g_assert_not_reached(); + break; + } } } @@ -245,13 +251,14 @@ static void gtk_list_view_update_factories (GtkListView *self) { gtk_list_view_update_factories_with (self, - gtk_list_view_is_inert (self) ? NULL : self->factory); + gtk_list_view_is_inert (self) ? NULL : self->factory, + gtk_list_view_is_inert (self) ? NULL : self->header_factory); } static void gtk_list_view_clear_factories (GtkListView *self) { - gtk_list_view_update_factories_with (self, NULL); + gtk_list_view_update_factories_with (self, NULL, NULL); } static GtkListItemBase * @@ -275,6 +282,20 @@ gtk_list_view_create_list_widget (GtkListBase *base) return GTK_LIST_ITEM_BASE (result); } +static GtkListHeaderBase * +gtk_list_view_create_header_widget (GtkListBase *base) +{ + GtkListView *self = GTK_LIST_VIEW (base); + GtkListItemFactory *factory; + + if (gtk_list_view_is_inert (self)) + factory = NULL; + else + factory = self->header_factory; + + return GTK_LIST_HEADER_BASE (gtk_list_header_widget_new (factory)); +} + static gboolean gtk_list_view_get_allocation (GtkListBase *base, guint pos, @@ -527,8 +548,11 @@ gtk_list_view_measure_list (GtkWidget *widget, gtk_widget_measure (tile->widget, orientation, for_size, &child_min, &child_nat, NULL, NULL); - g_array_append_val (min_heights, child_min); - g_array_append_val (nat_heights, child_nat); + if (tile->type == GTK_LIST_TILE_ITEM) + { + g_array_append_val (min_heights, child_min); + g_array_append_val (nat_heights, child_nat); + } min += child_min; nat += child_nat; } @@ -622,7 +646,8 @@ gtk_list_view_size_allocate (GtkWidget *widget, else row_height = nat; gtk_list_tile_set_area_size (self->item_manager, tile, list_width, row_height); - g_array_append_val (heights, row_height); + if (tile->type == GTK_LIST_TILE_ITEM) + g_array_append_val (heights, row_height); } /* step 3: determine height of unknown items and set the positions */ @@ -723,6 +748,10 @@ gtk_list_view_get_property (GObject *object, g_value_set_object (value, self->factory); break; + case PROP_HEADER_FACTORY: + g_value_set_object (value, self->header_factory); + break; + case PROP_MODEL: g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self))); break; @@ -763,6 +792,10 @@ gtk_list_view_set_property (GObject *object, gtk_list_view_set_factory (self, g_value_get_object (value)); break; + case PROP_HEADER_FACTORY: + gtk_list_view_set_header_factory (self, g_value_get_object (value)); + break; + case PROP_MODEL: gtk_list_view_set_model (self, g_value_get_object (value)); break; @@ -812,6 +845,8 @@ gtk_list_view_class_init (GtkListViewClass *klass) list_base_class->split = gtk_list_view_split; list_base_class->create_list_widget = gtk_list_view_create_list_widget; + list_base_class->prepare_section = gtk_list_view_prepare_section; + list_base_class->create_header_widget = gtk_list_view_create_header_widget; list_base_class->get_allocation = gtk_list_view_get_allocation; list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect; list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation; @@ -849,6 +884,18 @@ gtk_list_view_class_init (GtkListViewClass *klass) GTK_TYPE_LIST_ITEM_FACTORY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkListView:header-factory: (attributes org.gtk.Property.get=gtk_list_view_get_header_factory org.gtk.Property.set=gtk_list_view_set_header_factory) + * + * Factory for creating header widgets. + * + * Since: 4.12 + */ + properties[PROP_HEADER_FACTORY] = + g_param_spec_object ("header-factory", NULL, NULL, + GTK_TYPE_LIST_ITEM_FACTORY, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * GtkListView:model: (attributes org.gtk.Property.get=gtk_list_view_get_model org.gtk.Property.set=gtk_list_view_set_model) * @@ -1070,6 +1117,68 @@ gtk_list_view_set_factory (GtkListView *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]); } +/** + * gtk_list_view_get_header_factory: (attributes org.gtk.Method.get_property=header-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.12 + */ +GtkListItemFactory * +gtk_list_view_get_header_factory (GtkListView *self) +{ + g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL); + + return self->header_factory; +} + +/** + * gtk_list_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory) + * @self: a `GtkListView` + * @factory: (nullable) (transfer none): the factory to use + * + * Sets the `GtkListItemFactory` to use for populating the + * [class@Gtk.ListHeader] objects used in section headers. + * + * If this factory is set to %NULL, the list will not show section headers. + * + * Since: 4.12 + */ +void +gtk_list_view_set_header_factory (GtkListView *self, + GtkListItemFactory *factory) +{ + gboolean had_sections; + + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory)); + + had_sections = gtk_list_item_manager_get_has_sections (self->item_manager); + + if (!g_set_object (&self->header_factory, factory)) + return; + + gtk_list_item_manager_set_has_sections (self->item_manager, factory != NULL); + + if (had_sections && gtk_list_item_manager_get_has_sections (self->item_manager)) + { + GtkListTile *tile; + + for (tile = gtk_list_item_manager_get_first (self->item_manager); + tile != NULL; + tile = gtk_rb_tree_node_get_next (tile)) + { + if (tile->widget && tile->type == GTK_LIST_TILE_HEADER) + gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), factory); + } + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]); +} + /** * gtk_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators) * @self: a `GtkListView` @@ -1139,7 +1248,7 @@ gtk_list_view_set_single_click_activate (GtkListView *self, tile != NULL; tile = gtk_rb_tree_node_get_next (tile)) { - if (tile->widget) + if (tile->widget && tile->type == GTK_LIST_TILE_ITEM) gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (tile->widget), single_click_activate); } diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index b3e3afb60a..df4901e319 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -57,6 +57,13 @@ GDK_AVAILABLE_IN_ALL GtkListItemFactory * gtk_list_view_get_factory (GtkListView *self); +GDK_AVAILABLE_IN_4_12 +void gtk_list_view_set_header_factory (GtkListView *self, + GtkListItemFactory *factory); +GDK_AVAILABLE_IN_4_12 +GtkListItemFactory * + gtk_list_view_get_header_factory (GtkListView *self); + GDK_AVAILABLE_IN_ALL void gtk_list_view_set_show_separators (GtkListView *self, gboolean show_separators); diff --git a/gtk/gtklistviewprivate.h b/gtk/gtklistviewprivate.h index 002376fc1e..7dd02ce3f8 100644 --- a/gtk/gtklistviewprivate.h +++ b/gtk/gtklistviewprivate.h @@ -30,6 +30,7 @@ struct _GtkListView GtkListItemManager *item_manager; GtkListItemFactory *factory; + GtkListItemFactory *header_factory; gboolean show_separators; gboolean single_click_activate; }; diff --git a/gtk/meson.build b/gtk/meson.build index 9f6a6dd57c..ec214f0d74 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -271,7 +271,9 @@ gtk_public_sources = files([ 'gtklinkbutton.c', 'gtklistbox.c', 'gtklistfactorywidget.c', + 'gtklistheader.c', 'gtklistheaderbase.c', + 'gtklistheaderwidget.c', 'gtklistitem.c', 'gtklistitembase.c', 'gtklistitemfactory.c', @@ -520,6 +522,7 @@ gtk_public_headers = files([ 'gtklinkbutton.h', 'gtklistbase.h', 'gtklistbox.h', + 'gtklistheader.h', 'gtklistitem.h', 'gtklistitemfactory.h', 'gtklistview.h',