Compare commits

...

29 Commits

Author SHA1 Message Date
Matthias Clasen
106ce8580c Filter Emoji by font availability
Don't show hexboxes in the emoji grid.
2022-02-28 16:37:29 -07:00
Matthias Clasen
8690ca4a65 gtk-demo: Add a sectioned grid 2022-02-28 16:06:15 -07:00
Matthias Clasen
f7d55dd36f Add an emoji list
Add a list model that contains Emoji, grouped
into sections.
2022-02-28 16:06:15 -07:00
Benjamin Otte
dd5fd873fd gridview: Rework section handling
Instead of having custom SECTION blocks, assign the section area to the
cell(s) below. That way navigating to those cells will ensure the
section header is included, which is particularly useful when scrolling.
2022-02-28 04:24:27 +01:00
Benjamin Otte
afc2be4b14 gtk-demo: Add a sectioned view to the filebrowser demo 2022-02-27 02:40:12 +01:00
Benjamin Otte
2c6ffe622a demo: Add a listview sections demo
Just display the list of objects from GTK, and order them in some
made-up categories.
2022-02-27 02:40:12 +01:00
Benjamin Otte
9426761339 css: Add some CSS for sections
Does two things:

1. Makes sure I find them
2. Makes sure some designer will notice they need restyling
2022-02-27 02:40:12 +01:00
Benjamin Otte
49616f7d00 gridview: Add support for sections 2022-02-27 02:40:12 +01:00
Benjamin Otte
4a8d5d33df listview: Split out a function 2022-02-27 02:40:12 +01:00
Benjamin Otte
228a88f033 listview: Implement ::section-factory property
Add a property to listview, pipe it to the listitemmanager and make sure
the manager creates the sections so that listview can display it.
2022-02-27 02:40:12 +01:00
Benjamin Otte
e5dbadb96e testsuite: Add tests for sections to filterlistmodel 2022-02-27 02:40:12 +01:00
Matthias Clasen
0958f6f8b9 Add a test for filterlistmodel sections 2022-02-27 02:40:12 +01:00
Matthias Clasen
00eebfa215 filterlistmodel: Support sections
Propagate sections from the child model to
the filter model.
2022-02-27 02:40:12 +01:00
Benjamin Otte
e5b4e5cf66 sortlistmodel: add a fast path for get_section() 2022-02-27 02:40:12 +01:00
Benjamin Otte
a507b2fbfd testsuite: Add section tests to sortlistmodel test 2022-02-27 02:40:12 +01:00
Matthias Clasen
83f321aad4 Add a test for sortlistmodel sections 2022-02-27 02:40:12 +01:00
Benjamin Otte
a537dbb060 sortlistmodel: Implement GtkSectionModel
The get_section() implementation is a slow ans steady implementation
that has to be careful to not screw up when an incremental sort is only
partially sorted.
2022-02-26 20:35:44 +01:00
Benjamin Otte
15f27ac44a flattenlistmodel: Implement GtkSectionModel
Each child model is reported as one section.
2022-02-26 20:35:44 +01:00
Benjamin Otte
7e4f25e1a9 Implement GtkSectionModel for all selection models 2022-02-26 20:35:44 +01:00
Benjamin Otte
3ca1b04c11 Add GtkSectionModel
Prototyping the interface to be used for sections in listview, so people
can review and play with it.
2022-02-26 20:35:44 +01:00
Benjamin Otte
b974da285d DEBUG: Add debug prints to list item manager 2022-02-26 20:35:44 +01:00
Benjamin Otte
1d25a8f21a gridview: Dump some files 2022-02-26 20:35:44 +01:00
Benjamin Otte
5925cd68ce demo: Use the new GtkListiew::focus-item property
Show the current date in the weather view.

Shows how awkward it is to use the focus-item when scrolling with the
mouse.
2022-02-26 20:35:44 +01:00
Benjamin Otte
17539d9378 listview: Add ::focus-position and ::focus-item
Hands out the row that gets/would get keyboard focus and allows setting
it.
2022-02-26 20:35:44 +01:00
Benjamin Otte
352298f281 listitemmanager: Allow properties assigned to trackers
In theory, we could also use callback(s) here, but if the callbacks just
notify, it's easier to do it this way.
2022-02-26 20:35:44 +01:00
Benjamin Otte
f414521aab listitemmanager: Unify tracker updates
Have a central function to update a tracker's position and item. This
will be used in the next commit.
2022-02-26 20:35:44 +01:00
Benjamin Otte
0c5329212b listbase: Store the proper side
We want to store the side that is closer to the ideal position, not the
one that is further away.

Oops.
2022-02-26 20:35:44 +01:00
Benjamin Otte
bc9699e6ed listbase: Compare with the right adjustment
We want to keep the adjustment in place that is *not* scrolled, not the
one that is.
2022-02-26 20:35:44 +01:00
Benjamin Otte
0751a1ab67 listitemmanager: Change the way listitems are handled
Instead of immediately removing listitems when they are added/removed or
their widgets become unnecessary, force a manual cleanup. Do this
cleanup in size_allocate().

This way, we can assign space to listitems during allocate and be sure
it survives until the next call to size_allocate(), which allows
tracking multiple changes to the listitems in a single frame, as the
assigned space never collapses.

It also simplifies gridview code, because it allows splitting listitems
so that every listitem represents a rectangular region.

Fixes #2971
2022-02-26 20:35:44 +01:00
34 changed files with 3932 additions and 578 deletions

View File

@@ -303,8 +303,10 @@
<file>listview_applauncher.c</file>
<file>listview_colors.c</file>
<file>listview_clocks.c</file>
<file>listview_emoji.c</file>
<file>listview_filebrowser.c</file>
<file>listview_minesweeper.c</file>
<file>listview_objects.c</file>
<file>listview_settings.c</file>
<file>listview_ucd.c</file>
<file>listview_weather.c</file>

View File

