Merge branch 'wip/otte/sections' into 'main'

Add GtkSectionModel

See merge request GNOME/gtk!5818
This commit is contained in:
Matthias Clasen 2023-05-09 16:36:55 +00:00
commit 526ddfa866
42 changed files with 4289 additions and 515 deletions

View File

@ -195,6 +195,9 @@
<gresource prefix="/listview_settings">
<file>listview_settings.ui</file>
</gresource>
<gresource prefix="/listview_settings2">
<file>listview_settings2.ui</file>
</gresource>
<gresource prefix="/listview_ucd_data/">
<file>ucdnames.data</file>
</gresource>
@ -312,6 +315,7 @@
<file>listview_minesweeper.c</file>
<file>listview_selections.c</file>
<file>listview_settings.c</file>
<file>listview_settings2.c</file>
<file>listview_ucd.c</file>
<file>listview_weather.c</file>
<file>listview_words.c</file>

View File

@ -14,138 +14,7 @@
#include <gtk/gtk.h>
#include <stdlib.h>
/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
typedef struct _SettingsKey SettingsKey;
struct _SettingsKey
{
GObject parent_instance;
GSettings *settings;
GSettingsSchemaKey *key;
};
enum {
PROP_0,
PROP_NAME,
PROP_SUMMARY,
PROP_DESCRIPTION,
PROP_VALUE,
PROP_TYPE,
PROP_DEFAULT_VALUE,
N_PROPS
};
#define SETTINGS_TYPE_KEY (settings_key_get_type ())
G_DECLARE_FINAL_TYPE (SettingsKey, settings_key, SETTINGS, KEY, GObject);
G_DEFINE_TYPE (SettingsKey, settings_key, G_TYPE_OBJECT);
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
settings_key_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
SettingsKey *self = SETTINGS_KEY (object);
switch (property_id)
{
case PROP_DESCRIPTION:
g_value_set_string (value, g_settings_schema_key_get_description (self->key));
break;
case PROP_NAME:
g_value_set_string (value, g_settings_schema_key_get_name (self->key));
break;
case PROP_SUMMARY:
g_value_set_string (value, g_settings_schema_key_get_summary (self->key));
break;
case PROP_VALUE:
{
GVariant *variant = g_settings_get_value (self->settings, g_settings_schema_key_get_name (self->key));
g_value_take_string (value, g_variant_print (variant, FALSE));
g_variant_unref (variant);
}
break;
case PROP_TYPE:
{
const GVariantType *type = g_settings_schema_key_get_value_type (self->key);
g_value_set_string (value, g_variant_type_peek_string (type));
}
break;
case PROP_DEFAULT_VALUE:
{
GVariant *variant = g_settings_schema_key_get_default_value (self->key);
g_value_take_string (value, g_variant_print (variant, FALSE));
g_variant_unref (variant);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
settings_key_finalize (GObject *object)
{
SettingsKey *self = SETTINGS_KEY (object);
g_object_unref (self->settings);
g_settings_schema_key_unref (self->key);
G_OBJECT_CLASS (settings_key_parent_class)->finalize (object);
}
static void
settings_key_class_init (SettingsKeyClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = settings_key_finalize;
gobject_class->get_property = settings_key_get_property;
properties[PROP_DESCRIPTION] =
g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_NAME] =
g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_SUMMARY] =
g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_VALUE] =
g_param_spec_string ("value", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_TYPE] =
g_param_spec_string ("type", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_DEFAULT_VALUE] =
g_param_spec_string ("default-value", NULL, NULL, NULL, G_PARAM_READABLE);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
settings_key_init (SettingsKey *self)
{
}
static SettingsKey *
settings_key_new (GSettings *settings,
GSettingsSchemaKey *key)
{
SettingsKey *result = g_object_new (SETTINGS_TYPE_KEY, NULL);
result->settings = g_object_ref (settings);
result->key = g_settings_schema_key_ref (key);
return result;
}
#include "settings-key.h"
static void
item_value_changed (GtkEditableLabel *label,
@ -153,6 +22,7 @@ item_value_changed (GtkEditableLabel *label,
GtkColumnViewCell *cell)
{
SettingsKey *self;
GSettingsSchemaKey *key;
const char *text;
const GVariantType *type;
GVariant *variant;
@ -163,9 +33,10 @@ item_value_changed (GtkEditableLabel *label,
text = gtk_editable_get_text (GTK_EDITABLE (label));
self = gtk_column_view_cell_get_item (cell);
key = settings_key_get_key (self);
type = g_settings_schema_key_get_value_type (self->key);
name = g_settings_schema_key_get_name (self->key);
type = g_settings_schema_key_get_value_type (key);
name = g_settings_schema_key_get_name (key);
variant = g_variant_parse (type, text, NULL, NULL, &error);
if (!variant)
@ -175,13 +46,13 @@ item_value_changed (GtkEditableLabel *label,
goto revert;
}
if (!g_settings_schema_key_range_check (self->key, variant))
if (!g_settings_schema_key_range_check (key, variant))
{
g_warning ("Not a valid value for %s", name);
goto revert;
}
g_settings_set_value (self->settings, name, variant);
g_settings_set_value (settings_key_get_settings (self), name, variant);
g_variant_unref (variant);
return;

View File

@ -0,0 +1,230 @@
/* Lists/Settings v2
* #Keywords: GtkListHeaderFactory, GtkSectionModel
*
* This demo shows a settings viewer for GSettings.
*
* It demonstrates how to implement support for sections with GtkListView.
*
* It also shows how to quickly flatten a large tree of items into a list
* that can be filtered to find the itmes one is looking for.
*/
#include <gtk/gtk.h>
#include "settings-key.h"
static void
item_value_changed (GtkEditableLabel *label,
GParamSpec *pspec,
GtkColumnViewCell *cell)
{
SettingsKey *self;
GSettingsSchemaKey *key;
const char *text;
const GVariantType *type;
GVariant *variant;
GError *error = NULL;
const char *name;
char *value;
text = gtk_editable_get_text (GTK_EDITABLE (label));
self = gtk_column_view_cell_get_item (cell);
key = settings_key_get_key (self);
type = g_settings_schema_key_get_value_type (key);
name = g_settings_schema_key_get_name (key);
variant = g_variant_parse (type, text, NULL, NULL, &error);
if (!variant)
{
g_warning ("%s", error->message);
g_clear_error (&error);
goto revert;
}
if (!g_settings_schema_key_range_check (key, variant))
{
g_warning ("Not a valid value for %s", name);
goto revert;
}
g_settings_set_value (settings_key_get_settings (self), name, variant);
g_variant_unref (variant);
return;
revert:
gtk_widget_error_bell (GTK_WIDGET (label));
g_object_get (self, "value", &value, NULL);
gtk_editable_set_text (GTK_EDITABLE (label), value);
g_free (value);
}
static int
strvcmp (gconstpointer p1,
gconstpointer p2)
{
const char * const *s1 = p1;
const char * const *s2 = p2;
return strcmp (*s1, *s2);
}
static gpointer
map_settings_to_keys (gpointer item,
gpointer unused)
{
GSettings *settings = item;
GSettingsSchema *schema;
GListStore *store;
char **keys;
guint i;
g_object_get (settings, "settings-schema", &schema, NULL);
store = g_list_store_new (SETTINGS_TYPE_KEY);
keys = g_settings_schema_list_keys (schema);
for (i = 0; keys[i] != NULL; i++)
{
GSettingsSchemaKey *almost_there = g_settings_schema_get_key (schema, keys[i]);
SettingsKey *finally = settings_key_new (settings, almost_there);
g_list_store_append (store, finally);
g_object_unref (finally);
g_settings_schema_key_unref (almost_there);
}
g_strfreev (keys);
g_settings_schema_unref (schema);
g_object_unref (settings);
return store;
}
static GListModel *
create_settings_model (gpointer item,
gpointer unused)
{
GSettings *settings = item;
char **schemas;
GListStore *result;
guint i;
if (settings == NULL)
{
g_settings_schema_source_list_schemas (g_settings_schema_source_get_default (),
TRUE,
&schemas,
NULL);
}
else
{
schemas = g_settings_list_children (settings);
}
if (schemas == NULL || schemas[0] == NULL)
{
g_free (schemas);
return NULL;
}
qsort (schemas, g_strv_length (schemas), sizeof (char *), strvcmp);
result = g_list_store_new (G_TYPE_SETTINGS);
for (i = 0; schemas[i] != NULL; i++)
{
GSettings *child;
if (settings == NULL)
child = g_settings_new (schemas[i]);
else
child = g_settings_get_child (settings, schemas[i]);
g_list_store_append (result, child);
g_object_unref (child);
}
g_strfreev (schemas);
return G_LIST_MODEL (result);
}
static void
search_enabled (GtkSearchEntry *entry)
{
gtk_editable_set_text (GTK_EDITABLE (entry), "");
}
static void
stop_search (GtkSearchEntry *entry,
gpointer data)
{
gtk_editable_set_text (GTK_EDITABLE (entry), "");
}
static GtkWidget *window = NULL;
GtkWidget *
do_listview_settings2 (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkListView *listview;
GListModel *model;
GtkTreeListModel *treemodel;
GtkNoSelection *selection;
GtkBuilderScope *scope;
GtkBuilder *builder;
GError *error = NULL;
GtkFilter *filter;
g_type_ensure (SETTINGS_TYPE_KEY);
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback (scope, search_enabled);
gtk_builder_cscope_add_callback (scope, stop_search);
gtk_builder_cscope_add_callback (scope, settings_key_get_search_string);
gtk_builder_cscope_add_callback (scope, item_value_changed);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
g_object_unref (scope);
gtk_builder_add_from_resource (builder, "/listview_settings2/listview_settings2.ui", &error);
g_assert_no_error (error);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
listview = GTK_LIST_VIEW (gtk_builder_get_object (builder, "listview"));
filter = GTK_FILTER (gtk_builder_get_object (builder, "filter"));
model = create_settings_model (NULL, NULL);
treemodel = gtk_tree_list_model_new (model,
TRUE,
TRUE,
create_settings_model,
NULL,
NULL);
model = G_LIST_MODEL (gtk_map_list_model_new (G_LIST_MODEL (treemodel), map_settings_to_keys, NULL, NULL));
model = G_LIST_MODEL (gtk_flatten_list_model_new (model));
model = G_LIST_MODEL (gtk_filter_list_model_new (model, g_object_ref (filter)));
selection = gtk_no_selection_new (model);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), GTK_SELECTION_MODEL (selection));
g_object_unref (selection);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_widget_set_visible (window, TRUE);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkStringFilter" id="filter">
<property name="expression">
<closure type="gchararray" function="settings_key_get_search_string" />
</property>
<property name="search" bind-source="entry" bind-property="text" />
</object>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Settings</property>
<property name="default-width">640</property>
<property name="default-height">480</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="search_button">
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional"/>
<signal name="notify::search-mode-enabled" handler="search_enabled" object="entry"/>
<child>
<object class="GtkSearchEntry" id="entry">
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="GtkListView" id="listview">
<property name="vexpand">1</property>
<style>
<class name="rich-list"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<style>
<class name="dim-label"/>
</style>
<property name="xalign">0</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="summary" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</child>
<child>
<object class="GtkEntry">
<property name="hexpand">1</property>
<property name="halign">end</property>
<binding name="text">
<lookup name="value" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<signal name="notify::label" handler="item_value_changed"/>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<property name="header-factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListHeader">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="schema" type="GSettings">
<lookup name="settings" type="SettingsKey">
<lookup name="item">GtkListHeader</lookup>
</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@ -57,6 +57,7 @@ demos = files([
'listview_minesweeper.c',
'listview_selections.c',
'listview_settings.c',
'listview_settings2.c',
'listview_ucd.c',
'listview_weather.c',
'listview_words.c',
@ -131,6 +132,7 @@ extra_demo_sources = files([
'demo4widget.c',
'pixbufpaintable.c',
'script-names.c',
'settings-key.c',
'unicode-names.c',
'suggestionentry.c',
'language-names.c',

View File

@ -0,0 +1,165 @@
#include "settings-key.h"
/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
struct _SettingsKey
{
GObject parent_instance;
GSettings *settings;
GSettingsSchemaKey *key;
};
enum {
PROP_0,
PROP_NAME,
PROP_SETTINGS,
PROP_SUMMARY,
PROP_DESCRIPTION,
PROP_VALUE,
PROP_TYPE,
PROP_DEFAULT_VALUE,
N_PROPS
};
G_DEFINE_TYPE (SettingsKey, settings_key, G_TYPE_OBJECT);
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
settings_key_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
SettingsKey *self = SETTINGS_KEY (object);
switch (property_id)
{
case PROP_DESCRIPTION:
g_value_set_string (value, g_settings_schema_key_get_description (self->key));
break;
case PROP_NAME:
g_value_set_string (value, g_settings_schema_key_get_name (self->key));
break;
case PROP_SUMMARY:
g_value_set_string (value, g_settings_schema_key_get_summary (self->key));
break;
case PROP_VALUE:
{
GVariant *variant = g_settings_get_value (self->settings, g_settings_schema_key_get_name (self->key));
g_value_take_string (value, g_variant_print (variant, FALSE));
g_variant_unref (variant);
}
break;
case PROP_TYPE:
{
const GVariantType *type = g_settings_schema_key_get_value_type (self->key);
g_value_set_string (value, g_variant_type_peek_string (type));
}
break;
case PROP_DEFAULT_VALUE:
{
GVariant *variant = g_settings_schema_key_get_default_value (self->key);
g_value_take_string (value, g_variant_print (variant, FALSE));
g_variant_unref (variant);
}
break;
case PROP_SETTINGS:
g_value_set_object (value, self->settings);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
settings_key_finalize (GObject *object)
{
SettingsKey *self = SETTINGS_KEY (object);
g_object_unref (self->settings);
g_settings_schema_key_unref (self->key);
G_OBJECT_CLASS (settings_key_parent_class)->finalize (object);
}
static void
settings_key_class_init (SettingsKeyClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = settings_key_finalize;
gobject_class->get_property = settings_key_get_property;
properties[PROP_DESCRIPTION] =
g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_NAME] =
g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_SETTINGS] =
g_param_spec_object ("settings", NULL, NULL, G_TYPE_SETTINGS, G_PARAM_READABLE);
properties[PROP_SUMMARY] =
g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_VALUE] =
g_param_spec_string ("value", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_TYPE] =
g_param_spec_string ("type", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_DEFAULT_VALUE] =
g_param_spec_string ("default-value", NULL, NULL, NULL, G_PARAM_READABLE);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
settings_key_init (SettingsKey *self)
{
}
SettingsKey *
settings_key_new (GSettings *settings,
GSettingsSchemaKey *key)
{
SettingsKey *result = g_object_new (SETTINGS_TYPE_KEY, NULL);
result->settings = g_object_ref (settings);
result->key = g_settings_schema_key_ref (key);
return result;
}
GSettingsSchemaKey *
settings_key_get_key (SettingsKey *self)
{
return self->key;
}
GSettings *
settings_key_get_settings (SettingsKey *self)
{
return self->settings;
}
char *
settings_key_get_search_string (SettingsKey *self)
{
char *schema, *result;
g_object_get (self->settings, "schema-id", &schema, NULL);
result = g_strconcat (g_settings_schema_key_get_name (self->key), " ",
g_settings_schema_key_get_summary (self->key), " ",
schema,
NULL);
g_free (schema);
return result;
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <gtk/gtk.h>
#include <stdlib.h>
/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
typedef struct _SettingsKey SettingsKey;
#define SETTINGS_TYPE_KEY (settings_key_get_type ())
G_DECLARE_FINAL_TYPE (SettingsKey, settings_key, SETTINGS, KEY, GObject);
SettingsKey * settings_key_new (GSettings *settings,
GSettingsSchemaKey *key);
GSettingsSchemaKey * settings_key_get_key (SettingsKey *self);
GSettings * settings_key_get_settings (SettingsKey *self);
char * settings_key_get_search_string (SettingsKey *self);

View File

@ -174,6 +174,7 @@
#include <gtk/gtklistbase.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistheader.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtklistitemfactory.h>
#include <gtk/deprecated/gtkliststore.h>
@ -225,6 +226,7 @@
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtksearchbar.h>
#include <gtk/gtksearchentry.h>
#include <gtk/gtksectionmodel.h>
#include <gtk/gtkselectionfiltermodel.h>
#include <gtk/gtkselectionmodel.h>
#include <gtk/gtkseparator.h>

View File

@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkprivate.h"
#include "gtksectionmodelprivate.h"
/**
* GtkFilterListModel:
@ -135,8 +136,67 @@ gtk_filter_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_filter_list_model_get_item;
}
static void
gtk_filter_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
guint n_items;
guint pos, start, end;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
*out_start = 0;
*out_end = G_MAXUINT;
return;
case GTK_FILTER_MATCH_ALL:
gtk_list_model_get_section (self->model, position, out_start, out_end);
return;
case GTK_FILTER_MATCH_SOME:
n_items = gtk_bitset_get_size (self->matches);
if (position >= n_items)
{
*out_start = n_items;
*out_end = G_MAXUINT;
return;
}
if (!GTK_IS_SECTION_MODEL (self->model))
{
*out_start = 0;
*out_end = n_items;
return;
}
break;
default:
g_assert_not_reached ();
}
/* if we get here, we have a section model, and are MATCH_SOME */
pos = gtk_bitset_get_nth (self->matches, position);
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), pos, &start, &end);
if (start == 0)
*out_start = 0;
else
*out_start = gtk_bitset_get_size_in_range (self->matches, 0, start - 1);
*out_end = *out_start + gtk_bitset_get_size_in_range (self->matches, start, end - 1);
}
static void
gtk_filter_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_filter_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_filter_list_model_section_model_init))
static gboolean
gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
@ -164,7 +224,7 @@ gtk_filter_list_model_run_filter (GtkFilterListModel *self,
gboolean more;
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
if (self->pending == NULL)
return;
@ -355,7 +415,7 @@ gtk_filter_list_model_set_property (GObject *object,
}
}
static void
static void
gtk_filter_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
@ -503,7 +563,7 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
case GTK_FILTER_MATCH_SOME:
{
GtkBitset *old, *pending;
if (self->matches == NULL)
{
if (self->strictness == GTK_FILTER_MATCH_ALL)

View File

@ -21,8 +21,8 @@
#include "gtkflattenlistmodel.h"
#include "gtksectionmodel.h"
#include "gtkrbtreeprivate.h"
#include "gtkprivate.h"
/**
* GtkFlattenListModel:
@ -200,8 +200,39 @@ gtk_flatten_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_flatten_list_model_get_item;
}
static void
gtk_flatten_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (model);
FlattenNode *node;
guint model_pos;
node = gtk_flatten_list_model_get_nth (self->items, position, &model_pos);
if (node == NULL)
{
*out_start = gtk_flatten_list_model_get_n_items (G_LIST_MODEL (self));
*out_end = G_MAXUINT;
return;
}
*out_start = position - model_pos;
*out_end = position - model_pos + g_list_model_get_n_items (node->model);
}
static void
gtk_flatten_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_flatten_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkFlattenListModel, gtk_flatten_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_flatten_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_flatten_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_flatten_list_model_section_model_init))
static void
gtk_flatten_list_model_items_changed_cb (GListModel *model,
@ -433,7 +464,7 @@ gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class)
properties[PROP_MODEL] =
g_param_spec_object ("model", NULL, NULL,
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlattenListModel:n-items:

View File

@ -264,8 +264,7 @@ gtk_grid_view_is_inert (GtkGridView *self)
GtkWidget *widget = GTK_WIDGET (self);
return !gtk_widget_get_visible (widget) ||
gtk_widget_get_root (widget) == NULL ||
self->factory == NULL;
gtk_widget_get_root (widget) == NULL;
}
static void
@ -886,7 +885,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
{
GtkListTile *filler;
tile = gtk_list_item_manager_get_last (self->item_manager);
filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items);
filler = gtk_list_tile_append_filler (self->item_manager, tile);
gtk_list_tile_set_area_position (self->item_manager,
filler,
column_start (self, xspacing, i),
@ -1330,19 +1329,12 @@ void
gtk_grid_view_set_factory (GtkGridView *self,
GtkListItemFactory *factory)
{
gboolean was_inert;
g_return_if_fail (GTK_IS_GRID_VIEW (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
was_inert = gtk_grid_view_is_inert (self);
if (!g_set_object (&self->factory, factory))
return;
if (!was_inert || !gtk_grid_view_is_inert (self))
gtk_grid_view_update_factories (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}

View File

@ -1959,11 +1959,25 @@ gtk_list_base_split_func (GtkWidget *widget,
}
static GtkListItemBase *
gtk_list_base_create_widget_func (GtkWidget *widget)
gtk_list_base_create_list_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_list_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_prepare_section_func (GtkWidget *widget,
GtkListTile *tile,
guint pos)
{
GTK_LIST_BASE_GET_CLASS (widget)->prepare_section (GTK_LIST_BASE (widget), tile, pos);
}
static GtkListHeaderBase *
gtk_list_base_create_header_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_header_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_init_real (GtkListBase *self,
GtkListBaseClass *g_class)
@ -1973,7 +1987,9 @@ gtk_list_base_init_real (GtkListBase *self,
priv->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self),
gtk_list_base_split_func,
gtk_list_base_create_widget_func);
gtk_list_base_create_list_widget_func,
gtk_list_base_prepare_section_func,
gtk_list_base_create_header_widget_func);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
priv->anchor_side_along = GTK_PACK_START;
priv->anchor_side_across = GTK_PACK_START;

View File

@ -37,6 +37,10 @@ struct _GtkListBaseClass
GtkListTile *tile,
guint n_items);
GtkListItemBase * (* create_list_widget) (GtkListBase *self);
void (* prepare_section) (GtkListBase *self,
GtkListTile *tile,
guint position);
GtkListHeaderBase * (* create_header_widget) (GtkListBase *self);
gboolean (* get_allocation) (GtkListBase *self,
guint pos,

381
gtk/gtklistheader.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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;
}

50
gtk/gtklistheader.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
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

112
gtk/gtklistheaderbase.c Normal file
View File

@ -0,0 +1,112 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderbaseprivate.h"
typedef struct _GtkListHeaderBasePrivate GtkListHeaderBasePrivate;
struct _GtkListHeaderBasePrivate
{
GObject *item;
guint start;
guint end;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderBase, gtk_list_header_base, GTK_TYPE_WIDGET)
static void
gtk_list_header_base_default_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_set_object (&priv->item, item);
priv->start = start;
priv->end = end;
}
static void
gtk_list_header_base_dispose (GObject *object)
{
GtkListHeaderBase *self = GTK_LIST_HEADER_BASE (object);
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_clear_object (&priv->item);
G_OBJECT_CLASS (gtk_list_header_base_parent_class)->dispose (object);
}
static void
gtk_list_header_base_class_init (GtkListHeaderBaseClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->update = gtk_list_header_base_default_update;
gobject_class->dispose = gtk_list_header_base_dispose;
}
static void
gtk_list_header_base_init (GtkListHeaderBase *self)
{
}
void
gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
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);
}
guint
gtk_list_header_base_get_start (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->start;
}
guint
gtk_list_header_base_get_end (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->end;
}
gpointer
gtk_list_header_base_get_item (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->item;
}

View File

@ -0,0 +1,63 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtkwidget.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER_BASE (gtk_list_header_base_get_type ())
#define GTK_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBase))
#define GTK_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
#define GTK_IS_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_IS_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_LIST_HEADER_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
typedef struct _GtkListHeaderBase GtkListHeaderBase;
typedef struct _GtkListHeaderBaseClass GtkListHeaderBaseClass;
struct _GtkListHeaderBase
{
GtkWidget parent_instance;
};
struct _GtkListHeaderBaseClass
{
GtkWidgetClass parent_class;
void (* update) (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
};
GType gtk_list_header_base_get_type (void) G_GNUC_CONST;
void gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
guint gtk_list_header_base_get_start (GtkListHeaderBase *self);
guint gtk_list_header_base_get_end (GtkListHeaderBase *self);
gpointer gtk_list_header_base_get_item (GtkListHeaderBase *self);
G_END_DECLS

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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

295
gtk/gtklistheaderwidget.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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));
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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

View File

@ -81,6 +81,11 @@ gtk_list_item_base_update (GtkListItemBase *self,
GtkListItemBasePrivate *priv = gtk_list_item_base_get_instance_private (self);
gboolean was_selected;
if (priv->position == position &&
priv->item == item &&
priv->selected == selected)
return;
was_selected = priv->selected;
GTK_LIST_ITEM_BASE_GET_CLASS (self)->update (self, position, item, selected);

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@
#include "gtk/gtkenums.h"
#include "gtk/gtklistitembaseprivate.h"
#include "gtk/gtklistheaderbaseprivate.h"
#include "gtk/gtklistitemfactory.h"
#include "gtk/gtkrbtreeprivate.h"
#include "gtk/gtkselectionmodel.h"
@ -43,8 +44,20 @@ typedef struct _GtkListTile GtkListTile;
typedef struct _GtkListTileAugment GtkListTileAugment;
typedef struct _GtkListItemTracker GtkListItemTracker;
typedef enum
{
GTK_LIST_TILE_ITEM,
GTK_LIST_TILE_HEADER,
GTK_LIST_TILE_FOOTER,
GTK_LIST_TILE_UNMATCHED_HEADER,
GTK_LIST_TILE_UNMATCHED_FOOTER,
GTK_LIST_TILE_FILLER,
GTK_LIST_TILE_REMOVED,
} GtkListTileType;
struct _GtkListTile
{
GtkListTileType type;
GtkWidget *widget;
guint n_items;
/* area occupied by tile. May be empty if tile has no allcoation */
@ -54,6 +67,10 @@ struct _GtkListTile
struct _GtkListTileAugment
{
guint n_items;
guint has_header :1;
guint has_footer :1;
/* union of all areas of tile and children */
cairo_rectangle_int_t area;
};
@ -63,7 +80,9 @@ GType gtk_list_item_manager_get_type (void) G_GNUC_CO
GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget,
GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint),
GtkListItemBase * (* create_widget) (GtkWidget *));
GtkListItemBase * (* create_widget) (GtkWidget *),
void (* prepare_section) (GtkWidget *, GtkListTile *, guint),
GtkListHeaderBase * (* create_header_widget) (GtkWidget *));
void gtk_list_item_manager_get_tile_bounds (GtkListItemManager *self,
GdkRectangle *out_bounds);
@ -97,12 +116,17 @@ void gtk_list_tile_set_area_size (GtkListItemMana
GtkListTile * gtk_list_tile_split (GtkListItemManager *self,
GtkListTile *tile,
guint n_items);
GtkListTile * gtk_list_tile_append_filler (GtkListItemManager *self,
GtkListTile *previous);
GtkListTile * gtk_list_tile_gc (GtkListItemManager *self,
GtkListTile *tile);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
void gtk_list_item_manager_set_has_sections (GtkListItemManager *self,
gboolean has_sections);
gboolean gtk_list_item_manager_get_has_sections (GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
void gtk_list_item_tracker_free (GtkListItemManager *self,

View File

@ -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)
@ -222,13 +208,13 @@ gtk_list_view_is_inert (GtkListView *self)
GtkWidget *widget = GTK_WIDGET (self);
return !gtk_widget_get_visible (widget) ||
gtk_widget_get_root (widget) == NULL ||
self->factory == NULL;
gtk_widget_get_root (widget) == NULL;
}
static void
gtk_list_view_update_factories_with (GtkListView *self,
GtkListItemFactory *factory)
GtkListItemFactory *factory,
GtkListItemFactory *header_factory)
{
GtkListTile *tile;
@ -236,8 +222,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 +250,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 +281,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 +547,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 +645,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 +747,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 +791,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 +844,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 +883,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)
*
@ -1054,22 +1100,80 @@ void
gtk_list_view_set_factory (GtkListView *self,
GtkListItemFactory *factory)
{
gboolean was_inert;
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
was_inert = gtk_list_view_is_inert (self);
if (!g_set_object (&self->factory, factory))
return;
if (!was_inert || !gtk_list_view_is_inert (self))
gtk_list_view_update_factories (self);
gtk_list_view_update_factories (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 (!gtk_list_view_is_inert (self) &&
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 +1243,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);
}

View File

@ -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);

