Compare commits
29 Commits
main
...
emoji-grid
Author | SHA1 | Date | |
---|---|---|---|
|
106ce8580c | ||
|
8690ca4a65 | ||
|
f7d55dd36f | ||
|
dd5fd873fd | ||
|
afc2be4b14 | ||
|
2c6ffe622a | ||
|
9426761339 | ||
|
49616f7d00 | ||
|
4a8d5d33df | ||
|
228a88f033 | ||
|
e5dbadb96e | ||
|
0958f6f8b9 | ||
|
00eebfa215 | ||
|
e5b4e5cf66 | ||
|
a507b2fbfd | ||
|
83f321aad4 | ||
|
a537dbb060 | ||
|
15f27ac44a | ||
|
7e4f25e1a9 | ||
|
3ca1b04c11 | ||
|
b974da285d | ||
|
1d25a8f21a | ||
|
5925cd68ce | ||
|
17539d9378 | ||
|
352298f281 | ||
|
f414521aab | ||
|
0c5329212b | ||
|
bc9699e6ed | ||
|
0751a1ab67 |
@@ -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>
|
||||
|
216
demos/gtk-demo/listview_emoji.c
Normal file
216
demos/gtk-demo/listview_emoji.c
Normal 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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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>
|
||||
|
220
demos/gtk-demo/listview_objects.c
Normal file
220
demos/gtk-demo/listview_objects.c
Normal 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;
|
||||
}
|
@@ -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))
|
||||
|
@@ -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',
|
||||
|
@@ -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
457
gtk/gtkemojilist.c
Normal 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
78
gtk/gtkemojilist.h
Normal 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__ */
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
1211
gtk/gtkgridview.c
1211
gtk/gtkgridview.c
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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, §ion_start, §ion_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, §ion_start, §ion_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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
§ion_min, §ion_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));
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -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
226
gtk/gtksectionmodel.c
Normal 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
74
gtk/gtksectionmodel.h
Normal 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__ */
|
16
gtk/gtksectionmodelprivate.h
Normal file
16
gtk/gtksectionmodelprivate.h
Normal 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__ */
|
@@ -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))
|
||||
|
||||
|
@@ -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`
|
||||
|
@@ -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);
|
||||
|
@@ -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',
|
||||
|
@@ -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 {
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 ();
|
||||
}
|
||||
|
@@ -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 ();
|
||||
}
|
||||
|
@@ -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 ();
|
||||
}
|
||||
|
Reference in New Issue
Block a user