@@ -0,0 +1,216 @@
/* Lists/Emoji
* #Keywords: GtkListItemFactory, GtkGridView
*
* This demo uses the GtkGridView widget to show Emoji.
*
* It shows how to use sections in GtkGridView
*/
#include <gtk/gtk.h>
static char *
get_section (GtkEmojiObject *object)
{
switch (gtk_emoji_object_get_group (object))
{
case GTK_EMOJI_GROUP_RECENT: return g_strdup ("Recent");
case GTK_EMOJI_GROUP_SMILEYS: return g_strdup ("Smileys");
case GTK_EMOJI_GROUP_BODY: return g_strdup ("People");
case GTK_EMOJI_GROUP_COMPONENT: return g_strdup ("Components");
case GTK_EMOJI_GROUP_NATURE: return g_strdup ("Nature");
case GTK_EMOJI_GROUP_FOOD: return g_strdup ("Food");
case GTK_EMOJI_GROUP_PLACES: return g_strdup ("Places");
case GTK_EMOJI_GROUP_ACTIVITIES: return g_strdup ("Activities");
case GTK_EMOJI_GROUP_OBJECTS: return g_strdup ("Objects");
case GTK_EMOJI_GROUP_SYMBOLS: return g_strdup ("Symbols");
case GTK_EMOJI_GROUP_FLAGS: return g_strdup ("Flags");
default: return g_strdup ("Something else");
}
}
static void
setup_section_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_add_css_class (label, "heading");
gtk_widget_set_margin_top (label, 4);
gtk_widget_set_margin_bottom (label, 4);
gtk_list_item_set_child (list_item, label);
}
static void
bind_section_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
GtkEmojiObject *item;
char *text;
label = gtk_list_item_get_child (list_item);
item = gtk_list_item_get_item (list_item);
text = get_section (item);
gtk_label_set_label (GTK_LABEL (label), text);
g_free (text);
}
static void
setup_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
}
static void
bind_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
GtkEmojiObject *item;
char buffer[64];
PangoAttrList *attrs;
label = gtk_list_item_get_child (list_item);
item = gtk_list_item_get_item (list_item);
gtk_emoji_object_get_text (item, buffer, sizeof (buffer), 0);
gtk_label_set_label (GTK_LABEL (label), buffer);
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
gtk_label_set_attributes (GTK_LABEL (label), attrs);
pango_attr_list_unref (attrs);
}
static gboolean
match_tokens (const char **term_tokens,
const char **hit_tokens)
{
int i, j;
gboolean matched;
matched = TRUE;
for (i = 0; term_tokens[i]; i++)
{
for (j = 0; hit_tokens[j]; j++)
if (g_str_has_prefix (hit_tokens[j], term_tokens[i]))
goto one_matched;
matched = FALSE;
break;
one_matched:
continue;
}
return matched;
}
static gboolean
filter_func (gpointer item,
gpointer user_data)
{
GtkEmojiObject *emoji = item;
GtkWidget *entry = user_data;
const char *text;
const char *name;
const char **keywords;
char **term_tokens;
char **name_tokens;
gboolean res;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
if (text[0] == 0)
return TRUE;
name = gtk_emoji_object_get_name (emoji);
keywords = gtk_emoji_object_get_keywords (emoji);
term_tokens = g_str_tokenize_and_fold (text, "en", NULL);
name_tokens = g_str_tokenize_and_fold (name, "en", NULL);
res = match_tokens ((const char **)term_tokens, (const char **)name_tokens) ||
match_tokens ((const char **)term_tokens, keywords);
g_strfreev (term_tokens);
g_strfreev (name_tokens);
return res;
}
static void
search_changed (GtkEntry *entry,
gpointer data)
{
GtkFilter *filter = data;
gtk_filter_changed (filter, GTK_FILTER_CHANGE_DIFFERENT);
}
static GtkWidget *window = NULL;
GtkWidget *
do_listview_emoji (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *list, *sw;
GListModel *model;
GtkListItemFactory *factory;
GtkWidget *box, *entry;
GtkFilter *filter;
/* Create a window and set a few defaults */
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Emoji");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL);
entry = gtk_search_entry_new ();
model = G_LIST_MODEL (gtk_emoji_list_new ());
filter = GTK_FILTER (gtk_custom_filter_new (filter_func, entry, NULL));
model = G_LIST_MODEL (gtk_filter_list_model_new (model, filter));
g_signal_connect (entry, "search-changed", G_CALLBACK (search_changed), filter);
list = gtk_grid_view_new (GTK_SELECTION_MODEL (gtk_no_selection_new (model)), factory);
gtk_grid_view_set_max_columns (GTK_GRID_VIEW (list), 20);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_section_listitem_cb), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_section_listitem_cb), NULL);
gtk_grid_view_set_section_factory (GTK_GRID_VIEW (list), factory);
g_object_unref (factory);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append (GTK_BOX (box), entry);
sw = gtk_scrolled_window_new ();
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
gtk_box_append (GTK_BOX (box), sw);
gtk_window_set_child (GTK_WINDOW (window), box);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -18,6 +18,8 @@ struct _FileBrowserView
GObject parent_instance;
GtkListItemFactory *factory;
GtkListItemFactory *section_factory;
GtkSorter *section_sorter;
char *icon_name;
char *title;
GtkOrientation orientation;
@@ -27,8 +29,10 @@ enum {
PROP_0,
PROP_FACTORY,
PROP_ICON_NAME,
PROP_TITLE,
PROP_ORIENTATION,
PROP_SECTION_FACTORY,
PROP_SECTION_SORTER,
PROP_TITLE,
N_PROPS
};
@@ -58,14 +62,22 @@ file_browser_view_get_property (GObject *object,
g_value_set_string (value, self->icon_name);
break;
case PROP_TITLE:
g_value_set_string (value, self->title);
break;
case PROP_ORIENTATION:
g_value_set_enum (value, self->orientation);
break;
case PROP_SECTION_FACTORY:
g_value_set_object (value, self->section_factory);
break;
case PROP_SECTION_SORTER:
g_value_set_object (value, self->section_sorter);
break;
case PROP_TITLE:
g_value_set_string (value, self->title);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -91,15 +103,23 @@ file_browser_view_set_property (GObject *object,
self->icon_name = g_value_dup_string (value);
break;
case PROP_ORIENTATION:
self->orientation = g_value_get_enum (value);
break;
case PROP_SECTION_FACTORY:
g_set_object (&self->section_factory, g_value_get_object (value));
break;
case PROP_SECTION_SORTER:
g_set_object (&self->section_sorter, g_value_get_object (value));
break;
case PROP_TITLE:
g_free (self->title);
self->title = g_value_dup_string (value);
break;
case PROP_ORIENTATION:
self->orientation = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -139,12 +159,6 @@ file_browser_view_class_init (FileBrowserViewClass *klass)
"icon to display for selecting this view",
NULL,
G_PARAM_READWRITE);
properties[PROP_TITLE] =
g_param_spec_string ("title",
"title",
"title to display for selecting this view",
NULL,
G_PARAM_READWRITE);
properties[PROP_ORIENTATION] =
g_param_spec_enum ("orientation",
"orientation",
@@ -152,6 +166,24 @@ file_browser_view_class_init (FileBrowserViewClass *klass)
GTK_TYPE_ORIENTATION,
GTK_ORIENTATION_VERTICAL,
G_PARAM_READWRITE);
properties[PROP_SECTION_FACTORY] =
g_param_spec_object ("section-factory",
"section factory",
"factory to use for sections or NULL",
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE);
properties[PROP_SECTION_SORTER] =
g_param_spec_object ("section-sorter",
"section sorter",
"sorter to split files into sections or NULL",
GTK_TYPE_SORTER,
G_PARAM_READWRITE);
properties[PROP_TITLE] =
g_param_spec_string ("title",
"title",
"title to display for selecting this view",
NULL,
G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}

View File

@@ -91,6 +91,78 @@
<property name="orientation">vertical</property>
</object>
</child>
<child>
<object class="FileBrowserView">
<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="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="filebrowser_get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="hexpand">true</property>
<property name="width-chars">30</property>
<property name="ellipsize">middle</property>
<binding name="label">
<closure type="gchararray" function="filebrowser_get_display_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<property name="section-factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="filebrowser_get_content_type">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<property name="section-sorter">
<object class="GtkStringSorter">
<property name="expression">
<closure type="gchararray" function="filebrowser_get_content_type" swapped="true" />
</property>
</object>
</property>
<property name="icon-name">view-continuous-symbolic</property>
<property name="title" translatable="yes">Sections</property>
<property name="orientation">vertical</property>
</object>
</child>
<child>
<object class="FileBrowserView">
<property name="icon-name">view-paged-symbolic</property>
@@ -167,8 +239,17 @@
</object>
<object class="GtkSingleSelection" id="selection_model">
<property name="model">
<object class="GtkDirectoryList" id="dirlist">
<property name="attributes">standard::name,standard::display-name,standard::icon,standard::size,standard::content-type</property>
<object class="GtkSortListModel">
<binding name="section-sorter">
<lookup name="section-sorter" type="FileBrowserView">
<lookup name="selected-item">selected-view</lookup>
</lookup>
</binding>
<property name="model">
<object class="GtkDirectoryList" id="dirlist">
<property name="attributes">standard::name,standard::display-name,standard::icon,standard::size,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
@@ -238,6 +319,11 @@
<lookup name="selected-item">selected-view</lookup>
</lookup>
</binding>
<binding name="section-factory">
<lookup name="section-factory" type="FileBrowserView">
<lookup name="selected-item">selected-view</lookup>
</lookup>
</binding>
<binding name="orientation">
<lookup name="orientation" type="FileBrowserView">
<lookup name="selected-item">selected-view</lookup>

View File

@@ -0,0 +1,220 @@
/* Lists/Objects in GTK
* #Keywords: GtkListItemFactory, GtkSortListModel, GtkStringList
*
* This demo uses the GtkListView widget to show all the objects in GTK
* grouped by their type.
*
* It shows how to use sections in GtkListView
*/
#include <gtk/gtk.h>
/* This is the function that creates the GListModel that we need.
*/
static GListModel *
create_object_list (void)
{
GtkStringList *strings;
const GType *types;
guint i, n;
/* We use a GtkStringList here, because it requires the smallest amount of
* code, not because it's a great fit.
*/
strings = gtk_string_list_new (NULL);
/* This function is meant for testing, but we use it here to get some data
* to operate on
*/
gtk_test_register_all_types ();
types = gtk_test_list_all_types (&n);
for (i = 0; i < n; i++)
{
/* Add all the names of the object types in GTK */
if (g_type_is_a (types[i], G_TYPE_OBJECT))
gtk_string_list_append (strings, g_type_name (types[i]));
}
return G_LIST_MODEL (strings);
}
/* Make a function that returns a section name for all our types.
* Do this by adding a few type checks and returning a made up
* section name for it.
*/
static char *
get_section (GtkStringObject *object)
{
GType type;
type = g_type_from_name (gtk_string_object_get_string (object));
if (g_type_is_a (type, GTK_TYPE_WIDGET))
return g_strdup ("Widget");
else if (g_type_is_a (type, GTK_TYPE_FILTER))
return g_strdup ("Filter");
else if (g_type_is_a (type, GTK_TYPE_SORTER))
return g_strdup ("Sorter");
else if (g_type_is_a (type, G_TYPE_LIST_MODEL))
return g_strdup ("ListModel");
else
return g_strdup ("Zzz..."); /* boring stuff, cleverly sorted at the end */
}
/* These functions set up the object names
*/
static void
setup_section_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_add_css_class (label, "heading");
gtk_widget_set_margin_top (label, 4);
gtk_widget_set_margin_bottom (label, 4);
gtk_list_item_set_child (list_item, label);
}
/* Here we need to prepare the listitem for displaying its item. We get the
* listitem already set up from the previous function, so we can reuse the
* GtkImage widget we set up above.
* We get the item - which we know is a GAppInfo because it comes out of
* the model we set up above, grab its icon and display it.
*/
static void
bind_section_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
GtkStringObject *item;
char *text;
label = gtk_list_item_get_child (list_item);
item = gtk_list_item_get_item (list_item);
text = get_section (item);
gtk_label_set_label (GTK_LABEL (label), text);
g_free (text);
}
static void
setup_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_list_item_set_child (list_item, label);
}
/* Here we need to prepare the listitem for displaying its item. We get the
* listitem already set up from the previous function, so we can reuse the
* GtkImage widget we set up above.
* We get the item - which we know is a GAppInfo because it comes out of
* the model we set up above, grab its icon and display it.
*/
static void
bind_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
GtkStringObject *item;
label = gtk_list_item_get_child (list_item);
item = gtk_list_item_get_item (list_item);
gtk_label_set_label (GTK_LABEL (label), gtk_string_object_get_string (item));
}
/* In more complex code, we would also need functions to unbind and teardown
* the listitem, but this is simple code, so the default implementations are
* enough. If we had connected signals, this step would have been necessary.
*
* The GtkSignalListItemFactory documentation contains more information about
* this step.
*/
static GtkWidget *window = NULL;
GtkWidget *
do_listview_objects (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *list, *sw;
GListModel *model;
GtkListItemFactory *factory;
GtkSorter *sorter;
/* Create a window and set a few defaults */
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Objects in GTK");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
/* The GtkListitemFactory is what is used to create GtkListItems
* to display the data from the model. So it is absolutely necessary
* to create one.
* We will use a GtkSignalListItemFactory because it is the simplest
* one to use. Different ones are available for different use cases.
* The most powerful one is GtkBuilderListItemFactory which uses
* GtkBuilder .ui files, so it requires little code.
*/
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL);
/* And of course we need to set the data model. Here we call the function
* we wrote above that gives us the list of applications. Then we set
* it on the list widget.
* The list will now take items from the model and use the factory
* to create as many listitems as it needs to show itself to the user.
*/
model = create_object_list ();
/* Wrap the model in a sort model that sorts the objects alphabetically.
*/
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
model = G_LIST_MODEL (gtk_sort_list_model_new (model, sorter));
/* Create a sorter for the sections and tell the sort model about it
*/
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, G_CALLBACK (get_section), NULL, NULL)));
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (model), sorter);
g_object_unref (sorter);
/* Create the list widget here.
*/
list = gtk_list_view_new (GTK_SELECTION_MODEL (gtk_single_selection_new (model)), factory);
/* Set a factory for sections, otherwise the listview won't use sections.
*/
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_section_listitem_cb), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_section_listitem_cb), NULL);
gtk_list_view_set_section_factory (GTK_LIST_VIEW (list), factory);
g_object_unref (factory);
/* List widgets should always be contained in a GtkScrolledWindow,
* because otherwise they might get too large or they might not
* be scrollable.
*/
sw = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (window), sw);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -268,13 +268,31 @@ bind_widget (GtkSignalListItemFactory *factory,
break;
}
child = gtk_widget_get_next_sibling (child);
s = g_strdup_printf ("%d°", info->temperature);
gtk_label_set_text (GTK_LABEL (child), s);
g_free (s);
}
static gboolean
transform_weather_to_date_string (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer unused)
{
GtkWeatherInfo *info;
GDateTime *timestamp;
info = g_value_get_object (from_value);
if (info == NULL)
return TRUE;
timestamp = g_date_time_new_from_unix_utc (info->timestamp);
g_value_take_string (to_value, g_date_time_format (timestamp, "%x"));
g_date_time_unref (timestamp);
return TRUE;
}
static GtkWidget *window = NULL;
GtkWidget *
@@ -300,7 +318,7 @@ do_listview_weather (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *sw;
GtkWidget *listview, *sw, *box, *label;
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
@@ -310,10 +328,26 @@ do_listview_weather (GtkWidget *do_widget)
gtk_window_set_title (GTK_WINDOW (window), "Weather");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_window_set_child (GTK_WINDOW (window), box);
label = gtk_label_new ("");
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_box_append (GTK_BOX (box), label);
sw = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (window), sw);
gtk_widget_set_vexpand (sw, TRUE);
gtk_box_append (GTK_BOX (box), sw);
listview = create_weather_view ();
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview);
g_object_bind_property_full (listview, "focus-item",
label, "label",
G_BINDING_SYNC_CREATE,
transform_weather_to_date_string,
NULL,
NULL,
NULL);
}
if (!gtk_widget_get_visible (window))