View File

@ -30,6 +30,7 @@ struct _GtkListView
GtkListItemManager *item_manager;
GtkListItemFactory *factory;
GtkListItemFactory *header_factory;
gboolean show_separators;
gboolean single_click_activate;
};

View File

@ -22,6 +22,7 @@
#include "gtkmultiselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@ -94,6 +95,23 @@ gtk_multi_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_multi_selection_get_item;
}
static void
gtk_multi_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_multi_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_multi_selection_get_section;
}
static gboolean
gtk_multi_selection_is_selected (GtkSelectionModel *model,
guint position)
@ -205,6 +223,8 @@ gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_multi_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_multi_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_multi_selection_selection_model_init))

View File

@ -22,6 +22,7 @@
#include "gtknoselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@ -92,6 +93,23 @@ gtk_no_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_no_selection_get_item;
}
static void
gtk_no_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkNoSelection *self = GTK_NO_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_no_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_no_selection_get_section;
}
static gboolean
gtk_no_selection_is_selected (GtkSelectionModel *model,
guint position)
@ -117,6 +135,8 @@ gtk_no_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkNoSelection, gtk_no_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_no_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_no_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_no_selection_selection_model_init))

225
gtk/gtksectionmodel.c Normal file
View File

@ -0,0 +1,225 @@
/*
* Copyright © 2022 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksectionmodelprivate.h"
#include "gtkmarshalers.h"
/**
* GtkSectionModel:
*
* `GtkSectionModel` is an interface that adds support for section to list models.
*
* This support is then used by widgets using list models to be able to group their
* items into sections.
*
* Many GTK list models support sections inherently, or they pass through the sections
* of a model they are wrapping.
*
* A `GtkSectionModel` groups successive items into so-called sections. List widgets
* like `GtkListView` then allow displaying section headers for these sections.
*
* When the section groupings of a model changes, the model will emit the
* [signal@Gtk.SectionModel::sections-changed] signal by calling the
* [method@Gtk.SectionModel.sections_changed] function. All sections in the given range
* now need to be queried again.
* The [signal@Gio.ListModel::items-changed] signal has the same effect, all sections in
* that range are invalidated, too.
*
* Since: 4.12
*/
G_DEFINE_INTERFACE (GtkSectionModel, gtk_section_model, G_TYPE_LIST_MODEL)
enum {
SECTIONS_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
gtk_section_model_default_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end)
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (position >= n_items)
{
*out_start = n_items;
*out_end = G_MAXUINT;
}
*out_start = 0;
*out_end = n_items;
}
static void
gtk_section_model_default_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_section_model_default_get_section;
/**
* GtkSectionModel::sections-changed
* @model: a `GtkSectionModel`
* @position: The first item that may have changed
* @n_items: number of items with changes
*
* Emitted when the start-of-section state of some of the items in @model changes.
*
* Note that this signal does not specify the new section state of the
* items, they need to be queried manually. It is also not necessary for
* a model to change the section state of any of the items in the section
* model, though it would be rather useless to emit such a signal.
*
* The [signal@Gio.ListModel::items-changed] implies the effect of the
* [signal@Gtk.SectionModel::sections-changed] signal for all the items
* it covers.
*
* Since: 4.12
*/
signals[SECTIONS_CHANGED] =
g_signal_new ("sections-changed",
GTK_TYPE_SECTION_MODEL,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
_gtk_marshal_VOID__UINT_UINT,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
g_signal_set_va_marshaller (signals[SECTIONS_CHANGED],
GTK_TYPE_SECTION_MODEL,
_gtk_marshal_VOID__UINT_UINTv);
}
/**
* gtk_section_model_get_section:
* @self: a `GtkSectionModel`
* @position: the position of the item to query
* @out_start: (out caller-allocates): the position of the first
* item in the section
* @out_end: (out caller-allocates): the position of the first
* item not part of the section anymore.
*
* Query the section that covers the given position. The number of
* items in the section can be computed by `out_end - out_start`.
*
* If the position is larger than the number of items, a single
* range from n_items to G_MAXUINT will be returned.
*
* Since: 4.12
*/
void
gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end)
{
GtkSectionModelInterface *iface;
g_return_if_fail (GTK_IS_SECTION_MODEL (self));
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
iface = GTK_SECTION_MODEL_GET_IFACE (self);
iface->get_section (self, position, out_start, out_end);
g_warn_if_fail (*out_start < *out_end);
}
/* A version of gtk_section_model_get_section() that handles NULL
* (treats it as the empty list) and any GListModel (treats it as
* a single section).
**/
void
gtk_list_model_get_section (GListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
if (self == NULL)
{
*out_start = 0;
*out_end = G_MAXUINT;
return;
}
g_return_if_fail (G_IS_LIST_MODEL (self));
if (!GTK_IS_SECTION_MODEL (self))
{
guint n_items = g_list_model_get_n_items (self);
if (position < n_items)
{
*out_start = 0;
*out_end = G_MAXUINT;
}
else
{
*out_start = n_items;
*out_end = G_MAXUINT;
}
return;
}
gtk_section_model_get_section (GTK_SECTION_MODEL (self), position, out_start, out_end);
}
/**
* gtk_section_model_section_changed:
* @self: a `GtkSectionModel`
* @position: the first changed item
* @n_items: the number of changed items
*
* This function emits the [signal@Gtk.SectionModel::section-changed]
* signal to notify about changes to sections. It must cover all
* positions that used to be a section start or that are now a section
* start. It does not have to cover all positions for which the section
* has changed.
*
* The [signal@Gio.ListModel::items-changed] implies the effect of the
* [signal@Gtk.SectionModel::section-changed] signal for all the items
* it covers.
*
* It is recommended that when changes to the items cause section changes
* in a larger range, that the larger range is included in the emission
* of the [signal@Gio.ListModel::items-changed] instead of emitting
* two signals.
*
* Since: 4.12
*/
void
gtk_section_model_sections_changed (GtkSectionModel *self,
guint position,
guint n_items)
{
g_return_if_fail (GTK_IS_SECTION_MODEL (self));
g_return_if_fail (n_items > 0);
g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (self)));
g_signal_emit (self, signals[SECTIONS_CHANGED], 0, position, n_items);
}