View File

@@ -18,8 +18,9 @@ demos = files([
'css_shadows.c',
'cursors.c',
'dialog.c',
'drawingarea.c',
'dnd.c',
'drawingarea.c',
'dropdown.c',
'editable_cells.c',
'entry_completion.c',
'entry_undo.c',
@@ -28,6 +29,7 @@ demos = files([
'filtermodel.c',
'fishbowl.c',
'fixed.c',
'flowbox.c',
'fontrendering.c',
'frames.c',
'gears.c',
@@ -46,20 +48,20 @@ demos = files([
'links.c',
'listbox.c',
'listbox_controls.c',
'menu.c',
'flowbox.c',
'list_store.c',
'listview_applauncher.c',
'listview_clocks.c',
'listview_colors.c',
'listview_emoji.c',
'listview_filebrowser.c',
'listview_minesweeper.c',
'dropdown.c',
'listview_objects.c',
'listview_settings.c',
'listview_ucd.c',
'listview_weather.c',
'listview_words.c',
'markup.c',
'menu.c',
'overlay.c',
'overlay_decorative.c',
'paint.c',

View File

@@ -104,6 +104,7 @@
#include <gtk/gtkeditable.h>
#include <gtk/gtkeditablelabel.h>
#include <gtk/gtkemojichooser.h>
#include <gtk/gtkemojilist.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkentrybuffer.h>
#include <gtk/gtkentrycompletion.h>
@@ -213,6 +214,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>

457
gtk/gtkemojilist.c Normal file
View File

@@ -0,0 +1,457 @@
/*
* Copyright © 2022 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "gtkemojilist.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtksectionmodel.h"
#define GDK_ARRAY_ELEMENT_TYPE GtkEmojiObject *
#define GDK_ARRAY_NAME objects
#define GDK_ARRAY_TYPE_NAME Objects
#define GDK_ARRAY_FREE_FUNC g_object_unref
#include "gdk/gdkarrayimpl.c"
struct _GtkEmojiObject
{
GObject parent_instance;
GVariant *data;
gboolean is_recent;
gunichar modifier;
};
enum {
PROP_NAME = 1,
PROP_GROUP,
PROP_NUM_PROPERTIES
};
G_DEFINE_TYPE (GtkEmojiObject, gtk_emoji_object, G_TYPE_OBJECT);
static void
gtk_emoji_object_init (GtkEmojiObject *object)
{
}
static void
gtk_emoji_object_finalize (GObject *object)
{
GtkEmojiObject *self = GTK_EMOJI_OBJECT (object);
g_variant_unref (self->data);
G_OBJECT_CLASS (gtk_emoji_object_parent_class)->finalize (object);
}
static void
gtk_emoji_object_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkEmojiObject *self = GTK_EMOJI_OBJECT (object);
switch (property_id)
{
case PROP_NAME:
g_value_set_string (value, gtk_emoji_object_get_name (self));
break;
case PROP_GROUP:
g_value_set_enum (value, gtk_emoji_object_get_group (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_emoji_object_class_init (GtkEmojiObjectClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GParamSpec *pspec;
object_class->finalize = gtk_emoji_object_finalize;
object_class->get_property = gtk_emoji_object_get_property;
/**
* GtkEmojiObject:name: (attributes org.gtk.Property.get=gtk_emoji_object_get_name)
*
* The name.
*/
pspec = g_param_spec_string ("name", "Name", "Name",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_NAME, pspec);
pspec = g_param_spec_enum ("group", "Group", "Group",
GTK_TYPE_EMOJI_GROUP, GTK_EMOJI_GROUP_SMILEYS,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_GROUP, pspec);
}
static GtkEmojiObject *
gtk_emoji_object_new (GVariant *data,
gboolean recent,
gunichar modifier)
{
GtkEmojiObject *obj;
obj = g_object_new (GTK_TYPE_EMOJI_OBJECT, NULL);
obj->data = g_variant_ref (data);
obj->is_recent = recent;
obj->modifier = modifier;
return obj;
}
/**
* gtk_emoji_object_get_emoji: (attributes org.gtk.Method.get_property=emoji)
* @self: a `GtkEmojiObject`
*
* Returns the emoji contained in a `GtkEmojiObject`.
*
* Returns: the emoji of @self
*/
void
gtk_emoji_object_get_text (GtkEmojiObject *self,
char *buffer,
int length,
gunichar modifier)
{
g_return_if_fail (GTK_IS_EMOJI_OBJECT (self));
GVariant *codes;
char *p = buffer;
gunichar mod;
if (self->is_recent)
mod = self->modifier;
else
mod = modifier;
codes = g_variant_get_child_value (self->data, 0);
for (int i = 0; i < g_variant_n_children (codes); i++)
{
gunichar code;
g_variant_get_child (codes, i, "u", &code);
if (code == 0)
code = mod;
if (code != 0)
p += g_unichar_to_utf8 (code, p);
}
g_variant_unref (codes);
p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */
p[0] = 0;
}
GtkEmojiGroup
gtk_emoji_object_get_group (GtkEmojiObject *self)
{
g_return_val_if_fail (GTK_IS_EMOJI_OBJECT (self), GTK_EMOJI_GROUP_SMILEYS);
if (self->is_recent)
{
return GTK_EMOJI_GROUP_RECENT;
}
else
{
GtkEmojiGroup group;
g_variant_get_child (self->data, 3, "u", &group);
return group + 1;
}
}
const char *
gtk_emoji_object_get_name (GtkEmojiObject *self)
{
const char *name;
g_return_val_if_fail (GTK_IS_EMOJI_OBJECT (self), NULL);
g_variant_get_child (self->data, 1, "s", &name);
return name;
}
const char **
gtk_emoji_object_get_keywords (GtkEmojiObject *self)
{
const char **keywords;
g_return_val_if_fail (GTK_IS_EMOJI_OBJECT (self), NULL);
g_variant_get_child (self->data, 2, "^a&s", &keywords);
return keywords;
}
struct _GtkEmojiList
{
GObject parent_instance;
GVariant *data;
Objects items;
int section[GTK_EMOJI_GROUP_FLAGS + 1];
};
struct _GtkEmojiListClass
{
GObjectClass parent_class;
};
static GType
gtk_emoji_list_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_emoji_list_get_n_items (GListModel *list)
{
GtkEmojiList *self = GTK_EMOJI_LIST (list);
return objects_get_size (&self->items);
}
static gpointer
gtk_emoji_list_get_item (GListModel *list,
guint position)
{
GtkEmojiList *self = GTK_EMOJI_LIST (list);
if (position >= objects_get_size (&self->items))
return NULL;
return g_object_ref (objects_get (&self->items, position));
}
static void
gtk_emoji_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_emoji_list_get_item_type;
iface->get_n_items = gtk_emoji_list_get_n_items;
iface->get_item = gtk_emoji_list_get_item;
}
static void
gtk_emoji_list_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkEmojiList *self = GTK_EMOJI_LIST (model);
GtkEmojiObject *obj;
GtkEmojiGroup group;
if (objects_get_size (&self->items) <= position)
{
*out_start = objects_get_size (&self->items);
*out_end = G_MAXUINT;
return;
}
obj = objects_get (&self->items, position);
group = gtk_emoji_object_get_group (obj);
*out_end = self->section[group];
if (group > 0)
*out_start = self->section[group - 1];
else
*out_start = 0;
g_assert (*out_start <= position);
g_assert (position <= *out_end);
}
static void
gtk_emoji_list_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_emoji_list_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkEmojiList, gtk_emoji_list, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_emoji_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_emoji_list_section_model_init))
static void
gtk_emoji_list_dispose (GObject *object)
{
GtkEmojiList *self = GTK_EMOJI_LIST (object);
objects_clear (&self->items);
g_variant_unref (self->data);
G_OBJECT_CLASS (gtk_emoji_list_parent_class)->dispose (object);
}
static void
gtk_emoji_list_class_init (GtkEmojiListClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->dispose = gtk_emoji_list_dispose;
}
static void
gtk_emoji_list_init (GtkEmojiList *self)
{
objects_init (&self->items);
}
static gboolean
has_emoji_coverage (GtkEmojiObject *emoji)
{
PangoContext *context;
PangoLayout *layout;
char buffer[64];
gboolean ret;
context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
layout = pango_layout_new (context);
gtk_emoji_object_get_text (emoji, buffer, sizeof (buffer), 0);
pango_layout_set_text (layout, buffer, -1);
ret = pango_layout_get_unknown_glyphs_count (layout) == 0;
g_object_unref (layout);
g_object_unref (context);
return ret;
}
static void
gtk_emoji_list_populate_recent (GtkEmojiList *self)
{
GSettings *settings;
GVariant *variant;
GVariant *item;
GVariantIter iter;
int pos;
settings = g_settings_new ("org.gtk.gtk4.Settings.EmojiChooser");
variant = g_settings_get_value (settings, "recent-emoji");
pos = objects_get_size (&self->items);
g_variant_iter_init (&iter, variant);
while ((item = g_variant_iter_next_value (&iter)))
{
GVariant *emoji_data;
gunichar modifier;
GtkEmojiObject *emoji;
emoji_data = g_variant_get_child_value (item, 0);
g_variant_get_child (item, 1, "u", &modifier);
emoji = gtk_emoji_object_new (emoji_data, TRUE, modifier);
if (has_emoji_coverage (emoji))
{
GtkEmojiGroup group;
pos++;
group = gtk_emoji_object_get_group (emoji);
self->section[group] = MAX (self->section[group], pos);
objects_append (&self->items, emoji);
}
else
g_object_unref (emoji);
g_variant_unref (emoji_data);
g_variant_unref (item);
}
g_variant_unref (variant);
g_object_unref (settings);
}
static void
gtk_emoji_list_populate_data (GtkEmojiList *self)
{
GBytes *bytes;
GVariantIter *iter;
GVariant *item;
int pos;
bytes = get_emoji_data ();
self->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(ausasu)"), bytes, TRUE));
g_bytes_unref (bytes);
pos = objects_get_size (&self->items);
iter = g_variant_iter_new (self->data);
while ((item = g_variant_iter_next_value (iter)))
{
GtkEmojiObject *emoji = gtk_emoji_object_new (item, FALSE, 0);
if (has_emoji_coverage (emoji))
{
GtkEmojiGroup group;
pos++;
group = gtk_emoji_object_get_group (emoji);
self->section[group] = MAX (self->section[group], pos);
objects_append (&self->items, emoji);
}
else
g_object_unref (emoji);
g_variant_unref (item);
}
}
/**
* gtk_emoji_list_new:
*
* Creates a new `GtkEmojiList` with the given @emojis.
*
* Returns: a new `GtkEmojiList`
*/
GtkEmojiList *
gtk_emoji_list_new (void)
{
GtkEmojiList *self;
self = g_object_new (GTK_TYPE_EMOJI_LIST, NULL);
gtk_emoji_list_populate_recent (self);
gtk_emoji_list_populate_data (self);
return self;
}

78
gtk/gtkemojilist.h Normal file
View File

@@ -0,0 +1,78 @@
/*
* Copyright © 2022 Red Hat, Inc.
*
* 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: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_EMOJI_LIST_H__
#define __GTK_EMOJI_LIST_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
/* for GDK_AVAILABLE_IN_ALL */
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_EMOJI_OBJECT (gtk_emoji_object_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkEmojiObject, gtk_emoji_object, GTK, EMOJI_OBJECT, GObject)
GDK_AVAILABLE_IN_ALL
void gtk_emoji_object_get_text (GtkEmojiObject *self,
char *buffer,
int length,
gunichar modifier);
typedef enum {
GTK_EMOJI_GROUP_RECENT,
GTK_EMOJI_GROUP_SMILEYS,
GTK_EMOJI_GROUP_BODY,
GTK_EMOJI_GROUP_COMPONENT,
GTK_EMOJI_GROUP_NATURE,
GTK_EMOJI_GROUP_FOOD,
GTK_EMOJI_GROUP_PLACES,
GTK_EMOJI_GROUP_ACTIVITIES,
GTK_EMOJI_GROUP_OBJECTS,
GTK_EMOJI_GROUP_SYMBOLS,
GTK_EMOJI_GROUP_FLAGS
} GtkEmojiGroup;
GDK_AVAILABLE_IN_ALL
GtkEmojiGroup gtk_emoji_object_get_group (GtkEmojiObject *self);
GDK_AVAILABLE_IN_ALL
const char * gtk_emoji_object_get_name (GtkEmojiObject *self);
GDK_AVAILABLE_IN_ALL
const char ** gtk_emoji_object_get_keywords (GtkEmojiObject *self);
#define GTK_TYPE_EMOJI_LIST (gtk_emoji_list_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkEmojiList, gtk_emoji_list, GTK, EMOJI_LIST, GObject)
GDK_AVAILABLE_IN_ALL
GtkEmojiList * gtk_emoji_list_new (void);
G_END_DECLS
#endif /* __GTK_EMOJI_LIST_H__ */

View File

@@ -24,6 +24,7 @@
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtksectionmodelprivate.h"
/**
* GtkFilterListModel:
@@ -134,8 +135,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,
@@ -163,7 +223,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;
@@ -346,7 +406,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,
@@ -473,7 +533,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,9 +21,10 @@
#include "gtkflattenlistmodel.h"
#include "gtkrbtreeprivate.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtksectionmodel.h"
#include "gtkrbtreeprivate.h"
/**
* GtkFlattenListModel:
@@ -198,8 +199,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_start = 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,

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,13 @@ void gtk_grid_view_set_factory (GtkGridView
GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_grid_view_get_factory (GtkGridView *self);
GDK_AVAILABLE_IN_4_8
void gtk_grid_view_set_section_factory (GtkGridView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_8
GtkListItemFactory *
gtk_grid_view_get_section_factory (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
guint gtk_grid_view_get_min_columns (GtkGridView *self);
GDK_AVAILABLE_IN_ALL

View File

@@ -122,6 +122,11 @@ static GParamSpec *properties[N_PROPS] = { NULL, };
* last item will be returned for the whole width, even if there are empty
* cells.
*
* It is also possible for the area to be empty (ie have 0 width and height).
* This can happen when the widget has queued a resize and no current
* allocation information is available for the position or when the position
* has been newly added to the model.
*
* Returns: %TRUE on success or %FALSE if no position occupies the given offset.
**/
static guint
@@ -182,7 +187,7 @@ gtk_list_base_adjustment_value_changed_cb (GtkAdjustment *adjustment,
gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &area.x, &total_size, &area.width);
if (total_size == area.width)
align_across = 0.5;
else if (adjustment != priv->adjustment[priv->orientation])
else if (adjustment != priv->adjustment[OPPOSITE_ORIENTATION(priv->orientation)])
align_across = CLAMP (priv->anchor_align_across, 0, 1);
else
align_across = (double) area.x / (total_size - area.width);
@@ -192,7 +197,7 @@ gtk_list_base_adjustment_value_changed_cb (GtkAdjustment *adjustment,
gtk_list_base_get_adjustment_values (self, priv->orientation, &area.y, &total_size, &area.height);
if (total_size == area.height)
align_along = 0.5;
else if (adjustment != priv->adjustment[OPPOSITE_ORIENTATION(priv->orientation)])
else if (adjustment != priv->adjustment[priv->orientation])
align_along = CLAMP (priv->anchor_align_along, 0, 1);
else
align_along = (double) area.y / (total_size - area.height);
@@ -214,18 +219,18 @@ gtk_list_base_adjustment_value_changed_cb (GtkAdjustment *adjustment,
else if (cell_area.x >= area.x && cell_area.x + cell_area.width > area.x + area.width)
side_across = GTK_PACK_START;
else if (cell_area.x + cell_area.width / 2 > across)
side_across = GTK_PACK_END;
else
side_across = GTK_PACK_START;
else
side_across = GTK_PACK_END;
if (cell_area.y < area.y && cell_area.y + cell_area.height <= area.y + area.height)
side_along = GTK_PACK_END;
else if (cell_area.y >= area.y && cell_area.y + cell_area.height > area.y + area.height)
side_along = GTK_PACK_START;
else if (cell_area.y + cell_area.height / 2 > along)
side_along = GTK_PACK_END;
else
side_along = GTK_PACK_START;
else
side_along = GTK_PACK_END;
/* Compute the align based on side to keep the values identical */
if (side_across == GTK_PACK_START)
@@ -478,6 +483,14 @@ gtk_list_base_get_focus_position (GtkListBase *self)
return gtk_list_item_tracker_get_position (priv->item_manager, priv->focus);
}
gpointer
gtk_list_base_get_focus_item (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return gtk_list_item_tracker_get_item (priv->item_manager, priv->focus);
}
static gboolean
gtk_list_base_focus (GtkWidget *widget,
GtkDirectionType direction)
@@ -1522,10 +1535,10 @@ gtk_list_base_start_rubberband (GtkListBase *self,
priv->rubberband = g_new0 (RubberbandData, 1);
priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager);
priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager, NULL, NULL);
gtk_list_item_tracker_set_position (priv->item_manager, priv->rubberband->start_tracker, pos, 0, 0);
priv->rubberband->start_align_across = (double) (list_x - item_area.x) / item_area.width;
priv->rubberband->start_align_along = (double) (list_y - item_area.y) / item_area.height;
priv->rubberband->start_align_across = item_area.width ? (double) (list_x - item_area.x) / item_area.width : 0.5;
priv->rubberband->start_align_along = item_area.height ? (double) (list_y - item_area.y) / item_area.height : 0.5;
priv->rubberband->pointer_x = x;
priv->rubberband->pointer_y = y;
@@ -1796,11 +1809,13 @@ gtk_list_base_init_real (GtkListBase *self,
g_class->list_item_size,
g_class->list_item_augment_size,
g_class->list_item_augment_func);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager, NULL, NULL);
priv->anchor_side_along = GTK_PACK_START;
priv->anchor_side_across = GTK_PACK_START;
priv->selected = gtk_list_item_tracker_new (priv->item_manager);
priv->focus = gtk_list_item_tracker_new (priv->item_manager);
priv->selected = gtk_list_item_tracker_new (priv->item_manager, NULL, NULL);
priv->focus = gtk_list_item_tracker_new (priv->item_manager,
g_object_class_find_property (G_OBJECT_CLASS (g_class), "focus-position"),
g_object_class_find_property (G_OBJECT_CLASS (g_class), "focus-item"));
priv->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
g_object_ref_sink (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
@@ -2072,7 +2087,7 @@ gtk_list_base_grab_focus_on_item (GtkListBase *self,
if (!item->widget)
{
GtkListItemTracker *tracker = gtk_list_item_tracker_new (priv->item_manager);
GtkListItemTracker *tracker = gtk_list_item_tracker_new (priv->item_manager, NULL, NULL);
/* We need a tracker here to create the widget.
* That needs to have happened or we can't grab it.

View File

@@ -68,6 +68,7 @@ struct _GtkListBaseClass
GtkOrientation gtk_list_base_get_orientation (GtkListBase *self);
#define gtk_list_base_get_opposite_orientation(self) OPPOSITE_ORIENTATION(gtk_list_base_get_orientation(self))
guint gtk_list_base_get_focus_position (GtkListBase *self);
gpointer gtk_list_base_get_focus_item (GtkListBase *self);
GtkListItemManager * gtk_list_base_get_manager (GtkListBase *self);
GtkScrollablePolicy gtk_list_base_get_scroll_policy (GtkListBase *self,
GtkOrientation orientation);

View File

@@ -22,6 +22,7 @@
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemwidgetprivate.h"
#include "gtksectionmodelprivate.h"
#include "gtkwidgetprivate.h"
#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
@@ -33,6 +34,7 @@ struct _GtkListItemManager
GtkWidget *widget;
GtkSelectionModel *model;
GtkListItemFactory *factory;
GtkListItemFactory *section_factory;
gboolean single_click_activate;
const char *item_css_name;
GtkAccessibleRole item_role;
@@ -48,6 +50,9 @@ struct _GtkListItemManagerClass
struct _GtkListItemTracker
{
GParamSpec *position_property;
GParamSpec *item_property;
guint position;
GtkListItemWidget *widget;
guint n_before;
@@ -56,6 +61,7 @@ struct _GtkListItemTracker
static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
gboolean is_header,
GtkWidget *prev_sibling);
static GtkWidget * gtk_list_item_manager_try_reacquire_list_item
(GtkListItemManager *self,
@@ -74,6 +80,41 @@ static void gtk_list_item_manager_release_list_item (GtkListItemMana
GtkWidget *widget);
G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT)
static void
dump_items (GtkListItemManager *self)
{
GtkListItemManagerItem *item;
guint position;
gboolean tracked;
item = gtk_rb_tree_get_first (self->items);
if (item == NULL)
{
g_print ("0\n");
return;
}
g_print ("0 %s ", item->widget ? "X" : "-");
tracked = !!item->widget;
position = item->n_items;
for (item = gtk_rb_tree_node_get_next (item);
item != NULL;
item = gtk_rb_tree_node_get_next (item))
{
if (item->n_items == 0)
continue;
if (!!item->widget != tracked)
{
tracked = !tracked;
g_print ("%u %s ", position, tracked ? "X" : "-");
}
position += item->n_items;
}
g_print ("%u\n", position);
}
void
gtk_list_item_manager_augment_node (GtkRbTree *tree,
gpointer node_augment,
@@ -107,6 +148,7 @@ gtk_list_item_manager_clear_node (gpointer _item)
GtkListItemManagerItem *item G_GNUC_UNUSED = _item;
g_assert (item->widget == NULL);
g_assert (item->section_header == NULL);
}
GtkListItemManager *
@@ -145,6 +187,12 @@ gtk_list_item_manager_get_first (GtkListItemManager *self)
return gtk_rb_tree_get_first (self->items);
}
gpointer
gtk_list_item_manager_get_last (GtkListItemManager *self)
{
return gtk_rb_tree_get_last (self->items);
}
gpointer
gtk_list_item_manager_get_root (GtkListItemManager *self)
{
@@ -249,12 +297,48 @@ gtk_list_item_manager_get_item_augment (GtkListItemManager *self,
return gtk_rb_tree_get_augment (self->items, item);
}
static void
gtk_list_item_tracker_update_widget (GtkListItemManager *self,
GtkListItemTracker *tracker,
GtkListItemWidget *widget)
{
if (tracker->widget == widget)
return;
tracker->widget = widget;
if (tracker->item_property)
g_object_notify_by_pspec (G_OBJECT (self->widget), tracker->item_property);
}
static void
gtk_list_item_tracker_update_position (GtkListItemManager *self,
GtkListItemTracker *tracker,
guint position)
{
if (tracker->position == position)
return;
tracker->position = position;
if (tracker->position_property)
g_object_notify_by_pspec (G_OBJECT (self->widget), tracker->position_property);
}
static void
gtk_list_item_tracker_unset_position (GtkListItemManager *self,
GtkListItemTracker *tracker)
{
if (tracker->position == GTK_INVALID_LIST_POSITION)
return;
tracker->widget = NULL;
tracker->position = GTK_INVALID_LIST_POSITION;
if (tracker->position_property)
g_object_notify_by_pspec (G_OBJECT (self->widget), tracker->position_property);
if (tracker->item_property)
g_object_notify_by_pspec (G_OBJECT (self->widget), tracker->item_property);
}
static gboolean
@@ -347,6 +431,141 @@ restart:
}
}
static gboolean
gtk_list_item_manager_has_sections (GtkListItemManager *self)
{
return self->section_factory != NULL;
}
static void
gtk_list_item_manager_clear_sections (GtkListItemManager *self)
{
GtkListItemManagerItem *item;
for (item = gtk_rb_tree_get_first (self->items);
item != NULL;
item = gtk_rb_tree_node_get_next (item))
{
if (item->section_header)
{
gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
item->section_header = NULL;
}
}
}
typedef struct _SectionIter SectionIter;
struct _SectionIter
{
GtkListItemManagerItem *item;
guint item_pos;
GtkWidget *insert_after;
};
static void
section_iter_init (SectionIter *siter,
GtkListItemManager *self)
{
siter->item = gtk_rb_tree_get_first (self->items);
siter->item_pos = 0;
siter->insert_after = NULL;
}
static void
section_iter_next (SectionIter *siter)
{
siter->item_pos += siter->item->n_items;
if (siter->item->widget)
siter->insert_after = siter->item->widget;
siter->item = gtk_rb_tree_node_get_next (siter->item);
}
static void
section_iter_put_at (SectionIter *siter,
GtkListItemManager *self,
guint pos)
{
/* We already put an item there */
if (siter->item_pos == pos + 1)
return;
g_assert (pos >= siter->item_pos);
while (siter->item_pos < pos)
{
if (siter->item->section_header)
{
gtk_list_item_manager_release_list_item (self, NULL, siter->item->section_header);
siter->item->section_header = NULL;
}
if (siter->item->n_items + siter->item_pos > pos)
gtk_list_item_manager_split_item (self, siter->item, pos - siter->item_pos);
section_iter_next (siter);
}
g_assert (siter->item_pos == pos);
while (siter->item->n_items == 0)
section_iter_next (siter);
if (siter->item->n_items > 1)
gtk_list_item_manager_split_item (self, siter->item, 1);
if (siter->item->section_header == NULL)
{
siter->item->section_header = gtk_list_item_manager_acquire_list_item (self,
pos,
TRUE,
siter->insert_after);
}
section_iter_next (siter);
}
static void
section_iter_finish (SectionIter *siter,
GtkListItemManager *self)
{
while (siter->item)
section_iter_next (siter);
}
static void
gtk_list_item_manager_ensure_sections (GtkListItemManager *self)
{
SectionIter siter;
guint pos, n_items, query_n_items, section_start, section_end;
gboolean tracked;
if (!gtk_list_item_manager_has_sections (self) || self->model == NULL)
return;
pos = 0;
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
section_iter_init (&siter, self);
while (pos < n_items)
{
gtk_list_item_query_tracked_range (self, n_items, pos, &query_n_items, &tracked);
if (tracked)
{
gtk_list_model_get_section (G_LIST_MODEL (self->model), pos, &section_start, &section_end);
section_iter_put_at (&siter, self, section_start);
while (section_end < pos + query_n_items)
{
gtk_list_model_get_section (G_LIST_MODEL (self->model), section_end, &section_start, &section_end);
section_iter_put_at (&siter, self, section_start);
}
}
pos += query_n_items;
}
section_iter_finish (&siter, self);
}
static void
gtk_list_item_manager_remove_items (GtkListItemManager *self,
GHashTable *change,
@@ -372,10 +591,18 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
{
GtkListItemManagerItem *next = gtk_rb_tree_node_get_next (item);
if (item->widget)
gtk_list_item_manager_release_list_item (self, change, item->widget);
item->widget = NULL;
{
gtk_list_item_manager_release_list_item (self, change, item->widget);
item->widget = NULL;
}
if (item->section_header)
{
gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
item->section_header = NULL;
}
n_items -= item->n_items;
gtk_rb_tree_remove (self->items, item);
item->n_items = 0;
gtk_rb_tree_node_mark_dirty (item);
item = next;
}
}
@@ -404,12 +631,39 @@ gtk_list_item_manager_add_items (GtkListItemManager *self,
gtk_widget_queue_resize (GTK_WIDGET (self->widget));
}
gpointer
gtk_list_item_manager_split_item (GtkListItemManager *self,
gpointer itemp,
guint size)
{
GtkListItemManagerItem *item = itemp;
GtkListItemManagerItem *new_item;
g_assert (size <= item->n_items);
new_item = gtk_rb_tree_insert_after (self->items, item);
new_item->n_items = item->n_items - size;
if (size == 0)
{
new_item->widget = item->widget;
item->widget = NULL;
new_item->section_header = item->section_header;
item->section_header = NULL;
}
gtk_rb_tree_node_mark_dirty (new_item);
item->n_items = size;
gtk_rb_tree_node_mark_dirty (item);
return new_item;
}
static gboolean
gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
GtkListItemManagerItem *first,
GtkListItemManagerItem *second)
{
if (first->widget || second->widget)
if (first->widget || first->section_header ||
second->widget || second->section_header)
return FALSE;
first->n_items += second->n_items;
@@ -419,11 +673,36 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
return TRUE;
}
void
gtk_list_item_manager_gc (GtkListItemManager *self)
{
GtkListItemManagerItem *item, *next;
item = gtk_rb_tree_get_first (self->items);
while (item)
{
next = gtk_rb_tree_node_get_next (item);
if (item->n_items == 0)
{
gtk_rb_tree_remove (self->items, item);
item = next;
continue;
}
if (next && gtk_list_item_manager_merge_list_items (self, item, next))
continue;
item = next;
}
}
static void
gtk_list_item_manager_release_items (GtkListItemManager *self,
GQueue *released)
{
GtkListItemManagerItem *item, *prev, *next;
GtkListItemManagerItem *item;
guint position, i, n_items, query_n_items;
gboolean tracked;
@@ -433,6 +712,7 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
while (position < n_items)
{
gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked);
g_print ("tracked: %u => %u %s\n", position, position + query_n_items, tracked ? "X" : "-");
if (tracked)
{
position += query_n_items;
@@ -448,31 +728,19 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
{
g_queue_push_tail (released, item->widget);
item->widget = NULL;
i++;
prev = gtk_rb_tree_node_get_previous (item);
if (prev && gtk_list_item_manager_merge_list_items (self, prev, item))
item = prev;
next = gtk_rb_tree_node_get_next (item);
if (next && next->widget == NULL)
{
i += next->n_items;
if (!gtk_list_item_manager_merge_list_items (self, next, item))
g_assert_not_reached ();
item = gtk_rb_tree_node_get_next (next);
}
else
{
item = next;
}
}
else
if (item->section_header)
{
i += item->n_items;
item = gtk_rb_tree_node_get_next (item);
gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
item->section_header = NULL;
}
i += item->n_items;
item = gtk_rb_tree_node_get_next (item);
}
position += query_n_items;
}
dump_items (self);
}
static void
@@ -522,6 +790,8 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
for (i = 0; i < query_n_items; i++)
{
g_assert (item != NULL);
while (item->n_items == 0)
item = gtk_rb_tree_node_get_next (item);
if (item->n_items > 1)
{
new_item = gtk_rb_tree_insert_before (self->items, item);
@@ -557,6 +827,7 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
{
new_item->widget = gtk_list_item_manager_acquire_list_item (self,
position + i,
FALSE,
insert_after);
}
}
@@ -564,7 +835,11 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
else
{
if (update_start <= position + i)
gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
{
gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
if (new_item->section_header)
gtk_list_item_manager_update_list_item (self, new_item->section_header, position + i);
}
}
insert_after = new_item->widget;
}
@@ -573,6 +848,10 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
while ((widget = g_queue_pop_head (&released)))
gtk_list_item_manager_release_list_item (self, NULL, widget);
gtk_list_item_manager_ensure_sections (self);
dump_items (self);
}
static void
@@ -675,33 +954,35 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
{
/* if the list is no longer empty, set the tracker to a valid position. */
if (n_items > 0 && n_items == added && removed == 0)
tracker->position = 0;
gtk_list_item_tracker_update_position (self, tracker, 0);
}
else if (tracker->position >= position + removed)
{
tracker->position += added - removed;
gtk_list_item_tracker_update_position (self, tracker, tracker->position + added - removed);
}
else if (tracker->position >= position)
{
if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget)))
{
/* The item is gone. Guess a good new position */
tracker->position = position + (tracker->position - position) * added / removed;
if (tracker->position >= n_items)
guint new_position = position + (tracker->position - position) * added / removed;
if (new_position >= n_items)
{
if (n_items == 0)
tracker->position = GTK_INVALID_LIST_POSITION;
gtk_list_item_tracker_unset_position (self, tracker);
else
tracker->position--;
gtk_list_item_tracker_update_position (self, tracker, new_position - 1);
}
tracker->widget = NULL;
else
gtk_list_item_tracker_update_position (self, tracker, new_position);
tracker->widget = NULL; /* will be filled in below */
}
else
{
/* item was put in its right place in the expensive loop above,
* and we updated its position while at it. So grab it from there.
*/
tracker->position = gtk_list_item_widget_get_position (tracker->widget);
gtk_list_item_tracker_update_position (self, tracker, gtk_list_item_widget_get_position (tracker->widget));
}
}
else
@@ -728,7 +1009,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
g_assert (item != NULL);
g_assert (item->widget);
tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
gtk_list_item_tracker_update_widget (self, tracker, GTK_LIST_ITEM_WIDGET (item->widget));
}
g_hash_table_unref (change);
@@ -861,6 +1142,31 @@ gtk_list_item_manager_get_factory (GtkListItemManager *self)
return self->factory;
}
void
gtk_list_item_manager_set_section_factory (GtkListItemManager *self,
GtkListItemFactory *factory)
{
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
if (self->section_factory == factory)
return;
gtk_list_item_manager_clear_sections (self);
g_set_object (&self->section_factory, factory);
gtk_list_item_manager_ensure_sections (self);
}
GtkListItemFactory *
gtk_list_item_manager_get_section_factory (GtkListItemManager *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
return self->factory;
}
void
gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model)
@@ -902,6 +1208,8 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
* gtk_list_item_manager_acquire_list_item:
* @self: a `GtkListItemManager`
* @position: the row in the model to create a list item for
* @is_header: %TRUE if this is for acquiring a header, %FALSE for regular
* list items
* @prev_sibling: the widget this widget should be inserted before or %NULL
* if it should be the first widget
*
@@ -919,6 +1227,7 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
static GtkWidget *
gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
gboolean is_header,
GtkWidget *prev_sibling)
{
GtkWidget *result;
@@ -928,14 +1237,24 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
result = gtk_list_item_widget_new (self->factory,
self->item_css_name,
self->item_role);
gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), self->single_click_activate);
if (is_header)
{
result = gtk_list_item_widget_new (self->section_factory,
"section",
GTK_ACCESSIBLE_ROLE_ROW_HEADER);
gtk_list_item_widget_set_activatable (GTK_LIST_ITEM_WIDGET (result), FALSE);
selected = FALSE;
}
else
{
result = gtk_list_item_widget_new (self->factory,
self->item_css_name,
self->item_role);
selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), self->single_click_activate);
}
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, item, selected);
g_object_unref (item);
gtk_widget_insert_after (result, self->widget, prev_sibling);
@@ -1111,7 +1430,9 @@ gtk_list_item_manager_get_single_click_activate (GtkListItemManager *self)
}
GtkListItemTracker *
gtk_list_item_tracker_new (GtkListItemManager *self)
gtk_list_item_tracker_new (GtkListItemManager *self,
GParamSpec *position_property,
GParamSpec *item_property)
{
GtkListItemTracker *tracker;
@@ -1120,6 +1441,8 @@ gtk_list_item_tracker_new (GtkListItemManager *self)
tracker = g_slice_new0 (GtkListItemTracker);
tracker->position = GTK_INVALID_LIST_POSITION;
tracker->position_property = position_property;
tracker->item_property = item_property;
self->trackers = g_slist_prepend (self->trackers, tracker);
@@ -1151,26 +1474,37 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self,
GtkListItemManagerItem *item;
guint n_items;
gtk_list_item_tracker_unset_position (self, tracker);
if (self->model == NULL)
return;
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
if (position >= n_items)
position = n_items - 1; /* for n_items == 0 this underflows to GTK_INVALID_LIST_POSITION */
tracker->position = position;
tracker->n_before = n_before;
tracker->n_after = n_after;
if (self->model == NULL)
{
g_assert (tracker->position == GTK_INVALID_LIST_POSITION);
return;
}
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
if (n_items == 0)
{
g_assert (tracker->position == GTK_INVALID_LIST_POSITION);
return;
}
if (position >= n_items)
position = n_items - 1;
g_object_freeze_notify (G_OBJECT (self->widget));
gtk_list_item_tracker_update_position (self, tracker, position);
gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
item = gtk_list_item_manager_get_nth (self, position, NULL);
if (item)
tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
g_assert (item);
gtk_list_item_tracker_update_widget (self, tracker, GTK_LIST_ITEM_WIDGET (item->widget));
gtk_widget_queue_resize (self->widget);
g_object_thaw_notify (G_OBJECT (self->widget));
}
guint
@@ -1179,3 +1513,13 @@ gtk_list_item_tracker_get_position (GtkListItemManager *self,
{
return tracker->position;
}
gpointer
gtk_list_item_tracker_get_item (GtkListItemManager *self,
GtkListItemTracker *tracker)
{
if (tracker->widget == NULL)
return NULL;
return gtk_list_item_widget_get_item (tracker->widget);
}

View File

@@ -46,6 +46,7 @@ typedef struct _GtkListItemTracker GtkListItemTracker;
struct _GtkListItemManagerItem
{
GtkWidget *widget;
GtkWidget *section_header;
guint n_items;
};
@@ -73,6 +74,7 @@ void gtk_list_item_manager_augment_node (GtkRbTree
gpointer right);
gpointer gtk_list_item_manager_get_root (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_first (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_last (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_nth (GtkListItemManager *self,
guint position,
guint *offset);
@@ -81,9 +83,17 @@ guint gtk_list_item_manager_get_item_position (GtkListItemMana
gpointer gtk_list_item_manager_get_item_augment (GtkListItemManager *self,
gpointer item);
gpointer gtk_list_item_manager_split_item (GtkListItemManager *self,
gpointer item,
guint size);
void gtk_list_item_manager_gc (GtkListItemManager *self);
void gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self);
void gtk_list_item_manager_set_section_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_section_factory (GtkListItemManager *self);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
@@ -95,7 +105,9 @@ void gtk_list_item_manager_set_single_click_activate
gboolean gtk_list_item_manager_get_single_click_activate
(GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self,
GParamSpec *position_property,
GParamSpec *item_property);
void gtk_list_item_tracker_free (GtkListItemManager *self,
GtkListItemTracker *tracker);
void gtk_list_item_tracker_set_position (GtkListItemManager *self,
@@ -105,6 +117,8 @@ void gtk_list_item_tracker_set_position (GtkListItemMana
guint n_after);
guint gtk_list_item_tracker_get_position (GtkListItemManager *self,
GtkListItemTracker *tracker);
gpointer gtk_list_item_tracker_get_item (GtkListItemManager *self,
GtkListItemTracker *tracker);
G_END_DECLS

View File

@@ -148,6 +148,7 @@ typedef struct _ListRowAugment ListRowAugment;
struct _ListRow
{
GtkListItemManagerItem parent;
guint section_height;
guint height; /* per row */
};
@@ -161,7 +162,10 @@ enum
{
PROP_0,
PROP_FACTORY,
PROP_FOCUS_ITEM,
PROP_FOCUS_POSITION,
PROP_MODEL,
PROP_SECTION_FACTORY,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
@@ -213,7 +217,7 @@ list_row_augment (GtkRbTree *tree,
gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
aug->height = row->height * row->parent.n_items;
aug->height = row->height + row->section_height;
if (left)
{
@@ -253,9 +257,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
y -= aug->height;
}
if (y < row->height * row->parent.n_items)
if (y < row->height + row->section_height)
break;
y -= row->height * row->parent.n_items;
y -= row->height + row->section_height;
row = gtk_rb_tree_node_get_right (row);
}
@@ -295,7 +299,7 @@ list_row_get_y (GtkListView *self,
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
y += aug->height;
}
y += parent->height * parent->parent.n_items;
y += parent->height + parent->section_height;
}
row = parent;
@@ -304,20 +308,6 @@ list_row_get_y (GtkListView *self,
return y ;
}
static int
gtk_list_view_get_list_height (GtkListView *self)
{
ListRow *row;
ListRowAugment *aug;
row = gtk_list_item_manager_get_root (self->item_manager);
if (row == NULL)
return 0;
aug = gtk_list_item_manager_get_item_augment (self->item_manager, row);
return aug->height;
}
static gboolean
gtk_list_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -340,12 +330,14 @@ gtk_list_view_get_allocation_along (GtkListBase *base,
}
y = list_row_get_y (self, row);
y += skip * row->height;
g_assert (row->parent.n_items > 0);
if (skip)
y += row->section_height + skip * row->height / row->parent.n_items;
if (offset)
*offset = y;
if (size)
*size = row->height;
*size = row->height / row->parent.n_items + (skip ? 0 : row->section_height);
return TRUE;
}
@@ -374,6 +366,7 @@ gtk_list_view_get_items_in_rect (GtkListBase *base,
guint first, last, n_items;
GtkBitset *result;
ListRow *row;
int remaining;
result = gtk_bitset_new_empty ();
@@ -386,9 +379,13 @@ gtk_list_view_get_items_in_rect (GtkListBase *base,
first = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
first = rect->y < 0 ? 0 : n_items - 1;
row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, &remaining);
if (row)
last = gtk_list_item_manager_get_item_position (self->item_manager, row);
{
last = gtk_list_item_manager_get_item_position (self->item_manager, row);
if (remaining < row->section_height && last > 0)
last--;
}
else
last = rect->y < 0 ? 0 : n_items - 1;
@@ -420,7 +417,7 @@ gtk_list_view_get_position_from_allocation (GtkListBase *base,
{
GtkListView *self = GTK_LIST_VIEW (base);
ListRow *row;
int remaining;
int remaining, one_item_height;
if (across >= self->list_width)
return FALSE;
@@ -430,15 +427,24 @@ gtk_list_view_get_position_from_allocation (GtkListBase *base,
return FALSE;
*pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
g_assert (remaining < row->height * row->parent.n_items);
*pos += remaining / row->height;
if (row->parent.n_items == 0)
{
row = gtk_list_item_manager_get_nth (self->item_manager, *pos, NULL);
remaining = 0;
one_item_height = 1;
}
else
one_item_height = row->height / row->parent.n_items;
g_assert (remaining < row->height + row->section_height);
*pos += remaining / one_item_height;
if (area)
{
area->x = 0;
area->width = self->list_width;
area->y = along - remaining % row->height;
area->height = row->height;
area->y = along - remaining % one_item_height;
area->height = one_item_height;
}
return TRUE;
@@ -501,6 +507,14 @@ gtk_list_view_measure_across (GtkWidget *widget,
&child_min, &child_nat, NULL, NULL);
min = MAX (min, child_min);
nat = MAX (nat, child_nat);
if (row->parent.section_header)
{
gtk_widget_measure (row->parent.section_header,
orientation, for_size,
&child_min, &child_nat, NULL, NULL);
min = MAX (min, child_min);
nat = MAX (nat, child_nat);
}
}
*minimum = min;
@@ -537,6 +551,15 @@ gtk_list_view_measure_list (GtkWidget *widget,
&child_min, &child_nat, NULL, NULL);
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
if (row->parent.section_header)
{
int section_min, section_nat;
gtk_widget_measure (row->parent.section_header,
orientation, for_size,
&section_min, &section_nat, NULL, NULL);
child_min += section_min;
child_nat += section_nat;
}
min += child_min;
nat += child_nat;
}
@@ -575,6 +598,20 @@ gtk_list_view_measure (GtkWidget *widget,
gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
}
static void
row_set_height (ListRow *row,
int section_height,
int row_height)
{
if (row->section_height == section_height &&
row->height == row_height)
return;
row->section_height = section_height;
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
static void
gtk_list_view_size_allocate (GtkWidget *widget,
int width,
@@ -584,7 +621,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
GtkListView *self = GTK_LIST_VIEW (widget);
ListRow *row;
GArray *heights;
int min, nat, row_height;
int min, nat, row_height, section_height, total_height;
int x, y;
GtkOrientation orientation, opposite_orientation;
GtkScrollablePolicy scroll_policy, opposite_scroll_policy;
@@ -594,11 +631,14 @@ gtk_list_view_size_allocate (GtkWidget *widget,
scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
opposite_scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), opposite_orientation);
/* step 0: exit early if list is empty */
/* step 1: Clean up, so the items tracking deleted rows go away */
gtk_list_item_manager_gc (self->item_manager);
/* step 2: exit early if list is empty */
if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
return;
/* step 1: determine width of the list */
/* step 3: determine width of the list */
gtk_widget_measure (widget, opposite_orientation,
-1,
&min, &nat, NULL, NULL);
@@ -608,32 +648,45 @@ gtk_list_view_size_allocate (GtkWidget *widget,
else
self->list_width = MAX (nat, self->list_width);
/* step 2: determine height of known list items */
/* step 4: determine height of known list items */
heights = g_array_new (FALSE, FALSE, sizeof (int));
total_height = 0;
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget == NULL)
continue;
gtk_widget_measure (row->parent.widget, orientation,
self->list_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
row_height = min;
else
row_height = nat;
if (row->height != row_height)
if (row->parent.widget)
{
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
gtk_widget_measure (row->parent.widget, orientation,
self->list_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
row_height = min;
else
row_height = nat;
g_array_append_val (heights, row_height);
}
g_array_append_val (heights, row_height);
else
row_height = 0;
if (row->parent.section_header)
{
gtk_widget_measure (row->parent.section_header, orientation,
self->list_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
section_height = min;
else
section_height = nat;
}
else
section_height = 0;
row_set_height (row, section_height, row_height);
total_height += row_height;
}
/* step 3: determine height of unknown items */
/* step 5: determine height of unknown items */
row_height = gtk_list_view_get_unknown_row_height (self, heights);
g_array_free (heights, TRUE);
@@ -644,29 +697,35 @@ gtk_list_view_size_allocate (GtkWidget *widget,
if (row->parent.widget)
continue;
if (row->height != row_height)
{
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
row_set_height (row, 0, row->parent.n_items * row_height);
total_height += row->height;
}
/* step 3: update the adjustments */
/* step 6: update the adjustments */
gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
self->list_width,
gtk_list_view_get_list_height (self),
total_height,
gtk_widget_get_size (widget, opposite_orientation),
gtk_widget_get_size (widget, orientation),
&x, &y);
x = -x;
y = -y;
/* step 4: actually allocate the widgets */
/* step 7: actually allocate the widgets */
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.section_header)
{
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
row->parent.section_header,
x,
y,
self->list_width,
row->section_height);
y += row->section_height;
}
if (row->parent.widget)
{
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
@@ -676,8 +735,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
self->list_width,
row->height);
}
y += row->height * row->parent.n_items;
y += row->height;
}
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));
@@ -707,10 +765,22 @@ gtk_list_view_get_property (GObject *object,
g_value_set_object (value, gtk_list_item_manager_get_factory (self->item_manager));
break;
case PROP_FOCUS_ITEM:
g_value_set_object (value, gtk_list_base_get_focus_item (GTK_LIST_BASE (self)));
break;
case PROP_FOCUS_POSITION:
g_value_set_uint (value, gtk_list_base_get_focus_position (GTK_LIST_BASE (self)));
break;
case PROP_MODEL:
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
case PROP_SECTION_FACTORY:
g_value_set_object (value, gtk_list_item_manager_get_section_factory (self->item_manager));
break;
case PROP_SHOW_SEPARATORS:
g_value_set_boolean (value, self->show_separators);
break;
@@ -743,10 +813,18 @@ gtk_list_view_set_property (GObject *object,
gtk_list_view_set_factory (self, g_value_get_object (value));
break;
case PROP_FOCUS_POSITION:
gtk_list_view_set_focus_position (self, g_value_get_uint (value));
break;
case PROP_MODEL:
gtk_list_view_set_model (self, g_value_get_object (value));
break;
case PROP_SECTION_FACTORY:
gtk_list_view_set_section_factory (self, g_value_get_object (value));
break;
case PROP_SHOW_SEPARATORS:
gtk_list_view_set_show_separators (self, g_value_get_boolean (value));
break;
@@ -821,6 +899,35 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:focus-position: (attributes org.gtk.Property.get=gtk_list_view_get_focus_position org.gtk.Property.set=gtk_list_view_set_focus_position)
*
* The position of the focused item.
*
* If no item is in focus, the property has the value
* %GTK_INVALID_LIST_POSITION.
*/
properties[PROP_FOCUS_POSITION] =
g_param_spec_uint ("focus-position",
P_("Focus position"),
P_("Position of the focused item"),
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:focus-item: (attributes org.gtk.Property.get=gtk_list_view_get_focus_item)
*
* The focused item.
*
* If the list is empty, %NULL is returned.
*/
properties[PROP_FOCUS_ITEM] =
g_param_spec_object ("focus-item",
P_("Focused Item"),
P_("The focused item"),
G_TYPE_OBJECT,
G_PARAM_READABLE | 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)
*
@@ -833,6 +940,20 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GTK_TYPE_SELECTION_MODEL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:section-factory: (attributes org.gtk.Property.get=gtk_list_view_get_section_factory org.gtk.Property.set=gtk_list_view_set_section_factory)
*
* Factory for populating section headers.
*
* Since: 4.8
*/
properties[PROP_SECTION_FACTORY] =
g_param_spec_object ("section-factory",
P_("Section factory"),
P_("Factory for populating secion headers"),
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:show-separators: (attributes org.gtk.Property.get=gtk_list_view_get_show_separators org.gtk.Property.set=gtk_list_view_set_show_separators)
*
@@ -1042,6 +1163,122 @@ gtk_list_view_set_factory (GtkListView *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_list_view_get_section_factory: (attributes org.gtk.Method.get_property=section-factory)
* @self: a `GtkListView`
*
* Gets the factory that's currently used to populate section headers.
*
* Returns: (nullable) (transfer none): The factory in use
*
* Since: 4.8
*/
GtkListItemFactory *
gtk_list_view_get_section_factory (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return gtk_list_item_manager_get_section_factory (self->item_manager);
}
/**
* gtk_list_view_set_section_factory: (attributes org.gtk.Method.set_property=section-factory)
* @self: a `GtkListView`
* @factory: (nullable) (transfer none): the factory to use
*
* Sets the `GtkListItemFactory` to use for populating section headers.
*
* If this factory is set to %NULL, the list will not use section.
*
* Since: 4.8
*/
void
gtk_list_view_set_section_factory (GtkListView *self,
GtkListItemFactory *factory)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
if (factory == gtk_list_item_manager_get_section_factory (self->item_manager))
return;
gtk_list_item_manager_set_section_factory (self->item_manager, factory);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_FACTORY]);
}
/**
* gtk_list_view_set_focus_position: (attributes org.gtk.Method.set_property=focus-position)
* @self: a `GtkListView`
* @position: position of item to focus
*
* Sets focus to be given to the item at the given position.
* If position is larger than the number of items, this call is ignored.
*
* Moving keyboard focus will select the new item and scroll it into view.
*
* The operation will be performed even if the listview is hidden.
*
* If keyboard focus is not currently inside the listview, the application's
* keyboard focus will not change. Call gtk_widget_grab_focus() on the listview
* afterwards to move application keyboard focus.
*
* Since: 4.8
*/
void
gtk_list_view_set_focus_position (GtkListView *self,
guint position)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), position, TRUE, FALSE, FALSE);
}
/**
* gtk_list_view_get_focus_position: (attributes org.gtk.Method.get_property=focus-position)
* @self: a `GtkListView`
*
* Gets the position of the item that currently has keyboard focus - or that
* would have keyboard focus if the listview was focused.
*
* Note that the focused item might not be in the visible region, for example when
* the visible region was scrolled with the mouse.
*
* Returns: the position of the focused item, or %GTK_INVALID_LIST_POSITION
* if the listview is empty
*
* Since: 4.8
*/
guint
gtk_list_view_get_focus_position (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), GTK_INVALID_LIST_POSITION);
return gtk_list_base_get_focus_position (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_get_focus_item: (attributes org.gtk.Method.get_property=focus-item)
* @self: a `GtkListView`
*
* Gets the item that currently has keyboard focus - or that would have keyboard
* focus if the listview was focused.
*
* Note that the focused item might not be in the visible region, for example when
* the visible region was scrolled with the mouse.
*
* Returns: (transfer none) (type GObject) (nullable): The fcoused item
*
* Since: 4.8
**/
gpointer
gtk_list_view_get_focus_item (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return gtk_list_base_get_focus_item (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
* @self: a `GtkListView`
@@ -1161,3 +1398,4 @@ gtk_list_view_get_enable_rubberband (GtkListView *self)
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
}

View File

@@ -58,6 +58,21 @@ GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self);
GDK_AVAILABLE_IN_4_8
void gtk_list_view_set_section_factory (GtkListView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_8
GtkListItemFactory *
gtk_list_view_get_section_factory (GtkListView *self);
GDK_AVAILABLE_IN_4_8
void gtk_list_view_set_focus_position (GtkListView *self,
guint position);
GDK_AVAILABLE_IN_4_8
guint gtk_list_view_get_focus_position (GtkListView *self);
GDK_AVAILABLE_IN_4_8
gpointer gtk_list_view_get_focus_item (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_show_separators (GtkListView *self,
gboolean show_separators);

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -93,6 +94,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)
@@ -204,6 +222,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

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -91,6 +92,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)
@@ -116,6 +134,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))

226
gtk/gtksectionmodel.c Normal file
View File

@@ -0,0 +1,226 @@
/*
* 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 "gtkintl.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.8
*/
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::section-changed] signal for all the items
* it covers.
*
* Since: 4.8
*/
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:
* @model: 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.8
*/
void
gtk_section_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkSectionModelInterface *iface;
g_return_if_fail (GTK_IS_SECTION_MODEL (model));
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
iface = GTK_SECTION_MODEL_GET_IFACE (model);
iface->get_section (model, 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 *model,
guint position,
guint *out_start,
guint *out_end)
{
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
if (model == NULL)
{
*out_start = 0;
*out_end = G_MAXUINT;
return;
}
g_return_if_fail (G_IS_LIST_MODEL (model));
if (!GTK_IS_SECTION_MODEL (model))
{
guint n_items = g_list_model_get_n_items (model);
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 (model), position, out_start, out_end);
}
/**
* gtk_section_model_section_changed:
* @model: 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.8
*/
void
gtk_section_model_sections_changed (GtkSectionModel *model,
guint position,
guint n_items)
{
g_return_if_fail (GTK_IS_SECTION_MODEL (model));
g_return_if_fail (n_items > 0);
g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (model)));
g_signal_emit (model, signals[SECTIONS_CHANGED], 0, position, n_items);
}