72
gtk/gtksectionmodel.h Normal file
View File

@ -0,0 +1,72 @@
/*
* Copyright © 2022 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_SECTION_MODEL (gtk_section_model_get_type ())
GDK_AVAILABLE_IN_4_12
G_DECLARE_INTERFACE (GtkSectionModel, gtk_section_model, GTK, SECTION_MODEL, GListModel)
/**
* GtkSectionModelInterface:
* @get_section: Return the section that covers the given position. If
* the position is outside the number of items, returns a single range from
* n_items to G_MAXUINT
*
* The list of virtual functions for the `GtkSectionModel` interface.
* No function must be implemented, but unless `GtkSectionModel::get_section()`
* is implemented, the whole model will just be a single section.
*
* Since: 4.12
*/
struct _GtkSectionModelInterface
{
/*< private >*/
GTypeInterface g_iface;
/*< public >*/
void (* get_section) (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
};
GDK_AVAILABLE_IN_4_12
void gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
/* for implementations only */
GDK_AVAILABLE_IN_4_12
void gtk_section_model_sections_changed (GtkSectionModel *self,
guint position,
guint n_items);
G_END_DECLS

View File

@ -0,0 +1,14 @@
#pragma once
#include "gtksectionmodel.h"
G_BEGIN_DECLS
void gtk_list_model_get_section (GListModel *self,
guint position,
guint *out_start,
guint *out_end);
G_END_DECLS

View File

@ -22,6 +22,7 @@
#include "gtksingleselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@ -103,6 +104,23 @@ gtk_single_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_single_selection_get_item;
}
static void
gtk_single_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_single_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_single_selection_get_section;
}
static gboolean
gtk_single_selection_is_selected (GtkSelectionModel *model,
guint position)
@ -167,6 +185,8 @@ gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_single_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_single_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_single_selection_selection_model_init))