74
gtk/gtksectionmodel.h Normal file
View File

@@ -0,0 +1,74 @@
/*
* 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>
*/
#ifndef __GTK_SECTION_MODEL_H__
#define __GTK_SECTION_MODEL_H__
#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_8
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.8
*/
struct _GtkSectionModelInterface
{
/*< private >*/
GTypeInterface g_iface;
/*< public >*/
void (* get_section) (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end);
};
GDK_AVAILABLE_IN_4_8
void gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
/* for implementations only */
GDK_AVAILABLE_IN_4_8
void gtk_section_model_sections_changed (GtkSectionModel *model,
guint position,
guint n_items);
G_END_DECLS
#endif /* __GTK_SECTION_MODEL_H__ */

View File

@@ -0,0 +1,16 @@
#ifndef __GTK_SECTION_MODEL_PRIVATE_H__
#define __GTK_SECTION_MODEL_PRIVATE_H__
#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
#endif /* __GTK_SECTION_MODEL_PRIVATE_H__ */

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -102,6 +103,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)
@@ -166,6 +184,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

@@ -23,7 +23,9 @@
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtkmultisorter.h"
#include "gtkprivate.h"
#include "gtksectionmodel.h"
#include "gtksorterprivate.h"
#include "timsort/gtktimsortprivate.h"
@@ -74,6 +76,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 {
@@ -81,6 +90,7 @@ enum {
PROP_INCREMENTAL,
PROP_MODEL,
PROP_PENDING,
PROP_SECTION_SORTER,
PROP_SORTER,
NUM_PROPERTIES
};
@@ -91,6 +101,8 @@ struct _GtkSortListModel
GListModel *model;
GtkSorter *sorter;
GtkSorter *section_sorter;
GtkSorter *real_sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
@@ -98,6 +110,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;
@@ -173,8 +186,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)
@@ -377,6 +541,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;
}
@@ -424,9 +589,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
@@ -434,9 +599,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);
@@ -640,6 +808,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;
@@ -672,6 +844,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;
@@ -749,13 +925,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
@@ -764,7 +969,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);
};
@@ -814,6 +1021,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.
*/
properties[PROP_SECTION_SORTER] =
g_param_spec_object ("section-sorter",
P_("Section orter"),
P_("The sorter sorting this model into sections"),
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)
*
@@ -940,15 +1159,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]);
}
@@ -969,6 +1189,51 @@ 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.
*/
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_sorter: (attributes org.gtk.Method.get_property=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
*/
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

@@ -46,6 +46,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_8
void gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_4_8
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

@@ -14,6 +14,7 @@ gtk_cargs = [
# List of sources that do not contain public API, and should not be
# introspected
gtk_private_sources = files([
'gtkemojilist.c',
'fnmatch.c',
'gdkpixbufutils.c',
'gsettings-mapping.c',
@@ -354,6 +355,7 @@ gtk_public_sources = files([
'gtkscrolledwindow.c',
'gtksearchbar.c',
'gtksearchentry.c',
'gtksectionmodel.c',
'gtkselectionfiltermodel.c',
'gtkselectionmodel.c',
'gtkseparator.c',
@@ -519,6 +521,7 @@ gtk_public_headers = files([
'gtkeditable.h',
'gtkeditablelabel.h',
'gtkemojichooser.h',
'gtkemojilist.h',
'gtkentry.h',
'gtkentrybuffer.h',
'gtkentrycompletion.h',
@@ -628,6 +631,7 @@ gtk_public_headers = files([
'gtkscrolledwindow.h',
'gtksearchbar.h',
'gtksearchentry.h',
'gtksectionmodel.h',
'gtkselectionfiltermodel.h',
'gtkselectionmodel.h',
'gtkseparator.h',

View File

@@ -142,6 +142,11 @@ gridview {
margin: 12px;
}
}
> section {
background: hotpink;
padding: 6px;
}
}
coverflow cover {
@@ -3316,6 +3321,11 @@ list {
&.separators:not(.horizontal) > row:not(.separator) {
border-bottom: 1px solid $_treeview_borders_color;
}
> section {
background: hotpink;
padding: 6px;
}
}
row {

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)
{
@@ -441,6 +458,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

@@ -207,7 +207,7 @@ static void
test_create (void)
{
GtkFilterListModel *filter;
filter = new_model (10, NULL, NULL);
assert_model (filter, "1 2 3 4 5 6 7 8 9 10");
assert_changes (filter, "");
@@ -305,7 +305,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, "");
@@ -405,6 +405,94 @@ test_empty (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[])
{
@@ -419,6 +507,7 @@ main (int argc, char *argv[])
g_test_add_func ("/filterlistmodel/change_filter", test_change_filter);
g_test_add_func ("/filterlistmodel/incremental", test_incremental);
g_test_add_func ("/filterlistmodel/empty", test_empty);
g_test_add_func ("/filterlistmodel/sections", test_sections);
return g_test_run ();
}

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)
{
@@ -259,7 +274,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:
@@ -435,6 +450,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)
@@ -460,6 +727,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

@@ -249,7 +249,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");
@@ -267,7 +267,7 @@ test_set_model (void)
{
GtkSortListModel *sort;
GListStore *store;
sort = new_model (NULL);
assert_model (sort, "");
assert_changes (sort, "");
@@ -306,7 +306,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");
@@ -337,7 +337,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);
@@ -377,7 +377,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);
@@ -534,6 +534,58 @@ test_out_of_bounds_access (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[])
{
@@ -554,6 +606,7 @@ main (int argc, char *argv[])
g_test_add_func ("/sortlistmodel/stability", test_stability);
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/sections", test_sections);
return g_test_run ();
}