View File

@ -22,7 +22,9 @@
#include "gtksortlistmodel.h"
#include "gtkbitset.h"
#include "gtkmultisorter.h"
#include "gtkprivate.h"
#include "gtksectionmodel.h"
#include "gtksorterprivate.h"
#include "timsort/gtktimsortprivate.h"
@ -73,6 +75,13 @@
* If you run into performance issues with `GtkSortListModel`,
* it is strongly recommended that you write your own sorting list
* model.
*
* `GtkSortListModel` allows sorting the items into sections. It
* implements `GtkSectionModel` and when [property@Gtk.SortListModel:section-sorter]
* is set, it will sort all items with that sorter and items comparing
* equal with it will be put into the same section.
* The [property@Gtk.SortListModel:sorter] will then be used to sort items
* inside their sections.
*/
enum {
@ -82,6 +91,7 @@ enum {
PROP_MODEL,
PROP_N_ITEMS,
PROP_PENDING,
PROP_SECTION_SORTER,
PROP_SORTER,
NUM_PROPERTIES
};
@ -92,6 +102,8 @@ struct _GtkSortListModel
GListModel *model;
GtkSorter *sorter;
GtkSorter *section_sorter;
GtkSorter *real_sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
@ -99,6 +111,7 @@ struct _GtkSortListModel
guint n_items;
GtkSortKeys *sort_keys;
GtkSortKeys *section_sort_keys; /* we assume they are compatible with the sort keys because they're the first element */
gsize key_size;
gpointer keys;
GtkBitset *missing_keys;
@ -174,8 +187,159 @@ gtk_sort_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_sort_list_model_get_item;
}
static void
gtk_sort_list_model_ensure_key (GtkSortListModel *self,
guint pos)
{
gpointer item;
if (!gtk_bitset_contains (self->missing_keys, pos))
return;
item = g_list_model_get_item (self->model, pos);
gtk_sort_keys_init_key (self->sort_keys, item, key_from_pos (self, pos));
g_object_unref (item);
gtk_bitset_remove (self->missing_keys, pos);
}
static void
gtk_sort_list_model_get_section_unsorted (GtkSortListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
gpointer *pos, *start, *end;
pos = &self->positions[position];
gtk_sort_list_model_ensure_key (self, pos_from_key (self, *pos));
for (start = pos;
start > self->positions;
start--)
{
gtk_sort_list_model_ensure_key (self, pos_from_key (self, start[-1]));
if (gtk_sort_keys_compare (self->section_sort_keys, start[-1], *pos) != GTK_ORDERING_EQUAL)
break;
}
for (end = pos + 1;
end < &self->positions[self->n_items];
end++)
{
gtk_sort_list_model_ensure_key (self, pos_from_key (self, *end));
if (gtk_sort_keys_compare (self->section_sort_keys, *end, *pos) != GTK_ORDERING_EQUAL)
break;
}
*out_start = start - self->positions;
*out_end = end - self->positions;
}
static void
gtk_sort_list_model_get_section_sorted (GtkSortListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
gpointer *pos;
guint step, min, max, mid;
pos = &self->positions[position];
max = position;
step = 1;
while (max > 0)
{
min = max - MIN (max, step);
step *= 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[min], *pos) == GTK_ORDERING_EQUAL)
{
max = min;
continue;
}
/* now min is different, max is equal, bsearch where that changes */
while (max - min > 1)
{
mid = (max + min) / 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
max = mid;
else
min = mid;
}
break;
}
*out_start = max;
min = position;
step = 1;
while (min < self->n_items - 1)
{
max = min + MIN (self->n_items - 1 - min, step);
step *= 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[max], *pos) == GTK_ORDERING_EQUAL)
{
min = max;
continue;
}
/* now min is equal, max is different, bsearch where that changes */
while (max - min > 1)
{
mid = (max + min) / 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
min = mid;
else
max = mid;
}
break;
}
*out_end = min + 1;
}
static void
gtk_sort_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkSortListModel *self = GTK_SORT_LIST_MODEL (model);
if (position >= self->n_items)
{
*out_start = self->n_items;
*out_end = G_MAXUINT;
return;
}
if (self->section_sort_keys == NULL)
{
*out_start = 0;
*out_end = self->n_items;
return;
}
/* When the list is not sorted:
* - keys may not exist yet
* - equal items may not be adjacent
* So add a slow path that can deal with that, but is O(N).
* The fast path is O(log N) and will be used for I guess
* 99% of cases.
*/
if (self->sort_cb)
gtk_sort_list_model_get_section_unsorted (self, position, out_start, out_end);
else
gtk_sort_list_model_get_section_sorted (self, position, out_start, out_end);
}
static void
gtk_sort_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_sort_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkSortListModel, gtk_sort_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_sort_list_model_section_model_init))
static gboolean
gtk_sort_list_model_is_sorting (GtkSortListModel *self)
@ -379,6 +543,7 @@ gtk_sort_list_model_clear_keys (GtkSortListModel *self)
g_clear_pointer (&self->missing_keys, gtk_bitset_unref);
g_clear_pointer (&self->keys, g_free);
g_clear_pointer (&self->sort_keys, gtk_sort_keys_unref);
g_clear_pointer (&self->section_sort_keys, gtk_sort_keys_unref);
self->key_size = 0;
}
@ -426,9 +591,9 @@ gtk_sort_list_model_clear_items (GtkSortListModel *self,
static gboolean
gtk_sort_list_model_should_sort (GtkSortListModel *self)
{
return self->sorter != NULL &&
return self->real_sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
gtk_sorter_get_order (self->real_sorter) != GTK_SORTER_ORDER_NONE;
}
static void
@ -436,9 +601,12 @@ gtk_sort_list_model_create_keys (GtkSortListModel *self)
{
g_assert (self->keys == NULL);
g_assert (self->sort_keys == NULL);
g_assert (self->section_sort_keys == NULL);
g_assert (self->key_size == 0);
self->sort_keys = gtk_sorter_get_keys (self->sorter);
self->sort_keys = gtk_sorter_get_keys (self->real_sorter);
if (self->section_sorter)
self->section_sort_keys = gtk_sorter_get_keys (self->section_sorter);
self->key_size = gtk_sort_keys_get_key_size (self->sort_keys);
self->keys = g_malloc_n (self->n_items, self->key_size);
self->missing_keys = gtk_bitset_new_range (0, self->n_items);
@ -646,6 +814,10 @@ gtk_sort_list_model_set_property (GObject *object,
gtk_sort_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SECTION_SORTER:
gtk_sort_list_model_set_section_sorter (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sort_list_model_set_sorter (self, g_value_get_object (value));
break;
@ -686,6 +858,10 @@ gtk_sort_list_model_get_property (GObject *object,
g_value_set_uint (value, gtk_sort_list_model_get_pending (self));
break;
case PROP_SECTION_SORTER:
g_value_set_object (value, self->section_sorter);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
@ -763,13 +939,42 @@ gtk_sort_list_model_clear_model (GtkSortListModel *self)
}
static void
gtk_sort_list_model_clear_sorter (GtkSortListModel *self)
gtk_sort_list_model_clear_real_sorter (GtkSortListModel *self)
{
if (self->sorter == NULL)
if (self->real_sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
g_signal_handlers_disconnect_by_func (self->real_sorter, gtk_sort_list_model_sorter_changed_cb, self);
g_clear_object (&self->real_sorter);
}
static void
gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self)
{
if (self->sorter)
{
if (self->section_sorter)
{
GtkMultiSorter *multi;
multi = gtk_multi_sorter_new ();
self->real_sorter = GTK_SORTER (multi);
gtk_multi_sorter_append (multi, g_object_ref (self->section_sorter));
gtk_multi_sorter_append (multi, g_object_ref (self->sorter));
}
else
self->real_sorter = g_object_ref (self->sorter);
}
else
{
if (self->section_sorter)
self->real_sorter = g_object_ref (self->section_sorter);
}
if (self->real_sorter)
g_signal_connect (self->real_sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
gtk_sort_list_model_sorter_changed_cb (self->real_sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
static void
@ -778,7 +983,9 @@ gtk_sort_list_model_dispose (GObject *object)
GtkSortListModel *self = GTK_SORT_LIST_MODEL (object);
gtk_sort_list_model_clear_model (self);
gtk_sort_list_model_clear_sorter (self);
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->section_sorter);
g_clear_object (&self->sorter);
G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object);
};
@ -846,6 +1053,18 @@ gtk_sort_list_model_class_init (GtkSortListModelClass *class)
0, G_MAXUINT, 0,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSortListModel:section-sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_section_sorter org.gtk.Property.set=gtk_sort_list_model_set_section_sorter)
*
* The section sorter for this model, if one is set.
*
* Since: 4.12
*/
properties[PROP_SECTION_SORTER] =
g_param_spec_object ("section-sorter", NULL, NULL,
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSortListModel:sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_sorter org.gtk.Property.set=gtk_sort_list_model_set_sorter)
*
@ -972,15 +1191,16 @@ gtk_sort_list_model_set_sorter (GtkSortListModel *self,
g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sort_list_model_clear_sorter (self);
if (self->sorter == sorter)
return;
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->sorter);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
}
self->sorter = g_object_ref (sorter);
gtk_sort_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
gtk_sort_list_model_ensure_real_sorter (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
@ -1001,6 +1221,55 @@ gtk_sort_list_model_get_sorter (GtkSortListModel *self)
return self->sorter;
}
/**
* gtk_sort_list_model_set_section_sorter: (attributes org.gtk.Method.set_property=section-sorter)
* @self: a `GtkSortListModel`
* @sorter: (nullable): the `GtkSorter` to sort @model with
*
* Sets a new section sorter on @self.
*
* Since: 4.12
*/
void
gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
if (self->section_sorter == sorter)
return;
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->section_sorter);
if (sorter)
self->section_sorter = g_object_ref (sorter);
gtk_sort_list_model_ensure_real_sorter (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_SORTER]);
}
/**
* gtk_sort_list_model_get_section_sorter: (attributes org.gtk.Method.get_property=section-sorter)
* @self: a `GtkSortListModel`
*
* Gets the section sorter that is used to sort items of @self into
* sections.
*
* Returns: (nullable) (transfer none): the sorter of #self
*
* Since: 4.12
*/
GtkSorter *
gtk_sort_list_model_get_section_sorter (GtkSortListModel *self)
{
g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL);
return self->section_sorter;
}
/**
* gtk_sort_list_model_set_incremental: (attributes org.gtk.Method.set_property=incremental)
* @self: a `GtkSortListModel`

View File

@ -45,6 +45,12 @@ void gtk_sort_list_model_set_sorter (GtkSortListMode
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sort_list_model_get_sorter (GtkSortListModel *self);
GDK_AVAILABLE_IN_4_12
void gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_4_12
GtkSorter * gtk_sort_list_model_get_section_sorter (GtkSortListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sort_list_model_set_model (GtkSortListModel *self,
GListModel *model);

View File

@ -271,6 +271,9 @@ gtk_public_sources = files([
'gtklinkbutton.c',
'gtklistbox.c',
'gtklistfactorywidget.c',
'gtklistheader.c',
'gtklistheaderbase.c',
'gtklistheaderwidget.c',
'gtklistitem.c',
'gtklistitembase.c',
'gtklistitemfactory.c',
@ -333,6 +336,7 @@ gtk_public_sources = files([
'gtkscrolledwindow.c',
'gtksearchbar.c',
'gtksearchentry.c',
'gtksectionmodel.c',
'gtkselectionfiltermodel.c',
'gtkselectionmodel.c',
'gtkseparator.c',
@ -518,6 +522,7 @@ gtk_public_headers = files([
'gtklinkbutton.h',
'gtklistbase.h',
'gtklistbox.h',
'gtklistheader.h',
'gtklistitem.h',
'gtklistitemfactory.h',
'gtklistview.h',
@ -565,6 +570,7 @@ gtk_public_headers = files([
'gtkscrolledwindow.h',
'gtksearchbar.h',
'gtksearchentry.h',
'gtksectionmodel.h',
'gtkselectionfiltermodel.h',
'gtkselectionmodel.h',
'gtkseparator.h',

View File

@ -3377,7 +3377,7 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
.rich-list { /* rich lists usually containing other widgets than just labels/text */
& > row {
& > row, & > header {
padding: 8px 12px;
min-height: 32px; /* should be tall even when only containing a label */
@ -3385,6 +3385,14 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
border-spacing: 12px;
}
}
& > header {
@extend %osd;
background-color: $osd_bg_color;
border-bottom: 1px solid $borders-color;
border-top: 1px solid $borders-color;
font-weight: bold;
}
}
/********************************************************

View File

@ -43,6 +43,23 @@
} \
}G_STMT_END
#define assert_sections_equal(model1, model2) G_STMT_START{ \
guint _i, _n, _start1, _end1, _start2, _end2; \
g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model1)), ==, g_list_model_get_n_items (G_LIST_MODEL (model2))); \
_n = g_list_model_get_n_items (G_LIST_MODEL (model1)); \
for (_i = 0; _i < _n; _i = _end1) \
{ \
gtk_section_model_get_section (model1, _i, &_start1, &_end1); \
gtk_section_model_get_section (model2, _i, &_start2, &_end2); \
g_assert_cmpint (_start1, <, _end1); \
g_assert_cmpint (_start2, <, _end2); \
g_assert_cmpint (_start1, ==, _start2); \
g_assert_cmpint (_end1, ==, _end2); \
g_assert_cmpint (_i, ==, _start1); \
g_assert_cmpint (_end1, <=, _n); \
} \
}G_STMT_END
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@ -469,6 +486,7 @@ test_model_changes (gconstpointer model_id)
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (flatten1), G_LIST_MODEL (model2));
assert_sections_equal (GTK_SECTION_MODEL (flatten1), GTK_SECTION_MODEL (model2));
}
}

View File

@ -323,7 +323,7 @@ test_change_filter (void)
{
GtkFilterListModel *filter;
GtkFilter *custom;
filter = new_model (10, is_not_near, GUINT_TO_POINTER (5));
assert_model (filter, "1 2 8 9 10");
assert_changes (filter, "");
@ -457,6 +457,94 @@ test_add_remove_item (void)
g_object_unref (filter);
}
static int
sort_func (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
/* compare just the first byte */
return (int)(s1[0]) - (int)(s2[0]);
}
static gboolean
filter_func (gpointer item,
gpointer data)
{
const char *s = gtk_string_object_get_string ((GtkStringObject *)item);
return s[0] == s[1];
}
static void
test_sections (void)
{
GtkStringList *list;
const char *strings[] = {
"aaa",
"aab",
"abc",
"bbb",
"bq1",
"bq2",
"cc",
"cx",
NULL
};
GtkSorter *sorter;
GtkSortListModel *sorted;
GtkSorter *section_sorter;
guint s, e;
GtkFilterListModel *filtered;
GtkFilter *filter;
list = gtk_string_list_new (strings);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
g_object_unref (section_sorter);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 6);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
g_assert_cmpint (s, ==, 6);
g_assert_cmpint (e, ==, 8);
filtered = gtk_filter_list_model_new (NULL, NULL);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, G_MAXUINT);
gtk_filter_list_model_set_model (filtered, G_LIST_MODEL (sorted));
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
filter = GTK_FILTER (gtk_custom_filter_new (filter_func, NULL, NULL));
gtk_filter_list_model_set_filter (filtered, filter);
g_object_unref (filter);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 2);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 2, &s, &e);
g_assert_cmpint (s, ==, 2);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 4);
g_object_unref (filtered);
g_object_unref (sorted);
}
int
main (int argc, char *argv[])
{
@ -472,6 +560,7 @@ main (int argc, char *argv[])
g_test_add_func ("/filterlistmodel/incremental", test_incremental);
g_test_add_func ("/filterlistmodel/empty", test_empty);
g_test_add_func ("/filterlistmodel/add_remove_item", test_add_remove_item);
g_test_add_func ("/filterlistmodel/sections", test_sections);
return g_test_run ();
}

View File

@ -0,0 +1,497 @@
/*
* 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 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 <http://www.gnu.org/licenses/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
#include "gtk/gtklistitemmanagerprivate.h"
#include "gtk/gtklistbaseprivate.h"
static GListModel *
create_source_model (guint min_size, guint max_size)
{
GtkStringList *list;
guint i, size;
size = g_test_rand_int_range (min_size, max_size + 1);
list = gtk_string_list_new (NULL);
for (i = 0; i < size; i++)
gtk_string_list_append (list, g_test_rand_bit () ? "A" : "B");
return G_LIST_MODEL (list);
}
void
print_list_item_manager_tiles (GtkListItemManager *items)
{
GString *string;
GtkListTile *tile;
string = g_string_new ("");
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_ITEM:
if (tile->widget)
g_string_append_c (string, 'W');
else if (tile->n_items == 1)
g_string_append_c (string, 'x');
else
g_string_append_printf (string, "%u,", tile->n_items);
break;
case GTK_LIST_TILE_HEADER:
g_string_append_c (string, '[');
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_string_append_c (string, '(');
break;
case GTK_LIST_TILE_FOOTER:
g_string_append_c (string, ']');
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_string_append_c (string, ')');
break;
case GTK_LIST_TILE_FILLER:
g_string_append_c (string, '_');
break;
case GTK_LIST_TILE_REMOVED:
g_string_append_c (string, '.');
break;
default:
g_assert_not_reached ();
break;
}
}
g_print ("%s\n", string->str);
g_string_free (string, TRUE);
}
static void
check_list_item_manager (GtkListItemManager *items,
GtkListItemTracker **trackers,
gsize n_trackers)
{
GListModel *model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
GtkListTile *tile;
guint n_items = 0;
guint i;
gboolean has_sections;
enum {
NO_SECTION,
MATCHED_SECTION,
UNMATCHED_SECTION
} section_state = NO_SECTION;
has_sections = gtk_list_item_manager_get_has_sections (items);
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
GObject *item = g_list_model_get_item (model, n_items);
if (has_sections)
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
else
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmphex (GPOINTER_TO_SIZE (item), ==, GPOINTER_TO_SIZE (gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget))));
g_object_unref (item);
g_assert_cmpint (n_items, ==, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (tile->widget)));
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
/* We don't add fillers */
g_assert_not_reached ();
break;
case GTK_LIST_TILE_REMOVED:
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
break;
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
for (tile = gtk_list_tile_gc (items, gtk_list_item_manager_get_first (items));
tile != NULL;
tile = gtk_list_tile_gc (items, gtk_rb_tree_node_get_next (tile)))
;
n_items = 0;
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
case GTK_LIST_TILE_REMOVED:
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
}
static GtkListTile *
split_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
GtkListItemManager *items = g_object_get_data (G_OBJECT (widget), "the-items");
return gtk_list_tile_split (items, tile, n_items);
}
static void
prepare_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
}
static GtkListItemBase *
create_simple_item (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_ITEM_BASE, NULL);
}
static GtkListHeaderBase *
create_simple_header (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_HEADER_BASE, NULL);
}
static void
test_create (void)
{
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
gtk_window_destroy (GTK_WINDOW (widget));
}
static void
test_create_with_items (void)
{
GListModel *source;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
source = create_source_model (1, 50);
selection = gtk_no_selection_new (G_LIST_MODEL (source));
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
#define N_TRACKERS 3
#define N_WIDGETS_PER_TRACKER 10
#define N_RUNS 500
static void
print_changes_cb (GListModel *model,
guint position,
guint removed,
guint added,
gpointer unused)
{
if (!g_test_verbose ())
return;
if (removed == 0)
g_test_message ("%u/%u: adding %u items", position, g_list_model_get_n_items (model), added);
else if (added == 0)
g_test_message ("%u/%u: removing %u items", position, g_list_model_get_n_items (model), removed);
else
g_test_message ("%u/%u: removing %u and adding %u items", position, g_list_model_get_n_items (model), removed, added);
}
static void
test_exhaustive (void)
{
GtkListItemTracker *trackers[N_TRACKERS];
GListStore *store;
GtkFlattenListModel *flatten;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
gsize i;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
for (i = 0; i < N_TRACKERS; i++)
trackers[i] = gtk_list_item_tracker_new (items);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
selection = gtk_no_selection_new (G_LIST_MODEL (flatten));
g_signal_connect (selection, "items-changed", G_CALLBACK (print_changes_cb), NULL);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
for (i = 0; i < N_RUNS; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position, n_items;
if (g_test_verbose ())
print_list_item_manager_tiles (items);
switch (g_test_rand_int_range (0, 6))
{
case 0:
if (g_test_verbose ())
g_test_message ("GC and checking");
check_list_item_manager (items, trackers, N_TRACKERS);
break;
case 1:
/* remove a model */
remove = TRUE;
break;
case 2:
/* add a model */
add = TRUE;
break;
case 3:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
case 4:
n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
if (n_items > 0)
{
guint tracker_id = g_test_rand_int_range (0, N_TRACKERS);
guint pos = g_test_rand_int_range (0, n_items);
guint n_before = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
guint n_after = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
if (g_test_verbose ())
g_test_message ("setting tracker %u to %u -%u + %u", tracker_id, pos, n_before, n_after);
gtk_list_item_tracker_set_position (items,
trackers [tracker_id],
pos,
n_before, n_after);
}
break;
case 5:
{
gboolean has_sections = g_test_rand_bit ();
if (g_test_verbose ())
g_test_message ("Setting has_sections to %s", has_sections ? "true" : "false");
gtk_list_item_manager_set_has_sections (items, has_sections);
}
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the filters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
}
check_list_item_manager (items, trackers, N_TRACKERS);
for (i = 0; i < N_TRACKERS; i++)
gtk_list_item_tracker_free (items, trackers[i]);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
int
main (int argc, char *argv[])
{
gtk_test_init (&argc, &argv);
g_test_add_func ("/listitemmanager/create", test_create);
g_test_add_func ("/listitemmanager/create_with_items", test_create_with_items);
g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive);
return g_test_run ();
}

View File

@ -126,6 +126,7 @@ internal_tests = [
{ 'name': 'texthistory' },
{ 'name': 'fnmatch' },
{ 'name': 'a11y' },
{ 'name': 'listitemmanager' },
]
is_debug = get_option('buildtype').startswith('debug')

View File

@ -36,6 +36,8 @@
if (o1 != o2) \
{ \
char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
g_print ("%s\n", model_to_string (model1)); \
g_print ("%s\n", model_to_string (model2)); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
g_free (_s); \
} \
@ -45,6 +47,19 @@
} \
}G_STMT_END
#define assert_model_sections(model) G_STMT_START{ \
guint _i, _start, _end; \
_start = 0; \
_end = 0; \
for (_i = 0; _i < G_MAXUINT; _i = _end) \
{ \
gtk_section_model_get_section (GTK_SECTION_MODEL (model), _i, &_start, &_end); \
\
g_assert_cmpint (_start, ==, _i); \
g_assert_cmpint (_end, >, _i); \
} \
}G_STMT_END
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@ -287,7 +302,7 @@ create_sorter (gsize id)
/* match all As, Bs and nothing */
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
if (id == 1)
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
return sorter;
default:
@ -463,6 +478,258 @@ test_stability (gconstpointer model_id)
g_object_unref (flatten);
}
static gboolean
string_is_lowercase (GtkStringObject *o)
{
return g_ascii_islower (*gtk_string_object_get_string (o));
}
/* Run:
* source => section-sorter
* source => sorter
* and set a section sorter on the section sorter that is a subsort of
* the real sorter.
*
* And then randomly add/remove sources and change the sorters and
* see if the two sorters stay identical
*/
static void
test_section_sorters (gconstpointer model_id)
{
GListStore *store;
GtkFlattenListModel *flatten;
GtkSortListModel *sort1, *sort2;
GtkSorter *sorter;
gsize i;
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
sort1 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
sort2 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
for (i = 0; i < 500; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position;
switch (g_test_rand_int_range (0, 4))
{
case 0:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_section_sorter (sort1, sorter);
gtk_sort_list_model_set_sorter (sort1, NULL);
gtk_sort_list_model_set_sorter (sort2, sorter);
g_clear_object (&sorter);
break;
case 1:
/* use a section sorter that is a more generic version of the sorter */
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
gtk_sort_list_model_set_sorter (sort1, sorter);
gtk_sort_list_model_set_sorter (sort2, sorter);
g_clear_object (&sorter);
sorter = GTK_SORTER (gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_BOOLEAN, NULL, 0, NULL, G_CALLBACK (string_is_lowercase), NULL, NULL)));
gtk_sort_list_model_set_section_sorter (sort1, sorter);
g_clear_object (&sorter);
break;
case 2:
/* remove a model */
remove = TRUE;
break;
case 3:
/* add a model */
add = TRUE;
break;
case 4:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the sorters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
if (g_test_rand_bit ())
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
}
if (g_test_rand_bit ())
assert_model_sections (G_LIST_MODEL (sort1));
}
g_object_unref (sort2);
g_object_unref (sort1);
g_object_unref (flatten);
}
/* Run:
* source => sorter
* And then randomly add/remove sources and change the sorters and
* see if the invariants for sections keep correct.
*/
static void
test_sections (gconstpointer model_id)
{
GListStore *store;
GtkFlattenListModel *flatten;
GtkSortListModel *sort;
GtkSorter *sorter;
gsize i;
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
sort = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
for (i = 0; i < 500; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position;
switch (g_test_rand_int_range (0, 4))
{
case 0:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_sorter (sort, sorter);
g_clear_object (&sorter);
break;
case 1:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_section_sorter (sort, sorter);
g_clear_object (&sorter);
break;
case 2:
/* remove a model */
remove = TRUE;
break;
case 3:
/* add a model */
add = TRUE;
break;
case 4:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the sorters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
if (g_test_rand_bit ())
ensure_updated ();
if (g_test_rand_bit ())
{
guint start, end, pos, n, sec_start, sec_end;
gpointer prev_item, item;
n = g_list_model_get_n_items (G_LIST_MODEL (sort));
sorter = gtk_sort_list_model_get_section_sorter (sort);
start = end = 0;
prev_item = item = NULL;
for (pos = 0; pos < n; pos++)
{
gtk_section_model_get_section (GTK_SECTION_MODEL (sort), pos, &sec_start, &sec_end);
prev_item = item;
item = g_list_model_get_item (G_LIST_MODEL (sort), pos);
if (end <= pos)
{
g_assert_cmpint (pos, ==, end);
/* there should be a new section */
g_assert_cmpint (sec_start, ==, end);
g_assert_cmpint (sec_end, >, sec_start);
g_assert_cmpint (sec_end, <=, n);
start = sec_start;
end = sec_end;
if (prev_item)
{
g_assert_nonnull (sorter);
g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), !=, GTK_ORDERING_EQUAL);
}
}
else
{
/* the old section keeps on going */
g_assert_cmpint (sec_start, ==, start);
g_assert_cmpint (sec_end, ==, end);
if (prev_item && sorter)
g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), ==, GTK_ORDERING_EQUAL);
}
g_clear_object (&prev_item);
}
g_clear_object (&item);
/* for good measure, check the error condition */
if (n < G_MAXINT32)
{
gtk_section_model_get_section (GTK_SECTION_MODEL (sort), g_test_rand_int_range (n, G_MAXINT32), &sec_start, &sec_end);
g_assert_cmpint (sec_start, ==, n);
g_assert_cmpint (sec_end, ==, G_MAXUINT);
}
sorter = NULL;
}
}
g_object_unref (sort);
g_object_unref (flatten);
}
static void
add_test_for_all_models (const char *name,
GTestDataFunc test_func)
@ -488,6 +755,8 @@ main (int argc, char *argv[])
add_test_for_all_models ("two-sorters", test_two_sorters);
add_test_for_all_models ("stability", test_stability);
add_test_for_all_models ("section-sorters", test_section_sorters);
add_test_for_all_models ("sections", test_sections);
return g_test_run ();
}

View File

@ -258,7 +258,7 @@ test_create (void)
{
GtkSortListModel *sort;
GListStore *store;
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@ -280,7 +280,7 @@ test_set_model (void)
{
GtkSortListModel *sort;
GListStore *store;
sort = new_model (NULL);
assert_model (sort, "");
assert_changes (sort, "");
@ -319,7 +319,7 @@ test_set_sorter (void)
GtkSortListModel *sort;
GtkSorter *sorter;
GListStore *store;
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@ -350,7 +350,7 @@ test_add_items (void)
{
GtkSortListModel *sort;
GListStore *store;
/* add beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 50, 0 });
sort = new_model (store);
@ -390,7 +390,7 @@ test_remove_items (void)
{
GtkSortListModel *sort;
GListStore *store;
/* remove beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
sort = new_model (store);
@ -570,6 +570,58 @@ test_add_remove_item (void)
g_object_unref (sort);
}
static int
sort_func (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
/* compare just the first byte */
return (int)(s1[0]) - (int)(s2[0]);
}
static void
test_sections (void)
{
GtkStringList *list;
const char *strings[] = {
"aaa",
"aab",
"abc",
"bbb",
"bq1",
"bq2",
"cc",
"cx",
NULL
};
GtkSorter *sorter;
GtkSortListModel *sorted;
GtkSorter *section_sorter;
guint s, e;
list = gtk_string_list_new (strings);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
g_object_unref (section_sorter);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 6);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
g_assert_cmpint (s, ==, 6);
g_assert_cmpint (e, ==, 8);
g_object_unref (sorted);
}
int
main (int argc, char *argv[])
{
@ -589,6 +641,7 @@ main (int argc, char *argv[])
g_test_add_func ("/sortlistmodel/incremental/remove", test_incremental_remove);
g_test_add_func ("/sortlistmodel/oob-access", test_out_of_bounds_access);
g_test_add_func ("/sortlistmodel/add-remove-item", test_add_remove_item);
g_test_add_func ("/sortlistmodel/sections", test_sections);
return g_test_run ();
}