Compare commits

...

46 Commits

Author SHA1 Message Date
Matthias Clasen
8a6ce84635 scrollinfo: Init the refcount 2023-05-26 06:28:03 -04:00
Matthias Clasen
34c258c93c Add gtk_grid_view_scroll_to
This does the same as gtk_list_view_scroll_to, just
for a grid view.
2023-05-24 22:38:36 -04:00
Matthias Clasen
3ec5bdeaa4 Polish the scrollinfo demo a bit 2023-05-24 22:25:27 -04:00
Benjamin Otte
f2994cf826 wayland: Don't create resources on construct
Instead, wait until present().

This makes resource construction happen in present() in destruction in
hide() and makes sure things are matched.

In particular, it avoids a mismatch between surfaces that have been
present()ed before but are hidden again and surfaces that have never
been presented.
2023-04-11 19:45:50 +02:00
Benjamin Otte
e11fdd33d9 listbase: Add a debug keybinding
Ctrl-Shift-R (for rendernode, yay my creativity):
Creates a rendernode of the current tiles and dumps them into the
clipboard.
2023-04-11 15:59:43 +02:00
Benjamin Otte
1b699f2ce7 gtk-demo: Make arrowing in suggestionentry scroll the dropdown 2023-04-11 15:59:43 +02:00
Benjamin Otte
5269f328af inspector: Use scroll_to() in the object tree search 2023-04-11 15:59:43 +02:00
Benjamin Otte
cca2f0527a columnview: Implement gtk_column_view_scroll_to()
It's basically the listview version, but with an (optional) column to do
cell-based scrolling/focusing.
2023-04-11 15:59:43 +02:00
Benjamin Otte
4315289b04 listview: Implement gtk_list_view_scroll_to()
This adds a flags enum so we can also do select/focus at the same time.

It's implemented in GtkListBase, so adding support forgridview should be
easy.
2023-04-11 15:59:43 +02:00
Benjamin Otte
f4d4151b13 demo: Add a scrollinfo demo
The demo allows playing with the values passed to GtkScrollInfo and
scroll a viewport with those settings.
2023-04-11 15:59:43 +02:00
Benjamin Otte
1ae9b60339 viewport: Use gtk_viewport_scroll_to() for focus scrolling 2023-04-11 15:59:43 +02:00
Benjamin Otte
0714da4d69 viewport: Add gtk_viewport_scroll_to()
First implementation of GtkScrollInfo
2023-04-11 15:59:43 +02:00
Benjamin Otte
cc8f31a7bf scrollinfo: Fix static initializer for poor MSVC
MSVC can't deal with the (graphene_rect_t) cast that
GRAPHENE_RECT_INIT() prepends (as it should) when used as a static
initializer, pretending that
  error C2099: initializer is not a constant

But we can do without.
2023-04-11 15:59:43 +02:00
Benjamin Otte
9b749cee22 gtk: Add GtkScrollInfo
This struct carries information about scrolling a scrollable, so that
individual scrollables can share this struct for their scrolling APIs.
2023-04-11 15:59:43 +02:00
Benjamin Otte
cc5a644205 gsk: Add gsk_render_node_get_opaque()
Gets a rectangle inside the rendernode that is opaque.

For now, this is private API, I'm not sure yet if it should expose
regions or maybe just be TRUE/FALSE.
2023-04-11 15:59:43 +02:00
Benjamin Otte
2867a3bfe0 gtk-demo: Change the settings demo
Now it displays a long list of all settings with sections and allows
filtering the list to quickly search keys.

We might want to keep the old example, too, maybe?
2023-04-11 15:59:43 +02:00
Benjamin Otte
af56cb8a75 theme: Add some random CSS for section headers 2023-04-11 15:59:43 +02:00
Benjamin Otte
0a26820081 listitemmanager: Create header items for sections
Now, finally, listitemmanager has all the necessary support to make
section headers work.
2023-04-11 15:59:43 +02:00
Benjamin Otte
8c32cb7e88 listview: GtkListHeader and gtk_list_view_set_header_factory()
Adds infrastructure for handling factories for list headers.

For now, listitemmanager doesn't instantiate them, so they aren't used.
2023-04-11 15:59:43 +02:00
Benjamin Otte
34106bdda8 listitemmanager: Add GtkListItemChange
... for tracking widgets during changes.

This just pulls all the different disjointed parts into one struct with
a sensible API.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c474e0ac92 listitemmanager: Add GtkListHeaderBase and vfuncs
This is plumbing for section handling.

The vfuncs don't do anything, this is just the infrastructure.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c93eaa49b5 listitembase: exit early
When update() is called but no values are changed, exit early.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c25d0bbac3 listitemmanager: Make sections configurable
Add a gtk_list_item_manager_set_has_sections() (default: FALSE) that
requires explicit turning on for the listitemmanager to gain section
support.
2023-04-11 15:59:43 +02:00
Benjamin Otte
3353a0836b listitemmanager: Add/remove sections as needed
When ensuring widgets, ensure that their section is known. This will
be relevant when we use section widgets.

Also ensure that sections that don't cover any widget get destroyed.
2023-04-11 15:59:43 +02:00
Benjamin Otte
29d7c42fee testsuite: assert widgets are in matched sections
All widgets that we display should have their sections known, so assert
that that is the case.
2023-04-11 15:59:43 +02:00
Benjamin Otte
176be81ede testsuite: Add a print function for the listitemmanager
This is very useful when debugging.
2023-04-11 15:59:43 +02:00
Benjamin Otte
95effedead listitemmanager: Add gtk_list_tile_get_next_skip()
... and gtk_list_tile_get_previous_skip() and use them.

Allows skipping over REMOVED and FILLER tiles which makes the code a lot
more readable.
2023-04-11 15:59:43 +02:00
Benjamin Otte
89cfc182a2 listitemmanager: Add gtk_list_tile_set_type() 2023-04-11 15:59:43 +02:00
Benjamin Otte
c51d82f444 testsuite: Add verbose output to listitemmanager test 2023-04-11 15:59:43 +02:00
Benjamin Otte
cd2c64aff9 listitemmanager: Properly handle sections during add/remove
We don't insert sections ourselves yet, but we handle the existing one
when items get added or removed.
2023-04-11 15:59:43 +02:00
Benjamin Otte
5f6796b83f listitemmanager: Augment header/footer info 2023-04-11 15:59:43 +02:00
Benjamin Otte
77f3cd00a9 listitemmanager: Add section tiles
For now, we just have a HEADER at the start and a FOOTER at the end.
That's hard enough to get right.
2023-04-11 15:59:43 +02:00
Benjamin Otte
a1f41485f8 testsuite: Improve listitemmanager test
It now has trackers!
2023-04-11 15:59:43 +02:00
Benjamin Otte
5afda9e732 listitemmanager: Add tile types
We have a FILLER and a REMOVED type now.

Also makes gc() more sensitive to types.
2023-04-11 15:59:43 +02:00
Benjamin Otte
d181fb11a1 listitemmanager: Add a type to tiles
There's only one type for now, but hey, you have to start somewhere.
2023-04-11 15:59:43 +02:00
Benjamin Otte
37340cbebe testsuite: Add tests for sections to filterlistmodel 2023-04-11 15:59:43 +02:00
Matthias Clasen
38cf1e1895 Add a test for filterlistmodel sections 2023-04-11 15:59:43 +02:00
Matthias Clasen
d93e276f3f filterlistmodel: Support sections
Propagate sections from the child model to
the filter model.
2023-04-11 15:59:43 +02:00
Benjamin Otte
778321cb7a sortlistmodel: add a fast path for get_section() 2023-04-11 15:59:43 +02:00
Benjamin Otte
2259109e32 testsuite: Add section tests to sortlistmodel test 2023-04-11 15:59:43 +02:00
Matthias Clasen
5ceb4c0815 Add a test for sortlistmodel sections 2023-04-11 15:59:43 +02:00
Benjamin Otte
3960098e72 sortlistmodel: Implement GtkSectionModel
The get_section() implementation is a slow and steady implementation
that has to be careful to not screw up when an incremental sort is only
partially sorted.
2023-04-11 15:59:29 +02:00
Benjamin Otte
849df30b50 flattenlistmodel: Implement GtkSectionModel
Each child model is reported as one section.
2023-04-11 13:37:32 +02:00
Benjamin Otte
f886f8e677 Implement GtkSectionModel for all selection models 2023-04-11 13:37:32 +02:00
Benjamin Otte
74c55de53d Add GtkSectionModel
Prototyping the interface to be used for sections in listview, so people
can review and play with it.
2023-04-11 13:37:32 +02:00
Benjamin Otte
65a631a561 testsuite: Add a listitemmanager test 2023-04-11 13:35:50 +02:00
56 changed files with 5267 additions and 753 deletions

View File

@@ -226,6 +226,9 @@
<file>svgpaintable.c</file>
<file>org.gtk.gtk4.NodeEditor.Devel.svg</file>
</gresource>
<gresource prefix="/scrollinfo">
<file>scrollinfo.ui</file>
</gresource>
<gresource prefix="/shortcuts">
<file>shortcuts.ui</file>
<file>shortcuts-builder.ui</file>
@@ -338,6 +341,7 @@
<file>read_more.c</file>
<file>rotated_text.c</file>
<file>scale.c</file>
<file>scrollinfo.c</file>
<file>search_entry.c</file>
<file>search_entry2.c</file>
<file>shadertoy.c</file>

View File

@@ -29,6 +29,7 @@ struct _SettingsKey
enum {
PROP_0,
PROP_NAME,
PROP_SETTINGS,
PROP_SUMMARY,
PROP_DESCRIPTION,
PROP_VALUE,
@@ -89,6 +90,10 @@ settings_key_get_property (GObject *object,
}
break;
case PROP_SETTINGS:
g_value_set_object (value, self->settings);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -118,6 +123,8 @@ settings_key_class_init (SettingsKeyClass *klass)
g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_NAME] =
g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_SETTINGS] =
g_param_spec_object ("settings", NULL, NULL, G_TYPE_SETTINGS, G_PARAM_READABLE);
properties[PROP_SUMMARY] =
g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_VALUE] =
@@ -147,6 +154,23 @@ settings_key_new (GSettings *settings,
return result;
}
static char *
settings_key_get_search_string (SettingsKey *self)
{
char *schema, *result;
g_object_get (self->settings, "schema-id", &schema, NULL);
result = g_strconcat (g_settings_schema_key_get_name (self->key), " ",
g_settings_schema_key_get_summary (self->key), " ",
schema,
NULL);
g_free (schema);
return result;
}
static void
item_value_changed (GtkEditableLabel *label,
GParamSpec *pspec,
@@ -203,28 +227,16 @@ strvcmp (gconstpointer p1,
return strcmp (*s1, *s2);
}
static GtkFilter *current_filter;
static gboolean
transform_settings_to_keys (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data)
static gpointer
map_settings_to_keys (gpointer item,
gpointer unused)
{
GtkTreeListRow *treelistrow;
GSettings *settings;
GSettings *settings = item;
GSettingsSchema *schema;
GListStore *store;
GtkSortListModel *sort_model;
GtkFilterListModel *filter_model;
GtkFilter *filter;
char **keys;
guint i;
treelistrow = g_value_get_object (from_value);
if (treelistrow == NULL)
return TRUE;
settings = gtk_tree_list_row_get_item (treelistrow);
g_object_get (settings, "settings-schema", &schema, NULL);
store = g_list_store_new (SETTINGS_TYPE_KEY);
@@ -244,16 +256,7 @@ transform_settings_to_keys (GBinding *binding,
g_settings_schema_unref (schema);
g_object_unref (settings);
sort_model = gtk_sort_list_model_new (G_LIST_MODEL (store),
g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (data))));
filter = GTK_FILTER (gtk_string_filter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name")));
g_set_object (&current_filter, filter);
filter_model = gtk_filter_list_model_new (G_LIST_MODEL (sort_model), filter);
g_value_take_object (to_value, gtk_no_selection_new (G_LIST_MODEL (filter_model)));
return TRUE;
return store;
}
static GListModel *
@@ -310,24 +313,11 @@ search_enabled (GtkSearchEntry *entry)
gtk_editable_set_text (GTK_EDITABLE (entry), "");
}
static void
search_changed (GtkSearchEntry *entry,
gpointer data)
{
const char *text = gtk_editable_get_text (GTK_EDITABLE (entry));
if (current_filter)
gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), text);
}
static void
stop_search (GtkSearchEntry *entry,
gpointer data)
{
gtk_editable_set_text (GTK_EDITABLE (entry), "");
if (current_filter)
gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), "");
}
static GtkWidget *window = NULL;
@@ -337,94 +327,53 @@ do_listview_settings (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *columnview;
GtkListView *listview;
GListModel *model;
GtkTreeListModel *treemodel;
GtkSingleSelection *selection;
GtkNoSelection *selection;
GtkBuilderScope *scope;
GtkBuilder *builder;
GtkColumnViewColumn *name_column;
GtkColumnViewColumn *type_column;
GtkColumnViewColumn *default_column;
GtkColumnViewColumn *summary_column;
GtkColumnViewColumn *description_column;
GtkSorter *sorter;
GActionGroup *actions;
GAction *action;
GError *error = NULL;
GtkFilter *filter;
g_type_ensure (SETTINGS_TYPE_KEY);
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback (scope, search_enabled);
gtk_builder_cscope_add_callback (scope, search_changed);
gtk_builder_cscope_add_callback (scope, stop_search);
gtk_builder_cscope_add_callback (scope, settings_key_get_search_string);
gtk_builder_cscope_add_callback (scope, item_value_changed);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
g_object_unref (scope);
gtk_builder_add_from_resource (builder, "/listview_settings/listview_settings.ui", NULL);
gtk_builder_add_from_resource (builder, "/listview_settings/listview_settings.ui", &error);
g_assert_no_error (error);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
listview = GTK_WIDGET (gtk_builder_get_object (builder, "listview"));
columnview = GTK_WIDGET (gtk_builder_get_object (builder, "columnview"));
type_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "type_column"));
default_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "default_column"));
summary_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "summary_column"));
description_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "description_column"));
actions = G_ACTION_GROUP (g_simple_action_group_new ());
action = G_ACTION (g_property_action_new ("show-type", type_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-default", default_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-summary", summary_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-description", description_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
gtk_widget_insert_action_group (columnview, "columnview", actions);
g_object_unref (actions);
listview = GTK_LIST_VIEW (gtk_builder_get_object (builder, "listview"));
filter = GTK_FILTER (gtk_builder_get_object (builder, "filter"));
model = create_settings_model (NULL, NULL);
treemodel = gtk_tree_list_model_new (model,
FALSE,
TRUE,
TRUE,
create_settings_model,
NULL,
NULL);
selection = gtk_single_selection_new (G_LIST_MODEL (treemodel));
g_object_bind_property_full (selection, "selected-item",
columnview, "model",
G_BINDING_SYNC_CREATE,
transform_settings_to_keys,
NULL,
columnview, NULL);
model = G_LIST_MODEL (gtk_map_list_model_new (G_LIST_MODEL (treemodel), map_settings_to_keys, NULL, NULL));
model = G_LIST_MODEL (gtk_flatten_list_model_new (model));
model = G_LIST_MODEL (gtk_filter_list_model_new (model, g_object_ref (filter)));
selection = gtk_no_selection_new (model);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), GTK_SELECTION_MODEL (selection));
g_object_unref (selection);
name_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "name_column"));
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name")));
gtk_column_view_column_set_sorter (name_column, sorter);
g_object_unref (sorter);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "type")));
gtk_column_view_column_set_sorter (type_column, sorter);
g_object_unref (sorter);
g_object_unref (builder);
}

View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkStringFilter" id="filter">
<property name="expression">
<closure type="gchararray" function="settings_key_get_search_string" />
</property>
<property name="search" bind-source="entry" bind-property="text" />
</object>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Settings</property>
<property name="default-width">640</property>
@@ -14,15 +20,26 @@
</object>
</child>
<child>
<object class="GtkPaned">
<property name="position">300</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional"/>
<signal name="notify::search-mode-enabled" handler="search_enabled" object="entry"/>
<child>
<object class="GtkSearchEntry" id="entry">
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="GtkListView" id="listview">
<property name="tab-behavior">item</property>
<property name="vexpand">1</property>
<style>
<class name="navigation-sidebar"/>
<class name="rich-list"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
@@ -31,20 +48,71 @@
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkTreeExpander" id="expander">
<binding name="list-row">
<lookup name="item">GtkListItem</lookup>
</binding>
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="schema" type="GSettings">
<lookup name="item">expander</lookup>
<object class="GtkBox">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<style>
<class name="dim-label"/>
</style>
<property name="xalign">0</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="summary" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</child>
<child>
<object class="GtkEntry">
<property name="hexpand">1</property>
<property name="halign">end</property>
<binding name="text">
<lookup name="value" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<signal name="notify::label" handler="item_value_changed"/>
</object>
</property>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<property name="header-factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListHeader">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="schema" type="GSettings">
<lookup name="settings" type="SettingsKey">
<lookup name="item">GtkListHeader</lookup>
</lookup>
</lookup>
</binding>
</object>
</property>
</template>
@@ -56,232 +124,7 @@
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional"/>
<signal name="notify::search-mode-enabled" handler="search_enabled" object="entry"/>
<child>
<object class="GtkSearchEntry" id="entry">
<signal name="search-changed" handler="search_changed"/>
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkColumnView" id="columnview">
<property name="tab-behavior">cell</property>
<style>
<class name="data-table"/>
</style>
<child>
<object class="GtkColumnViewColumn" id="name_column">
<property name="title">Name</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title">Value</property>
<property name="resizable">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkEditableLabel">
<binding name="text">
<lookup name="value" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
<signal name="notify::label" handler="item_value_changed"/>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="type_column">
<property name="title">Type</property>
<property name="resizable">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="type" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="default_column">
<property name="title">Default</property>
<property name="resizable">1</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="default-value" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="summary_column">
<property name="title">Summary</property>
<property name="resizable">1</property>
<property name="visible">0</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="wrap">1</property>
<binding name="label">
<lookup name="summary" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="description_column">
<property name="title">Description</property>
<property name="resizable">1</property>
<property name="visible">0</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="wrap">1</property>
<binding name="label">
<lookup name="description" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<menu id="header_menu">
<section>
<item>
<attribute name="label" translatable="yes">Type</attribute>
<attribute name="action">columnview.show-type</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Default value</attribute>
<attribute name="action">columnview.show-default</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Summary</attribute>
<attribute name="action">columnview.show-summary</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Description</attribute>
<attribute name="action">columnview.show-description</attribute>
</item>
</section>
</menu>
</interface>

View File

@@ -78,6 +78,7 @@ demos = files([
'revealer.c',
'rotated_text.c',
'scale.c',
'scrollinfo.c',
'search_entry.c',
'search_entry2.c',
'shadertoy.c',

View File

@@ -0,0 +1,91 @@
/* ScrollInfo
*
* GtkScrollInfo allows you to pass scrolling information to many
* scrollable widgets. This allows for more detailed control of
* scrolling behavior.
*/
#include <gtk/gtk.h>
static GtkScrollInfo *scroll;
static void
viewport_x_width_changed (GtkAdjustment *x,
GtkAdjustment *width)
{
graphene_rect_t viewport = *gtk_scroll_info_get_viewport (scroll);
viewport.origin.x = gtk_adjustment_get_value (x);
viewport.size.width = MIN (gtk_adjustment_get_value (width), 1.0 - viewport.origin.x);
gtk_scroll_info_set_viewport (scroll, &viewport);
}
static void
viewport_y_height_changed (GtkAdjustment *y,
GtkAdjustment *height)
{
graphene_rect_t viewport = *gtk_scroll_info_get_viewport (scroll);
viewport.origin.y = gtk_adjustment_get_value (y);
viewport.size.height = MIN (gtk_adjustment_get_value (height), 1.0 - viewport.origin.y);
gtk_scroll_info_set_viewport (scroll, &viewport);
}
static void
enabled_changed (GtkCheckButton *horizontal,
GParamSpec *unused,
GtkCheckButton *vertical)
{
gtk_scroll_info_set_enable_horizontal (scroll, gtk_check_button_get_active (horizontal));
gtk_scroll_info_set_enable_vertical (scroll, gtk_check_button_get_active (vertical));
}
static void
do_scroll (GtkButton *button,
GtkWidget *widget)
{
GtkWidget *viewport = gtk_widget_get_parent (gtk_widget_get_parent (widget));
gtk_viewport_scroll_to (GTK_VIEWPORT (viewport),
widget,
scroll);
}
GtkWidget *
do_scrollinfo (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilderScope *scope;
GtkBuilder *builder;
GError *error = NULL;
scroll = gtk_scroll_info_new ();
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback (scope, viewport_x_width_changed);
gtk_builder_cscope_add_callback (scope, viewport_y_height_changed);
gtk_builder_cscope_add_callback (scope, enabled_changed);
gtk_builder_cscope_add_callback (scope, do_scroll);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
if (!gtk_builder_add_from_resource (builder, "/scrollinfo/scrollinfo.ui", &error))
g_error ("%s", error->message);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
g_object_unref (builder);
g_object_unref (scope);
}
if (!gtk_widget_get_visible (window))
gtk_widget_set_visible (window, TRUE);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">ScrollInfo</property>
<property name="default-width">600</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkScrolledWindow">
<property name="min-content-width">200</property>
<property name="min-content-height">200</property>
<property name="child">
<object class="GtkViewport" id="viewport">
<style>
<class name="view"/>
</style>
<property name="child">
<object class="GtkFixed">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox">
<property name="width-request">1000</property>
<property name="height-request">1000</property>
</object>
</child>
<child>
<object class="GtkImage" id="face_cool">
<property name="icon-name">face-cool-symbolic</property>
<property name="icon-size">large</property>
<layout>
<property name="transform">translate(284,284)</property>
</layout>
</object>
</child>
<child>
<object class="GtkImage" id="face_smirk">
<property name="icon-name">face-smirk-symbolic</property>
<property name="icon-size">large</property>
<layout>
<property name="transform">translate(584,284)</property>
</layout>
</object>
</child>
<child>
<object class="GtkImage" id="face_wink">
<property name="icon-name">face-wink-symbolic</property>
<property name="icon-size">large</property>
<layout>
<property name="transform">translate(284,584)</property>
</layout>
</object>
</child>
<child>
<object class="GtkImage" id="face_laugh">
<property name="icon-name">face-laugh-symbolic</property>
<property name="icon-size">large</property>
<layout>
<property name="transform">translate(584,584)</property>
</layout>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<property name="margin-top">20</property>
<property name="margin-bottom">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<child>
<object class="GtkLabel">
<property name="label">Viewport X</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkScale">
<property name="width-request">200</property>
<property name="draw-value">1</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_x">
<property name="upper">1</property>
<property name="value">0</property>
<property name="step-increment">0.02</property>
<property name="page-increment">0.2</property>
<signal name="value-changed" handler="viewport_x_width_changed" object="viewport_width" swapped="no"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Viewport Y</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkScale">
<property name="width-request">200</property>
<property name="draw-value">1</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_y">
<property name="upper">1</property>
<property name="value">0</property>
<property name="step-increment">0.02</property>
<property name="page-increment">0.2</property>
<signal name="value-changed" handler="viewport_y_height_changed" object="viewport_height" swapped="no"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Viewport Width</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkScale">
<property name="width-request">200</property>
<property name="draw-value">1</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_width">
<property name="upper">1</property>
<property name="value">1</property>
<property name="step-increment">0.02</property>
<property name="page-increment">0.2</property>
<signal name="value-changed" handler="viewport_x_width_changed" object="viewport_x" swapped="yes"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Viewport Height</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkScale">
<property name="width-request">200</property>
<property name="draw-value">1</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_height">
<property name="upper">1</property>
<property name="value">1</property>
<property name="step-increment">0.02</property>
<property name="page-increment">0.2</property>
<signal name="value-changed" handler="viewport_y_height_changed" object="viewport_y" swapped="yes"/>
</object>
</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="enable_horizontal">
<property name="label">Scroll horizontally</property>
<property name="active">1</property>
<signal name="notify::active" handler="enabled_changed" object="enable_vertical" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckButton" id="enable_vertical">
<property name="label">Scroll vertically</property>
<property name="active">1</property>
<signal name="notify::active" handler="enabled_changed" object="enable_horizontal" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkButton">
<property name="icon-name">face-cool-symbolic</property>
<signal name="clicked" handler="do_scroll" object="face_cool" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton">
<property name="icon-name">face-smirk-symbolic</property>
<signal name="clicked" handler="do_scroll" object="face_smirk" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton">
<property name="icon-name">face-wink-symbolic</property>
<signal name="clicked" handler="do_scroll" object="face_wink" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton">
<property name="icon-name">face-laugh-symbolic</property>
<signal name="clicked" handler="do_scroll" object="face_laugh" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -801,7 +801,7 @@ suggestion_entry_key_pressed (GtkEventControllerKey *controller,
selected = matches - 1;
}
gtk_single_selection_set_selected (self->selection, selected);
gtk_list_view_scroll_to (GTK_LIST_VIEW (self->list), selected, GTK_LIST_SCROLL_SELECT, NULL);
return TRUE;
}

View File

@@ -599,8 +599,6 @@ _gdk_wayland_display_create_surface (GdkDisplay *display,
}
}
gdk_wayland_surface_create_wl_surface (surface);
g_signal_connect (frame_clock, "before-paint", G_CALLBACK (on_frame_clock_before_paint), surface);
g_signal_connect (frame_clock, "after-paint", G_CALLBACK (on_frame_clock_after_paint), surface);

View File

@@ -159,6 +159,13 @@ gsk_render_node_real_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_render_node_real_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
return FALSE;
}
static void
gsk_render_node_class_init (GskRenderNodeClass *klass)
{
@@ -167,6 +174,7 @@ gsk_render_node_class_init (GskRenderNodeClass *klass)
klass->draw = gsk_render_node_real_draw;
klass->can_diff = gsk_render_node_real_can_diff;
klass->diff = gsk_render_node_real_diff;
klass->get_opaque = gsk_render_node_real_get_opaque;
}
static void
@@ -501,6 +509,13 @@ gsk_render_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
gboolean
gsk_render_node_get_opaque (GskRenderNode *self,
graphene_rect_t *opaque)
{
return GSK_RENDER_NODE_GET_CLASS (self)->get_opaque (self, opaque);
}
/**
* gsk_render_node_write_to_file:
* @node: a `GskRenderNode`

View File

@@ -115,6 +115,19 @@ gsk_color_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_color_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskColorNode *self = (GskColorNode *) node;
if (!gdk_rgba_is_opaque (&self->color))
return FALSE;
*opaque = node->bounds;
return TRUE;
}
static void
gsk_color_node_class_init (gpointer g_class,
gpointer class_data)
@@ -125,6 +138,7 @@ gsk_color_node_class_init (gpointer g_class,
node_class->draw = gsk_color_node_draw;
node_class->diff = gsk_color_node_diff;
node_class->get_opaque = gsk_color_node_get_opaque;
}
/**
@@ -275,6 +289,23 @@ gsk_linear_gradient_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_linear_gradient_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
guint i;
for (i = 0; i < self->n_stops; i++)
{
if (!gdk_rgba_is_opaque (&self->stops[i].color))
return FALSE;
}
*opaque = node->bounds;
return TRUE;
}
static void
gsk_linear_gradient_node_class_init (gpointer g_class,
gpointer class_data)
@@ -286,6 +317,7 @@ gsk_linear_gradient_node_class_init (gpointer g_class,
node_class->finalize = gsk_linear_gradient_node_finalize;
node_class->draw = gsk_linear_gradient_node_draw;
node_class->diff = gsk_linear_gradient_node_diff;
node_class->get_opaque = gsk_linear_gradient_node_get_opaque;
}
static void
@@ -299,6 +331,7 @@ gsk_repeating_linear_gradient_node_class_init (gpointer g_class,
node_class->finalize = gsk_linear_gradient_node_finalize;
node_class->draw = gsk_linear_gradient_node_draw;
node_class->diff = gsk_linear_gradient_node_diff;
node_class->get_opaque = gsk_linear_gradient_node_get_opaque;
}
/**
@@ -591,6 +624,23 @@ gsk_radial_gradient_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_radial_gradient_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskRadialGradientNode *self = (GskRadialGradientNode *) node;
guint i;
for (i = 0; i < self->n_stops; i++)
{
if (!gdk_rgba_is_opaque (&self->stops[i].color))
return FALSE;
}
*opaque = node->bounds;
return TRUE;
}
static void
gsk_radial_gradient_node_class_init (gpointer g_class,
gpointer class_data)
@@ -615,6 +665,7 @@ gsk_repeating_radial_gradient_node_class_init (gpointer g_class,
node_class->finalize = gsk_radial_gradient_node_finalize;
node_class->draw = gsk_radial_gradient_node_draw;
node_class->diff = gsk_radial_gradient_node_diff;
node_class->get_opaque = gsk_radial_gradient_node_get_opaque;
}
/**
@@ -1077,6 +1128,23 @@ gsk_conic_gradient_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_conic_gradient_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskConicGradientNode *self = (GskConicGradientNode *) node;
guint i;
for (i = 0; i < self->n_stops; i++)
{
if (!gdk_rgba_is_opaque (&self->stops[i].color))
return FALSE;
}
*opaque = node->bounds;
return TRUE;
}
static void
gsk_conic_gradient_node_class_init (gpointer g_class,
gpointer class_data)
@@ -1088,6 +1156,7 @@ gsk_conic_gradient_node_class_init (gpointer g_class,
node_class->finalize = gsk_conic_gradient_node_finalize;
node_class->draw = gsk_conic_gradient_node_draw;
node_class->diff = gsk_conic_gradient_node_diff;
node_class->get_opaque = gsk_conic_gradient_node_get_opaque;
}
/**
@@ -2644,7 +2713,7 @@ gsk_outset_shadow_node_diff (GskRenderNode *node1,
static void
gsk_outset_shadow_node_class_init (gpointer g_class,
gpointer class_data)
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
@@ -2841,6 +2910,19 @@ gsk_cairo_node_draw (GskRenderNode *node,
cairo_paint (cr);
}
static gboolean
gsk_cairo_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskCairoNode *self = (GskCairoNode *) node;
if (cairo_surface_get_content (self->surface) & CAIRO_CONTENT_ALPHA)
return FALSE;
*opaque = node->bounds;
return TRUE;
}
static void
gsk_cairo_node_class_init (gpointer g_class,
gpointer class_data)
@@ -2851,6 +2933,7 @@ gsk_cairo_node_class_init (gpointer g_class,
node_class->finalize = gsk_cairo_node_finalize;
node_class->draw = gsk_cairo_node_draw;
node_class->get_opaque = gsk_cairo_node_get_opaque;
}
/**
@@ -3092,6 +3175,51 @@ gsk_container_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_container_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskContainerNode *self = (GskContainerNode *) node;
graphene_rect_t child_opaque;
double size, child_size, desired_size;
guint i;
for (i = 0; i < self->n_children; i++)
{
if (gsk_render_node_get_opaque (self->children[i], opaque))
break;
}
if (i == self->n_children)
return FALSE;
size = opaque->size.width * opaque->size.height;
/* the 80% is random. I just want it to be low enough to catch
* rounded corners, but not so small it catches on to .view
* backgrounds for the smaller child of a paned.
*/
desired_size = node->bounds.size.width * node->bounds.size.height * 0.8;
for (i++; i < self->n_children; i++)
{
if (size >= desired_size)
break;
if (!gsk_render_node_get_opaque (self->children[i], &child_opaque))
continue;
child_size = child_opaque.size.width * child_opaque.size.height;
/* We allow == here because we want to find the topmost opaque child */
if (child_size < size)
continue;
*opaque = child_opaque;
size = child_size;
}
return TRUE;
}
static void
gsk_container_node_class_init (gpointer g_class,
gpointer class_data)
@@ -3103,6 +3231,7 @@ gsk_container_node_class_init (gpointer g_class,
node_class->finalize = gsk_container_node_finalize;
node_class->draw = gsk_container_node_draw;
node_class->diff = gsk_container_node_diff;
node_class->get_opaque = gsk_container_node_get_opaque;
}
/**
@@ -3365,6 +3494,37 @@ gsk_transform_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_transform_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskTransformNode *self = (GskTransformNode *) node;
graphene_rect_t child_opaque;
switch (gsk_transform_get_category (self->transform))
{
case GSK_TRANSFORM_CATEGORY_IDENTITY:
case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
break;
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
case GSK_TRANSFORM_CATEGORY_ANY:
case GSK_TRANSFORM_CATEGORY_3D:
case GSK_TRANSFORM_CATEGORY_2D:
default:
return FALSE;
}
if (!gsk_render_node_get_opaque (self->child, &child_opaque))
return FALSE;
gsk_transform_transform_bounds (self->transform,
&child_opaque,
opaque);
return TRUE;
}
static void
gsk_transform_node_class_init (gpointer g_class,
gpointer class_data)
@@ -3377,6 +3537,7 @@ gsk_transform_node_class_init (gpointer g_class,
node_class->draw = gsk_transform_node_draw;
node_class->can_diff = gsk_transform_node_can_diff;
node_class->diff = gsk_transform_node_diff;
node_class->get_opaque = gsk_transform_node_get_opaque;
}
/**
@@ -4076,9 +4237,22 @@ gsk_clip_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_clip_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskClipNode *self = (GskClipNode *) node;
graphene_rect_t child_opaque;
if (!gsk_render_node_get_opaque (self->child, &child_opaque))
return FALSE;
return graphene_rect_intersection (&self->clip, &child_opaque, opaque);
}
static void
gsk_clip_node_class_init (gpointer g_class,
gpointer class_data)
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
@@ -4087,6 +4261,7 @@ gsk_clip_node_class_init (gpointer g_class,
node_class->finalize = gsk_clip_node_finalize;
node_class->draw = gsk_clip_node_draw;
node_class->diff = gsk_clip_node_diff;
node_class->get_opaque = gsk_clip_node_get_opaque;
}
/**
@@ -4224,6 +4399,39 @@ gsk_rounded_clip_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_rounded_clip_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
graphene_rect_t child_opaque, wide_opaque, high_opaque;
double start, end;
if (!gsk_render_node_get_opaque (self->child, &child_opaque))
return FALSE;
wide_opaque = self->clip.bounds;
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].height, self->clip.corner[GSK_CORNER_TOP_RIGHT].height);
end = MAX(self->clip.corner[GSK_CORNER_BOTTOM_LEFT].height, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].height);
wide_opaque.size.height -= MIN (wide_opaque.size.height, start + end);
wide_opaque.origin.y += start;
graphene_rect_intersection (&wide_opaque, &child_opaque, &wide_opaque);
high_opaque = self->clip.bounds;
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].width, self->clip.corner[GSK_CORNER_BOTTOM_LEFT].width);
end = MAX(self->clip.corner[GSK_CORNER_TOP_RIGHT].width, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].width);
high_opaque.size.width -= MIN (high_opaque.size.width, start + end);
high_opaque.origin.x += start;
graphene_rect_intersection (&high_opaque, &child_opaque, &high_opaque);
if (wide_opaque.size.width * wide_opaque.size.height > high_opaque.size.width * high_opaque.size.height)
*opaque = wide_opaque;
else
*opaque = high_opaque;
return TRUE;
}
static void
gsk_rounded_clip_node_class_init (gpointer g_class,
gpointer class_data)
@@ -4235,6 +4443,7 @@ gsk_rounded_clip_node_class_init (gpointer g_class,
node_class->finalize = gsk_rounded_clip_node_finalize;
node_class->draw = gsk_rounded_clip_node_draw;
node_class->diff = gsk_rounded_clip_node_diff;
node_class->get_opaque = gsk_rounded_clip_node_get_opaque;
}
/**
@@ -4832,6 +5041,20 @@ gsk_cross_fade_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
static gboolean
gsk_cross_fade_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
graphene_rect_t start_opaque, end_opaque;
if (!gsk_render_node_get_opaque (self->start, &start_opaque) ||
!gsk_render_node_get_opaque (self->end, &end_opaque))
return FALSE;
return graphene_rect_intersection (&start_opaque, &end_opaque, opaque);
}
static void
gsk_cross_fade_node_class_init (gpointer g_class,
gpointer class_data)
@@ -4843,6 +5066,7 @@ gsk_cross_fade_node_class_init (gpointer g_class,
node_class->finalize = gsk_cross_fade_node_finalize;
node_class->draw = gsk_cross_fade_node_draw;
node_class->diff = gsk_cross_fade_node_diff;
node_class->get_opaque = gsk_cross_fade_node_get_opaque;
}
/**
@@ -5807,6 +6031,15 @@ gsk_debug_node_diff (GskRenderNode *node1,
gsk_render_node_diff (self1->child, self2->child, region);
}
static gboolean
gsk_debug_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskDebugNode *self = (GskDebugNode *) node;
return gsk_render_node_get_opaque (self->child, opaque);
}
static void
gsk_debug_node_class_init (gpointer g_class,
gpointer class_data)
@@ -5819,6 +6052,7 @@ gsk_debug_node_class_init (gpointer g_class,
node_class->draw = gsk_debug_node_draw;
node_class->can_diff = gsk_debug_node_can_diff;
node_class->diff = gsk_debug_node_diff;
node_class->get_opaque = gsk_debug_node_get_opaque;
}
/**

View File

@@ -45,6 +45,8 @@ struct _GskRenderNodeClass
void (* diff) (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region);
gboolean (* get_opaque) (GskRenderNode *node,
graphene_rect_t*opaque);
};
void gsk_render_node_init_types (void);
@@ -67,6 +69,9 @@ void gsk_container_node_diff_with (GskRenderNode
GskRenderNode *other,
cairo_region_t *region);
gboolean gsk_render_node_get_opaque (GskRenderNode *node,
graphene_rect_t *opaque);
bool gsk_border_node_get_uniform (const GskRenderNode *self);
bool gsk_border_node_get_uniform_color (const GskRenderNode *self);

View File

@@ -174,6 +174,7 @@
#include <gtk/gtklistbase.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistheader.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtklistitemfactory.h>
#include <gtk/deprecated/gtkliststore.h>
@@ -222,9 +223,11 @@
#include <gtk/gtkscalebutton.h>
#include <gtk/gtkscrollable.h>
#include <gtk/gtkscrollbar.h>
#include <gtk/gtkscrollinfo.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtksearchbar.h>
#include <gtk/gtksearchentry.h>
#include <gtk/gtksectionmodel.h>
#include <gtk/gtkselectionfiltermodel.h>
#include <gtk/gtkselectionmodel.h>
#include <gtk/gtkseparator.h>

View File

@@ -2106,3 +2106,42 @@ gtk_column_view_get_tab_behavior (GtkColumnView *self)
return gtk_list_view_get_tab_behavior (self->listview);
}
/**
* gtk_column_view_scroll_to:
* @self: The listview to scroll in
* @pos: position of the item
* @column: (nullable) (transfer none): The column to scroll in
* or %NULL to not scroll columns.
* @flags: actions to perform
* @scroll: (nullable) (transfer full): details of how to perform
* the scroll operation or NULL to scroll into view
*
* Scroll to the row at the given position - or cell if a column is
* given - and performs the actions specified in @flags.
*
* This function works if the columnview is not shown or not focused,
* the changes will take effect once that happens.
*
* Since: 4.12
*/
void
gtk_column_view_scroll_to (GtkColumnView *self,
guint pos,
GtkColumnViewColumn *column,
GtkListScrollFlags flags,
GtkScrollInfo *scroll)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
g_return_if_fail (column == NULL || GTK_IS_COLUMN_VIEW_COLUMN (column));
if (column)
{
g_return_if_fail (gtk_column_view_column_get_column_view (column) == self);
}
gtk_list_view_scroll_to (self->listview, pos, flags, scroll);
if (column)
{
g_warning ("FIXME: column scrolling is not implemented.");
}
}

View File

@@ -122,5 +122,12 @@ GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_column_view_get_row_factory (GtkColumnView *self);
GDK_AVAILABLE_IN_4_12
void gtk_column_view_scroll_to (GtkColumnView *self,
guint pos,
GtkColumnViewColumn *column,
GtkListScrollFlags flags,
GtkScrollInfo *scroll);
G_END_DECLS

View File

@@ -289,6 +289,23 @@ typedef enum
GTK_LIST_TAB_CELL
} GtkListTabBehavior;
/**
* GtkListScrollFlags:
* @GTK_LIST_SCROLL_FOCUS: Focus the target item
* @GTK_LIST_SCROLL_SELECT: Select the target item and
* unselect all other items.
* @GTK_LIST_SCROLL_NO_SCROLL: Do not actually scroll,
* just perform actions from other flags.
*
* List of actions to perform when scrolling to items in
* a list widget.
*/
typedef enum {
GTK_LIST_SCROLL_FOCUS = 1 << 0,
GTK_LIST_SCROLL_SELECT = 1 << 1,
GTK_LIST_SCROLL_NO_SCROLL = 1 << 2,
} GtkListScrollFlags;
/**
* GtkMessageType:
* @GTK_MESSAGE_INFO: Informational message

View File

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

View File

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

View File

@@ -886,7 +886,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
{
GtkListTile *filler;
tile = gtk_list_item_manager_get_last (self->item_manager);
filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items);
filler = gtk_list_tile_append_filler (self->item_manager, tile);
gtk_list_tile_set_area_position (self->item_manager,
filler,
column_start (self, xspacing, i),
@@ -1568,3 +1568,29 @@ gtk_grid_view_get_tab_behavior (GtkGridView *self)
return gtk_list_base_get_tab_behavior (GTK_LIST_BASE (self));
}
/**
* gtk_grid_view_scroll_to:
* @self: The gridview to scroll in
* @pos: position of the item
* @flags: actions to perform
* @scroll: (nullable) (transfer full): details of how to perform
* the scroll operation or `NULL` to scroll into view
*
* Scroll to the item at the given position and performs the actions
* specified in @flags.
*
* This function works if the listview is not shown or not focused,
* the changes will take effect once that happens.
*
* Since: 4.12
*/
void
gtk_grid_view_scroll_to (GtkGridView *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll)
{
g_return_if_fail (GTK_IS_GRID_VIEW (self));
gtk_list_base_scroll_to (GTK_LIST_BASE (self), pos, flags, scroll);
}

View File

@@ -85,6 +85,13 @@ void gtk_grid_view_set_single_click_activate (GtkGridView
GDK_AVAILABLE_IN_ALL
gboolean gtk_grid_view_get_single_click_activate (GtkGridView *self);
GDK_AVAILABLE_IN_4_12
void gtk_grid_view_scroll_to (GtkGridView *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkGridView, g_object_unref)
G_END_DECLS

View File

@@ -34,11 +34,14 @@
#include "gtkmultiselection.h"
#include "gtkorientable.h"
#include "gtkscrollable.h"
#include "gtkscrollinfo.h"
#include "gtksingleselection.h"
#include "gtksnapshot.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include <gdk/gdkrgbaprivate.h>
/* Allow shadows to overdraw without immediately culling the widget at the viewport
* boundary.
* Choose this so that roughly 1 extra widget gets drawn on each side of the viewport,
@@ -523,7 +526,7 @@ gtk_list_base_get_n_items (GtkListBase *self)
return g_list_model_get_n_items (G_LIST_MODEL (priv->model));
}
guint
static guint
gtk_list_base_get_focus_position (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
@@ -814,22 +817,19 @@ gtk_list_base_set_property (GObject *object,
static void
gtk_list_base_compute_scroll_align (GtkListBase *self,
GtkOrientation orientation,
int cell_start,
int cell_end,
int cell_size,
int visible_start,
int visible_size,
double current_align,
GtkPackType current_side,
double *new_align,
GtkPackType *new_side)
{
int visible_start, visible_size, visible_end;
int cell_size;
int cell_end, visible_end;
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
orientation,
&visible_start, NULL, &visible_size);
visible_end = visible_start + visible_size;
cell_size = cell_end - cell_start;
cell_end = cell_start + cell_size;
if (cell_size <= visible_size)
{
@@ -873,26 +873,40 @@ gtk_list_base_compute_scroll_align (GtkListBase *self,
}
static void
gtk_list_base_scroll_to_item (GtkListBase *self,
guint pos)
gtk_list_base_scroll_to_item (GtkListBase *self,
guint pos,
GtkScrollInfo *scroll)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double align_along, align_across;
GtkPackType side_along, side_across;
GdkRectangle area;
GdkRectangle area, viewport;
int x, y;
if (!gtk_list_base_get_allocation (GTK_LIST_BASE (self), pos, &area))
return;
{
g_clear_pointer (&scroll, gtk_scroll_info_unref);
return;
}
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
&viewport.y, NULL, &viewport.height);
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
&viewport.x, NULL, &viewport.width);
gtk_scroll_info_compute_scroll (scroll, &area, &viewport, &x, &y);
gtk_list_base_compute_scroll_align (self,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
area.y, area.y + area.height,
area.y, area.height,
y, viewport.height,
priv->anchor_align_along, priv->anchor_side_along,
&align_along, &side_along);
gtk_list_base_compute_scroll_align (self,
gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
area.x, area.x + area.width,
area.x, area.width,
x, viewport.width,
priv->anchor_align_across, priv->anchor_side_across,
&align_across, &side_across);
@@ -900,6 +914,8 @@ gtk_list_base_scroll_to_item (GtkListBase *self,
pos,
align_across, side_across,
align_along, side_along);
g_clear_pointer (&scroll, gtk_scroll_info_unref);
}
static void
@@ -915,7 +931,7 @@ gtk_list_base_scroll_to_item_action (GtkWidget *widget,
g_variant_get (parameter, "u", &pos);
gtk_list_base_scroll_to_item (self, pos);
gtk_list_base_scroll_to_item (self, pos, NULL);
}
static void
@@ -935,7 +951,7 @@ gtk_list_base_set_focus_child (GtkWidget *widget,
if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus))
{
gtk_list_base_scroll_to_item (self, pos);
gtk_list_base_scroll_to_item (self, pos, NULL);
gtk_list_item_tracker_set_position (priv->item_manager,
priv->focus,
pos,
@@ -1137,6 +1153,120 @@ gtk_list_base_move_cursor (GtkWidget *widget,
return TRUE;
}
static GskRenderNode *
gtk_list_base_dump_tiles (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkSnapshot *snapshot;
GtkListTile *tile;
cairo_rectangle_int_t viewport;
guint i, focus, anchor, selected;
PangoLayout *layout;
char *s;
focus = gtk_list_base_get_focus_position (self);
anchor = gtk_list_base_get_anchor (self);
selected = gtk_list_item_tracker_get_position (priv->item_manager, priv->selected);
snapshot = gtk_snapshot_new ();
i = 0;
for (tile = gtk_list_item_manager_get_first (priv->item_manager);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
{
GdkRGBA color;
if (i == focus)
color = GDK_RGBA("00FF00");
else if (i == anchor)
color = GDK_RGBA("FFFF00");
else if (i == selected)
color = GDK_RGBA("0000FF");
else
color = GDK_RGBA("FFFFFF");
gtk_snapshot_append_color (snapshot,
&color,
&GRAPHENE_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
));
/* This should really look at the ListItem */
s = g_strdup_printf ("%u", i);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), s);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (tile->area.x + 2, tile->area.y + 2));
gtk_snapshot_append_layout (snapshot, layout, &GDK_RGBA("000000"));
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- tile->area.x - 2, - tile->area.y - 2));
g_object_unref (layout);
g_free (s);
}
else
{
GdkRGBA color;
if (tile->n_items == 0)
color = GDK_RGBA("A07070");
else
color = GDK_RGBA("808080");
gtk_snapshot_append_color (snapshot,
&color,
&GRAPHENE_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
));
}
gtk_snapshot_append_border (snapshot,
&GSK_ROUNDED_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
),
(float[4]) { 1, 1, 1, 1 },
(GdkRGBA[4]) { GDK_RGBA("000000"), GDK_RGBA("000000"), GDK_RGBA("000000"), GDK_RGBA("000000") });
i += tile->n_items;
}
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
&viewport.y, NULL, &viewport.height);
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
&viewport.x, NULL, &viewport.width);
gtk_snapshot_append_color (snapshot,
&GDK_RGBA("0000F040"),
&GRAPHENE_RECT_INIT(
viewport.x, viewport.y,
viewport.width, viewport.height
));
return gtk_snapshot_free_to_node (snapshot);
}
static gboolean
gtk_list_base_copy_tiles_to_clipboard (GtkWidget *widget,
GVariant *args,
gpointer unused)
{
GtkListBase *self = GTK_LIST_BASE (widget);
GskRenderNode *node;
node = gtk_list_base_dump_tiles (self);
if (node == NULL)
return TRUE;
gdk_clipboard_set (gtk_widget_get_clipboard (widget),
GSK_TYPE_RENDER_NODE,
node);
gsk_render_node_unref (node);
return TRUE;
}
static void
gtk_list_base_add_move_binding (GtkWidgetClass *widget_class,
guint keyval,
@@ -1315,6 +1445,8 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding (widget_class, GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, gtk_list_base_copy_tiles_to_clipboard, NULL);
}
static gboolean
@@ -1954,11 +2086,25 @@ gtk_list_base_split_func (GtkWidget *widget,
}
static GtkListItemBase *
gtk_list_base_create_widget_func (GtkWidget *widget)
gtk_list_base_create_list_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_list_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_prepare_section_func (GtkWidget *widget,
GtkListTile *tile,
guint pos)
{
GTK_LIST_BASE_GET_CLASS (widget)->prepare_section (GTK_LIST_BASE (widget), tile, pos);
}
static GtkListHeaderBase *
gtk_list_base_create_header_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_header_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_init_real (GtkListBase *self,
GtkListBaseClass *g_class)
@@ -1968,7 +2114,9 @@ gtk_list_base_init_real (GtkListBase *self,
priv->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self),
gtk_list_base_split_func,
gtk_list_base_create_widget_func);
gtk_list_base_create_list_widget_func,
gtk_list_base_prepare_section_func,
gtk_list_base_create_header_widget_func);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
priv->anchor_side_along = GTK_PACK_START;
priv->anchor_side_across = GTK_PACK_START;
@@ -2299,3 +2447,43 @@ gtk_list_base_get_tab_behavior (GtkListBase *self)
return priv->tab_behavior;
}
void
gtk_list_base_scroll_to (GtkListBase *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (flags & GTK_LIST_SCROLL_FOCUS)
{
GtkListItemTracker *old_focus;
/* We need a tracker here to keep the focus widget around,
* because we need to update the focus tracker before grabbing
* focus, becuase otherwise gtk_list_base_set_focus_child() will
* scroll to the item, and we want to avoid that.
*/
old_focus = gtk_list_item_tracker_new (priv->item_manager);
gtk_list_item_tracker_set_position (priv->item_manager, old_focus, gtk_list_base_get_focus_position (self), 0, 0);
gtk_list_item_tracker_set_position (priv->item_manager, priv->focus, pos, 0, 0);
/* XXX: Is this the proper check? */
if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_FOCUS_WITHIN)
{
GtkListTile *tile = gtk_list_item_manager_get_nth (priv->item_manager, pos, NULL);
gtk_widget_grab_focus (tile->widget);
}
gtk_list_item_tracker_free (priv->item_manager, old_focus);
}
if (flags & GTK_LIST_SCROLL_SELECT)
gtk_list_base_select_item (self, pos, FALSE, FALSE);
if (!(flags & GTK_LIST_SCROLL_NO_SCROLL))
gtk_list_base_scroll_to_item (self, pos, scroll);
}

View File

@@ -37,6 +37,10 @@ struct _GtkListBaseClass
GtkListTile *tile,
guint n_items);
GtkListItemBase * (* create_list_widget) (GtkListBase *self);
void (* prepare_section) (GtkListBase *self,
GtkListTile *tile,
guint position);
GtkListHeaderBase * (* create_header_widget) (GtkListBase *self);
gboolean (* get_allocation) (GtkListBase *self,
guint pos,
@@ -58,7 +62,6 @@ 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);
void gtk_list_base_get_border_spacing (GtkListBase *self,
int *xspacing,
int *yspacing);
@@ -91,3 +94,7 @@ GtkListTabBehavior gtk_list_base_get_tab_behavior (GtkListBase
void gtk_list_base_allocate (GtkListBase *self);
void gtk_list_base_scroll_to (GtkListBase *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll);

379
gtk/gtklistheader.c Normal file
View File

@@ -0,0 +1,379 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderprivate.h"
/**
* GtkListHeader:
*
* `GtkListHeader` is used by list widgets to represent the headers they
* display.
*
* The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via
* their factory, but provide a different set of properties suitable for
* managing the header instead of individual items.
*
* Since: 4.12
*/
enum
{
PROP_0,
PROP_CHILD,
PROP_END,
PROP_ITEM,
PROP_N_ITEMS,
PROP_START,
N_PROPS
};
G_DEFINE_TYPE (GtkListHeader, gtk_list_header, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_list_header_dispose (GObject *object)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
g_assert (self->owner == NULL); /* would hold a reference */
g_clear_object (&self->child);
G_OBJECT_CLASS (gtk_list_header_parent_class)->dispose (object);
}
static void
gtk_list_header_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
switch (property_id)
{
case PROP_CHILD:
g_value_set_object (value, self->child);
break;
case PROP_END:
if (self->owner)
g_value_set_uint (value, gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)));
else
g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
break;
case PROP_ITEM:
if (self->owner)
g_value_set_object (value, gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner)));
break;
case PROP_N_ITEMS:
g_value_set_uint (value, gtk_list_header_get_n_items (self));
break;
case PROP_START:
if (self->owner)
g_value_set_uint (value, gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner)));
else
g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
switch (property_id)
{
case PROP_CHILD:
gtk_list_header_set_child (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_class_init (GtkListHeaderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_list_header_dispose;
gobject_class->get_property = gtk_list_header_get_property;
gobject_class->set_property = gtk_list_header_set_property;
/**
* GtkListHeader:child: (attributes org.gtk.Property.get=gtk_list_header_get_child org.gtk.Property.set=gtk_list_header_set_child)
*
* Widget used for display.
*
* Since: 4.12
*/
properties[PROP_CHILD] =
g_param_spec_object ("child", NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:end: (attributes org.gtk.Property.get=gtk_list_header_get_end)
*
* The first position no longer part of this section.
*
* Since: 4.12
*/
properties[PROP_END] =
g_param_spec_uint ("end", NULL, NULL,
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:item: (attributes org.gtk.Property.get=gtk_list_header_get_item)
*
* Displayed item.
*
* Since: 4.12
*/
properties[PROP_ITEM] =
g_param_spec_object ("item", NULL, NULL,
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:n-items: (attributes org.gtk.Property.get=gtk_list_header_get_n_items)
*
* Number of items in this section.
*
* Since: 4.12
*/
properties[PROP_N_ITEMS] =
g_param_spec_uint ("n-items", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:start: (attributes org.gtk.Property.get=gtk_list_header_get_start)
*
* First position of items in this section.
*
* Since: 4.12
*/
properties[PROP_START] =
g_param_spec_uint ("start", NULL, NULL,
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_list_header_init (GtkListHeader *self)
{
}
GtkListHeader *
gtk_list_header_new (void)
{
return g_object_new (GTK_TYPE_LIST_HEADER, NULL);
}
void
gtk_list_header_do_notify (GtkListHeader *list_header,
gboolean notify_item,
gboolean notify_start,
gboolean notify_end,
gboolean notify_n_items)
{
GObject *object = G_OBJECT (list_header);
if (notify_item)
g_object_notify_by_pspec (object, properties[PROP_ITEM]);
if (notify_start)
g_object_notify_by_pspec (object, properties[PROP_START]);
if (notify_end)
g_object_notify_by_pspec (object, properties[PROP_END]);
if (notify_n_items)
g_object_notify_by_pspec (object, properties[PROP_N_ITEMS]);
}
/**
* gtk_list_header_get_item: (attributes org.gtk.Method.get_property=item)
* @self: a `GtkListHeader`
*
* Gets the model item that associated with @self.
*
* If @self is unbound, this function returns %NULL.
*
* Returns: (nullable) (transfer none) (type GObject): The item displayed
*
* Since: 4.12
**/
gpointer
gtk_list_header_get_item (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
if (self->owner)
return gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner));
else
return NULL;
}
/**
* gtk_list_header_get_child: (attributes org.gtk.Method.get_property=child)
* @self: a `GtkListHeader`
*
* Gets the child previously set via gtk_list_header_set_child() or
* %NULL if none was set.
*
* Returns: (transfer none) (nullable): The child
*
* Since: 4.12
*/
GtkWidget *
gtk_list_header_get_child (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
return self->child;
}
/**
* gtk_list_header_set_child: (attributes org.gtk.Method.set_property=child)
* @self: a `GtkListHeader`
* @child: (nullable): The list item's child or %NULL to unset
*
* Sets the child to be used for this listitem.
*
* This function is typically called by applications when
* setting up a header so that the widget can be reused when
* binding it multiple times.
*
* Since: 4.12
*/
void
gtk_list_header_set_child (GtkListHeader *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_LIST_HEADER (self));
g_return_if_fail (child == NULL || gtk_widget_get_parent (child) == NULL);
if (self->child == child)
return;
g_clear_object (&self->child);
if (child)
{
g_object_ref_sink (child);
self->child = child;
}
if (self->owner)
gtk_list_header_widget_set_child (self->owner, child);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
}
/**
* gtk_list_header_get_start: (attributes org.gtk.Method.get_property=start)
* @self: a `GtkListHeader`
*
* Gets the start position in the model of the section that @self is
* currently the header for.
*
* If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
*
* Returns: The start position of the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_start (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
else
return GTK_INVALID_LIST_POSITION;
}
/**
* gtk_list_header_get_end: (attributes org.gtk.Method.get_property=end)
* @self: a `GtkListHeader`
*
* Gets the end position in the model of the section that @self is
* currently the header for.
*
* If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
*
* Returns: The end position of the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_end (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner));
else
return GTK_INVALID_LIST_POSITION;
}
/**
* gtk_list_header_get_n_items: (attributes org.gtk.Method.get_property=n-items)
* @self: a `GtkListHeader`
*
* Gets the the number of items in the section.
*
* If @self is unbound, 0 is returned.
*
* Returns: The number of items in the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_n_items (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)) -
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
else
return 0;
}

50
gtk/gtklistheader.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER (gtk_list_header_get_type ())
GDK_AVAILABLE_IN_4_12
GDK_DECLARE_INTERNAL_TYPE (GtkListHeader, gtk_list_header, GTK, LIST_HEADER, GObject)
GDK_AVAILABLE_IN_4_12
gpointer gtk_list_header_get_item (GtkListHeader *self);
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_start (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_end (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_n_items (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
void gtk_list_header_set_child (GtkListHeader *self,
GtkWidget *child);
GDK_AVAILABLE_IN_4_12
GtkWidget * gtk_list_header_get_child (GtkListHeader *self);
G_END_DECLS

112
gtk/gtklistheaderbase.c Normal file
View File

@@ -0,0 +1,112 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderbaseprivate.h"
typedef struct _GtkListHeaderBasePrivate GtkListHeaderBasePrivate;
struct _GtkListHeaderBasePrivate
{
GObject *item;
guint start;
guint end;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderBase, gtk_list_header_base, GTK_TYPE_WIDGET)
static void
gtk_list_header_base_default_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_set_object (&priv->item, item);
priv->start = start;
priv->end = end;
}
static void
gtk_list_header_base_dispose (GObject *object)
{
GtkListHeaderBase *self = GTK_LIST_HEADER_BASE (object);
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_clear_object (&priv->item);
G_OBJECT_CLASS (gtk_list_header_base_parent_class)->dispose (object);
}
static void
gtk_list_header_base_class_init (GtkListHeaderBaseClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->update = gtk_list_header_base_default_update;
gobject_class->dispose = gtk_list_header_base_dispose;
}
static void
gtk_list_header_base_init (GtkListHeaderBase *self)
{
}
void
gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
if (priv->item == item &&
priv->start == start &&
priv->end == end)
return;
GTK_LIST_HEADER_BASE_GET_CLASS (self)->update (self, item, start, end);
}
guint
gtk_list_header_base_get_start (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->start;
}
guint
gtk_list_header_base_get_end (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->end;
}
gpointer
gtk_list_header_base_get_item (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->item;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtkwidget.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER_BASE (gtk_list_header_base_get_type ())
#define GTK_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBase))
#define GTK_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
#define GTK_IS_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_IS_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_LIST_HEADER_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
typedef struct _GtkListHeaderBase GtkListHeaderBase;
typedef struct _GtkListHeaderBaseClass GtkListHeaderBaseClass;
struct _GtkListHeaderBase
{
GtkWidget parent_instance;
};
struct _GtkListHeaderBaseClass
{
GtkWidgetClass parent_class;
void (* update) (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
};
GType gtk_list_header_base_get_type (void) G_GNUC_CONST;
void gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
guint gtk_list_header_base_get_start (GtkListHeaderBase *self);
guint gtk_list_header_base_get_end (GtkListHeaderBase *self);
gpointer gtk_list_header_base_get_item (GtkListHeaderBase *self);
G_END_DECLS

View File

@@ -0,0 +1,52 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtklistheader.h"
#include "gtklistheaderwidgetprivate.h"
G_BEGIN_DECLS
struct _GtkListHeader
{
GObject parent_instance;
GtkListHeaderWidget *owner; /* has a reference */
GtkWidget *child;
};
struct _GtkListHeaderClass
{
GObjectClass parent_class;
};
GtkListHeader * gtk_list_header_new (void);
void gtk_list_header_do_notify (GtkListHeader *list_header,
gboolean notify_item,
gboolean notify_start,
gboolean notify_end,
gboolean notify_n_items);
G_END_DECLS

295
gtk/gtklistheaderwidget.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderwidgetprivate.h"
#include "gtkbinlayout.h"
#include "gtklistheaderprivate.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistbaseprivate.h"
#include "gtkwidget.h"
typedef struct _GtkListHeaderWidgetPrivate GtkListHeaderWidgetPrivate;
struct _GtkListHeaderWidgetPrivate
{
GtkListItemFactory *factory;
GtkListHeader *header;
};
enum {
PROP_0,
PROP_FACTORY,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderWidget, gtk_list_header_widget, GTK_TYPE_LIST_HEADER_BASE)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_list_header_widget_setup_func (gpointer object,
gpointer data)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header = object;
priv->header = header;
header->owner = self;
gtk_list_header_widget_set_child (self, header->child);
gtk_list_header_do_notify (header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
}
static void
gtk_list_header_widget_setup_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header;
header = gtk_list_header_new ();
gtk_list_item_factory_setup (priv->factory,
G_OBJECT (header),
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_widget_setup_func,
self);
g_assert (priv->header == header);
}
static void
gtk_list_header_widget_teardown_func (gpointer object,
gpointer data)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header = object;
header->owner = NULL;
priv->header = NULL;
gtk_list_header_widget_set_child (self, NULL);
gtk_list_header_do_notify (header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
}
static void
gtk_list_header_widget_teardown_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
gpointer header = priv->header;
gtk_list_item_factory_teardown (priv->factory,
header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_widget_teardown_func,
self);
g_assert (priv->header == NULL);
g_object_unref (header);
}
typedef struct {
GtkListHeaderWidget *widget;
gpointer item;
guint start;
guint end;
} GtkListHeaderWidgetUpdate;
static void
gtk_list_header_widget_update_func (gpointer object,
gpointer data)
{
GtkListHeaderWidgetUpdate *update = data;
GtkListHeaderWidget *self = update->widget;
GtkListHeaderBase *base = GTK_LIST_HEADER_BASE (self);
/* Track notify manually instead of freeze/thaw_notify for performance reasons. */
gboolean notify_item, notify_start, notify_end, notify_n_items;
/* FIXME: It's kinda evil to notify external objects from here... */
notify_item = gtk_list_header_base_get_item (base) != update->item;
notify_start = gtk_list_header_base_get_start (base) != update->start;
notify_end = gtk_list_header_base_get_end (base) != update->end;
notify_n_items = gtk_list_header_base_get_end (base) - gtk_list_header_base_get_start (base) != update->end - update->start;
GTK_LIST_HEADER_BASE_CLASS (gtk_list_header_widget_parent_class)->update (base,
update->item,
update->start,
update->end);
if (object)
gtk_list_header_do_notify (object, notify_item, notify_start, notify_end, notify_n_items);
}
static void
gtk_list_header_widget_update (GtkListHeaderBase *base,
gpointer item,
guint start,
guint end)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (base);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeaderWidgetUpdate update = { self, item, start, end };
if (priv->header)
{
gtk_list_item_factory_update (priv->factory,
G_OBJECT (priv->header),
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
item != NULL,
gtk_list_header_widget_update_func,
&update);
}
else
{
gtk_list_header_widget_update_func (NULL, &update);
}
}
static void
gtk_list_header_widget_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
switch (property_id)
{
case PROP_FACTORY:
gtk_list_header_widget_set_factory (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_widget_clear_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
if (priv->factory == NULL)
return;
if (priv->header)
gtk_list_header_widget_teardown_factory (self);
g_clear_object (&priv->factory);
}
static void
gtk_list_header_widget_dispose (GObject *object)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
gtk_list_header_widget_clear_factory (self);
G_OBJECT_CLASS (gtk_list_header_widget_parent_class)->dispose (object);
}
static void
gtk_list_header_widget_class_init (GtkListHeaderWidgetClass *klass)
{
GtkListHeaderBaseClass *base_class = GTK_LIST_HEADER_BASE_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
base_class->update = gtk_list_header_widget_update;
gobject_class->set_property = gtk_list_header_widget_set_property;
gobject_class->dispose = gtk_list_header_widget_dispose;
properties[PROP_FACTORY] =
g_param_spec_object ("factory", NULL, NULL,
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gtk_widget_class_set_css_name (widget_class, I_("header"));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_ROW_HEADER);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
}
static void
gtk_list_header_widget_init (GtkListHeaderWidget *self)
{
}
void
gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
GtkListItemFactory *factory)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
if (priv->factory == factory)
return;
gtk_list_header_widget_clear_factory (self);
if (factory)
{
priv->factory = g_object_ref (factory);
gtk_list_header_widget_setup_factory (self);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
GtkWidget *
gtk_list_header_widget_new (GtkListItemFactory *factory)
{
return g_object_new (GTK_TYPE_LIST_HEADER_WIDGET,
"factory", factory,
NULL);
}
void
gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
GtkWidget *child)
{
GtkWidget *cur_child = gtk_widget_get_first_child (GTK_WIDGET (self));
if (cur_child == child)
return;
g_clear_pointer (&cur_child, gtk_widget_unparent);
if (child)
gtk_widget_set_parent (child, GTK_WIDGET (self));
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtklistheaderbaseprivate.h"
#include "gtklistitemfactory.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER_WIDGET (gtk_list_header_widget_get_type ())
#define GTK_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidget))
#define GTK_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
#define GTK_IS_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_WIDGET))
#define GTK_IS_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_WIDGET))
#define GTK_LIST_HEADER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
typedef struct _GtkListHeaderWidget GtkListHeaderWidget;
typedef struct _GtkListHeaderWidgetClass GtkListHeaderWidgetClass;
struct _GtkListHeaderWidget
{
GtkListHeaderBase parent_instance;
};
struct _GtkListHeaderWidgetClass
{
GtkListHeaderBaseClass parent_class;
};
GType gtk_list_header_widget_get_type (void) G_GNUC_CONST;
GtkWidget * gtk_list_header_widget_new (GtkListItemFactory *factory);
void gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_header_widget_get_factory (GtkListHeaderWidget *self);
void gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
GtkWidget *child);
G_END_DECLS

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtklistbaseprivate.h"
#include "gtklistheaderwidgetprivate.h"
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemwidgetprivate.h"
#include "gtkmultiselection.h"
@@ -145,6 +146,7 @@ enum
PROP_0,
PROP_ENABLE_RUBBERBAND,
PROP_FACTORY,
PROP_HEADER_FACTORY,
PROP_MODEL,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
@@ -163,29 +165,6 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE)
static GParamSpec *properties[N_PROPS] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void G_GNUC_UNUSED
dump (GtkListView *self)
{
GtkListTile *tile;
guint n_widgets, n_list_rows;
n_widgets = 0;
n_list_rows = 0;
//g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
for (tile = gtk_list_item_manager_get_first (self->item_manager);
tile;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
n_widgets++;
n_list_rows++;
g_print (" %4u%s %d,%d,%d,%d\n", tile->n_items, tile->widget ? " (widget)" : "",
tile->area.x, tile->area.y, tile->area.width, tile->area.height);
}
g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows);
}
static GtkListTile *
gtk_list_view_split (GtkListBase *base,
GtkListTile *tile,
@@ -215,6 +194,13 @@ gtk_list_view_split (GtkListBase *base,
return new_tile;
}
static void
gtk_list_view_prepare_section (GtkListBase *base,
GtkListTile *tile,
guint position)
{
}
/* We define the listview as **inert** when the factory isn't used. */
static gboolean
gtk_list_view_is_inert (GtkListView *self)
@@ -228,7 +214,8 @@ gtk_list_view_is_inert (GtkListView *self)
static void
gtk_list_view_update_factories_with (GtkListView *self,
GtkListItemFactory *factory)
GtkListItemFactory *factory,
GtkListItemFactory *header_factory)
{
GtkListTile *tile;
@@ -236,8 +223,27 @@ gtk_list_view_update_factories_with (GtkListView *self,
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
switch (tile->type)
{
case GTK_LIST_TILE_ITEM:
if (tile->widget)
gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
break;
case GTK_LIST_TILE_HEADER:
if (tile->widget)
gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), header_factory);
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
case GTK_LIST_TILE_FOOTER:
case GTK_LIST_TILE_UNMATCHED_FOOTER:
case GTK_LIST_TILE_FILLER:
case GTK_LIST_TILE_REMOVED:
g_assert (tile->widget == NULL);
break;
default:
g_assert_not_reached();
break;
}
}
}
@@ -245,13 +251,14 @@ static void
gtk_list_view_update_factories (GtkListView *self)
{
gtk_list_view_update_factories_with (self,
gtk_list_view_is_inert (self) ? NULL : self->factory);
gtk_list_view_is_inert (self) ? NULL : self->factory,
gtk_list_view_is_inert (self) ? NULL : self->header_factory);
}
static void
gtk_list_view_clear_factories (GtkListView *self)
{
gtk_list_view_update_factories_with (self, NULL);
gtk_list_view_update_factories_with (self, NULL, NULL);
}
static GtkListItemBase *
@@ -275,6 +282,20 @@ gtk_list_view_create_list_widget (GtkListBase *base)
return GTK_LIST_ITEM_BASE (result);
}
static GtkListHeaderBase *
gtk_list_view_create_header_widget (GtkListBase *base)
{
GtkListView *self = GTK_LIST_VIEW (base);
GtkListItemFactory *factory;
if (gtk_list_view_is_inert (self))
factory = NULL;
else
factory = self->header_factory;
return GTK_LIST_HEADER_BASE (gtk_list_header_widget_new (factory));;
}
static gboolean
gtk_list_view_get_allocation (GtkListBase *base,
guint pos,
@@ -527,8 +548,11 @@ gtk_list_view_measure_list (GtkWidget *widget,
gtk_widget_measure (tile->widget,
orientation, for_size,
&child_min, &child_nat, NULL, NULL);
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
if (tile->type == GTK_LIST_TILE_ITEM)
{
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
}
min += child_min;
nat += child_nat;
}
@@ -622,7 +646,8 @@ gtk_list_view_size_allocate (GtkWidget *widget,
else
row_height = nat;
gtk_list_tile_set_area_size (self->item_manager, tile, list_width, row_height);
g_array_append_val (heights, row_height);
if (tile->type == GTK_LIST_TILE_ITEM)
g_array_append_val (heights, row_height);
}
/* step 3: determine height of unknown items and set the positions */
@@ -723,6 +748,10 @@ gtk_list_view_get_property (GObject *object,
g_value_set_object (value, self->factory);
break;
case PROP_HEADER_FACTORY:
g_value_set_object (value, self->header_factory);
break;
case PROP_MODEL:
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
@@ -763,6 +792,10 @@ gtk_list_view_set_property (GObject *object,
gtk_list_view_set_factory (self, g_value_get_object (value));
break;
case PROP_HEADER_FACTORY:
gtk_list_view_set_header_factory (self, g_value_get_object (value));
break;
case PROP_MODEL:
gtk_list_view_set_model (self, g_value_get_object (value));
break;
@@ -812,6 +845,8 @@ gtk_list_view_class_init (GtkListViewClass *klass)
list_base_class->split = gtk_list_view_split;
list_base_class->create_list_widget = gtk_list_view_create_list_widget;
list_base_class->prepare_section = gtk_list_view_prepare_section;
list_base_class->create_header_widget = gtk_list_view_create_header_widget;
list_base_class->get_allocation = gtk_list_view_get_allocation;
list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
@@ -849,6 +884,18 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:header-factory: (attributes org.gtk.Property.get=gtk_list_view_get_header_factory org.gtk.Property.set=gtk_list_view_set_header_factory)
*
* Factory for populating list items.
*
* Since: 4.12
*/
properties[PROP_HEADER_FACTORY] =
g_param_spec_object ("header-factory", NULL, NULL,
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:model: (attributes org.gtk.Property.get=gtk_list_view_get_model org.gtk.Property.set=gtk_list_view_set_model)
*
@@ -1070,6 +1117,68 @@ gtk_list_view_set_factory (GtkListView *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_list_view_get_header_factory: (attributes org.gtk.Method.get_property=header-factory)
* @self: a `GtkListView`
*
* Gets the factory that's currently used to populate section headers.
*
* Returns: (nullable) (transfer none): The factory in use
*
* Since: 4.12
*/
GtkListItemFactory *
gtk_list_view_get_header_factory (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return self->header_factory;
}
/**
* gtk_list_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory)
* @self: a `GtkListView`
* @factory: (nullable) (transfer none): the factory to use
*
* Sets the `GtkListItemFactory` to use for populating the
* [class@Gtk.ListHeader] objects used in section headers.
*
* If this factory is set to %NULL, the list will not use sections.
*
* Since: 4.12
*/
void
gtk_list_view_set_header_factory (GtkListView *self,
GtkListItemFactory *factory)
{
gboolean had_sections;
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
had_sections = gtk_list_item_manager_get_has_sections (self->item_manager);
if (!g_set_object (&self->header_factory, factory))
return;
gtk_list_item_manager_set_has_sections (self->item_manager, factory != NULL);
if (had_sections && gtk_list_item_manager_get_has_sections (self->item_manager))
{
GtkListTile *tile;
for (tile = gtk_list_item_manager_get_first (self->item_manager);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget && tile->type == GTK_LIST_TILE_HEADER)
gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), factory);
}
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
* @self: a `GtkListView`
@@ -1139,7 +1248,7 @@ gtk_list_view_set_single_click_activate (GtkListView *self,
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
if (tile->widget && tile->type == GTK_LIST_TILE_ITEM)
gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (tile->widget), single_click_activate);
}
@@ -1241,3 +1350,30 @@ gtk_list_view_get_tab_behavior (GtkListView *self)
return gtk_list_base_get_tab_behavior (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_scroll_to:
* @self: The listview to scroll in
* @pos: position of the item
* @flags: actions to perform
* @scroll: (nullable) (transfer full): details of how to perform
* the scroll operation or NULL to scroll into view
*
* Scroll to the item at the given position and performs the actions
* specified in @flags.
*
* This function works if the listview is not shown or not focused,
* the changes will take effect once that happens.
*
* Since: 4.12
*/
void
gtk_list_view_scroll_to (GtkListView *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
gtk_list_base_scroll_to (GTK_LIST_BASE (self), pos, flags, scroll);
}

View File

@@ -57,6 +57,13 @@ GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self);
GDK_AVAILABLE_IN_4_12
void gtk_list_view_set_header_factory (GtkListView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_list_view_get_header_factory (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_show_separators (GtkListView *self,
gboolean show_separators);
@@ -82,6 +89,11 @@ GDK_AVAILABLE_IN_4_12
GtkListTabBehavior
gtk_list_view_get_tab_behavior (GtkListView *self);
GDK_AVAILABLE_IN_4_12
void gtk_list_view_scroll_to (GtkListView *self,
guint pos,
GtkListScrollFlags flags,
GtkScrollInfo *scroll);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref)

View File

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

View File

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

View File

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

370
gtk/gtkscrollinfo.c Normal file
View File

@@ -0,0 +1,370 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* GtkScrollInfo:
*
* The `GtkScrollInfo` can be used to provide more accurate data on how a scroll
* operation should be performed.
*
* Scrolling functions usually allow not passing a scroll info which will cause
* the default values to be used which will just scroll the element into view.
*
* Since: 4.12
*/
#include "config.h"
#include "gtkscrollinfo.h"
#include <math.h>
struct _GtkScrollInfo
{
guint ref_count;
graphene_rect_t viewport;
graphene_point_t position;
guint has_position :1;
gboolean enabled[2]; /* directions */
};
static GtkScrollInfo default_scroll_info = {
1,
#if 0
/* MSVC can't deal with this */
GRAPHENE_RECT_INIT (0, 0, 1, 1),
GRAPHENE_POINT_INIT (0.5, 0.5),
#else
{ { 0, 0 }, { 1, 1 }},
{ 0.5, 0.5 },
#endif
FALSE,
{ TRUE, TRUE }
};
G_DEFINE_BOXED_TYPE (GtkScrollInfo, gtk_scroll_info,
gtk_scroll_info_ref,
gtk_scroll_info_unref)
/**
* gtk_scroll_info_new:
*
* Creates a new scroll info for scrolling an element into view.
*
* Returns: A new scroll info
*
* Since: 4.12
**/
GtkScrollInfo *
gtk_scroll_info_new (void)
{
GtkScrollInfo *self;
self = g_new0 (GtkScrollInfo, 1);
self->ref_count = 1;
self->viewport = GRAPHENE_RECT_INIT (0, 0, 1, 1);
self->has_position = FALSE;
self->enabled[GTK_ORIENTATION_HORIZONTAL] = TRUE;
self->enabled[GTK_ORIENTATION_VERTICAL] = TRUE;
return self;
}
/**
* gtk_scroll_info_ref:
* @self: a `GtkScrollInfo`
*
* Increases the reference count of a `GtkScrollInfo` by one.
*
* Returns: the passed in `GtkScrollInfo`.
*
* Since: 4.12
*/
GtkScrollInfo *
gtk_scroll_info_ref (GtkScrollInfo *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gtk_scroll_info_unref:
* @self: a `GtkScrollInfo`
*
* Decreases the reference count of a `GtkScrollInfo` by one.
*
* If the resulting reference count is zero, frees the self.
*
* Since: 4.12
*/
void
gtk_scroll_info_unref (GtkScrollInfo *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
g_free (self);
}
/**
* gtk_scroll_info_set_viewport:
* @self: a `GtkScrollInfo`
* @viewport: the viewport to use
*
* Sets the rectangle of the scrollable's viewport region
* that should be used for scrolling into.
*
* The values of the viewport rect are given in the range
* from 0 to 1 where 0 means the top/start of the scrollable's
* viewport and 1 means the bottom/end.
*
* By default, this viewport is set to (0, 0, 1, 1) which
* means the whole visible region of the scrollable is
* used.
*
* Since: 4.12
**/
void
gtk_scroll_info_set_viewport (GtkScrollInfo *self,
const graphene_rect_t *viewport)
{
g_return_if_fail (self != NULL);
g_return_if_fail (viewport != NULL);
self->viewport = *viewport;
}
/**
* gtk_scroll_info_get_viewport:
* @self: a `GtkScrollInfo`
*
* Gets the viewport set via gtk_scroll_info_set_viewport().
*
* Returns: (transfer none): The viewport
**/
const graphene_rect_t *
gtk_scroll_info_get_viewport (GtkScrollInfo *self)
{
g_return_val_if_fail (self != NULL, NULL);
return &self->viewport;
}
/**
* gtk_scroll_info_set_position:
* @self: a `GtkScrollInfo`
* @position: (nullable): The position to target
*
* Specifies the position inside the viewport where the element
* should be placed.
*
* If the position is unset - which is the default - the element
* will be scrolled into view.
**/
void
gtk_scroll_info_set_position (GtkScrollInfo *self,
const graphene_point_t *position)
{
g_return_if_fail (self != NULL);
if (position)
{
self->has_position = TRUE;
self->position = *position;
}
else
{
self->has_position = FALSE;
}
}
/**
* gtk_scroll_info_get_position:
* @self: a `GtkScrollInfo`
*
* Gets the position set via gtk_scroll_info_set_position().
*
* Returns: (transfer none) (nullable): The position
*/
const graphene_point_t *
gtk_scroll_info_get_position (GtkScrollInfo *self)
{
g_return_val_if_fail (self != NULL, NULL);
if (!self->has_position)
return NULL;
return &self->position;
}
/**
* gtk_scroll_info_set_enable_horizontal:
* @self: a `GtkScrollInfo`
* @horizontal: if scrolling in the horizontal direction
* should happen
*
* Turns horizontal scrolling on or off.
**/
void
gtk_scroll_info_set_enable_horizontal (GtkScrollInfo *self,
gboolean horizontal)
{
g_return_if_fail (self != NULL);
self->enabled[GTK_ORIENTATION_HORIZONTAL] = horizontal;
}
/**
* gtk_scroll_info_get_enable_horizontal:
* @self: a `GtkScrollInfo`
*
* Checks if horizontal scrolling is enabled.
*
* Returns: %TRUE if horizontal scrolling is enabled.
**/
gboolean
gtk_scroll_info_get_enable_horizontal (GtkScrollInfo *self)
{
g_return_val_if_fail (self != NULL, FALSE);
return self->enabled[GTK_ORIENTATION_HORIZONTAL];
}
/**
* gtk_scroll_info_set_enable_vertical:
* @self: a `GtkScrollInfo`
* @vertical: if scrolling in the vertical direction
* should happen
*
* Turns vertical scrolling on or off.
**/
void
gtk_scroll_info_set_enable_vertical (GtkScrollInfo *self,
gboolean vertical)
{
g_return_if_fail (self != NULL);
self->enabled[GTK_ORIENTATION_VERTICAL] = vertical;
}
/**
* gtk_scroll_info_get_enable_vertical:
* @self: a `GtkScrollInfo`
*
* Checks if vertical scrolling is enabled.
*
* Returns: %TRUE if vertical scrolling is enabled.
**/
gboolean
gtk_scroll_info_get_enable_vertical (GtkScrollInfo *self)
{
g_return_val_if_fail (self != NULL, FALSE);
return self->enabled[GTK_ORIENTATION_VERTICAL];
}
int
gtk_scroll_info_compute_for_orientation (GtkScrollInfo *self,
GtkOrientation orientation,
int area_origin,
int area_size,
int viewport_origin,
int viewport_size)
{
float origin, size, position;
int delta;
if (self == NULL)
self = &default_scroll_info;
if (!self->enabled[orientation])
return viewport_origin;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
origin = viewport_origin + viewport_size * self->viewport.origin.x;
size = viewport_size * self->viewport.size.width;
position = self->position.x;
}
else if (orientation == GTK_ORIENTATION_VERTICAL)
{
origin = viewport_origin + viewport_size * self->viewport.origin.y;
size = viewport_size * self->viewport.size.height;
position = self->position.y;
}
else
{
g_return_val_if_reached (viewport_origin);
}
if (self->has_position)
{
float space = size - area_size;
delta = area_origin - round (origin + space * position);
}
else
{
if (area_origin <= origin)
delta = area_origin - ceil (origin);
else if (area_origin + area_size > origin + size)
delta = area_origin + area_size - floor (origin + size);
else
delta = 0;
}
return viewport_origin + delta;
}
/*<private>
* gtk_scroll_info_compute_scroll:
* @self: a `GtkScrollInfo`
* @area: area to scroll
* @viewport: viewport area to scroll into
* @out_x: (out): x coordinate to scroll viewport to
* @out_y: (out): y coordinate to scroll viewport to
*
* Computes The new x/y coordinate to move the viewport to
* according to this scroll info.
**/
void
gtk_scroll_info_compute_scroll (GtkScrollInfo *self,
const cairo_rectangle_int_t *area,
const cairo_rectangle_int_t *viewport,
int *out_x,
int *out_y)
{
*out_x = gtk_scroll_info_compute_for_orientation (self,
GTK_ORIENTATION_HORIZONTAL,
area->x, area->width,
viewport->x, viewport->width);
*out_y = gtk_scroll_info_compute_for_orientation (self,
GTK_ORIENTATION_VERTICAL,
area->y, area->height,
viewport->y, viewport->height);
}

84
gtk/gtkscrollinfo.h Normal file
View File

@@ -0,0 +1,84 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
#include <gtk/gtkenums.h>
#include <gtk/gtktypes.h>
#include <graphene.h>
G_BEGIN_DECLS
#define GTK_TYPE_SCROLL_INFO (gtk_scroll_info_get_type ())
GDK_AVAILABLE_IN_4_12
GType gtk_scroll_info_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_4_12
GtkScrollInfo * gtk_scroll_info_new (void);
GDK_AVAILABLE_IN_4_12
GtkScrollInfo * gtk_scroll_info_ref (GtkScrollInfo *self);
GDK_AVAILABLE_IN_4_12
void gtk_scroll_info_unref (GtkScrollInfo *self);
GDK_AVAILABLE_IN_4_12
void gtk_scroll_info_set_viewport (GtkScrollInfo *self,
const graphene_rect_t *viewport);
GDK_AVAILABLE_IN_4_12
const graphene_rect_t * gtk_scroll_info_get_viewport (GtkScrollInfo *self);
GDK_AVAILABLE_IN_4_12
void gtk_scroll_info_set_position (GtkScrollInfo *self,
const graphene_point_t *position);
GDK_AVAILABLE_IN_4_12
const graphene_point_t *gtk_scroll_info_get_position (GtkScrollInfo *self);
GDK_AVAILABLE_IN_4_12
void gtk_scroll_info_set_enable_horizontal (GtkScrollInfo *self,
gboolean horizontal);
GDK_AVAILABLE_IN_4_12
gboolean gtk_scroll_info_get_enable_horizontal (GtkScrollInfo *self);
GDK_AVAILABLE_IN_4_12
void gtk_scroll_info_set_enable_vertical (GtkScrollInfo *self,
gboolean vertical);
GDK_AVAILABLE_IN_4_12
gboolean gtk_scroll_info_get_enable_vertical (GtkScrollInfo *self);
void gtk_scroll_info_compute_scroll (GtkScrollInfo *self,
const cairo_rectangle_int_t *area,
const cairo_rectangle_int_t *viewport,
int *out_x,
int *out_y);
int gtk_scroll_info_compute_for_orientation (GtkScrollInfo *self,
GtkOrientation orientation,
int area_origin,
int area_size,
int viewport_origin,
int viewport_size);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkScrollInfo, gtk_scroll_info_unref)
G_END_DECLS

225
gtk/gtksectionmodel.c Normal file
View File

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

72
gtk/gtksectionmodel.h Normal file
View File

@@ -0,0 +1,72 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_SECTION_MODEL (gtk_section_model_get_type ())
GDK_AVAILABLE_IN_4_12
G_DECLARE_INTERFACE (GtkSectionModel, gtk_section_model, GTK, SECTION_MODEL, GListModel)
/**
* GtkSectionModelInterface:
* @get_section: Return the section that covers the given position. If
* the position is outside the number of items, returns a single range from
* n_items to G_MAXUINT
*
* The list of virtual functions for the `GtkSectionModel` interface.
* No function must be implemented, but unless `GtkSectionModel::get_section()`
* is implemented, the whole model will just be a single section.
*
* Since: 4.12
*/
struct _GtkSectionModelInterface
{
/*< private >*/
GTypeInterface g_iface;
/*< public >*/
void (* get_section) (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
};
GDK_AVAILABLE_IN_4_12
void gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
/* for implementations only */
GDK_AVAILABLE_IN_4_12
void gtk_section_model_sections_changed (GtkSectionModel *self,
guint position,
guint n_items);
G_END_DECLS

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,6 +46,7 @@ typedef struct _GtkListItemFactory GtkListItemFactory;
typedef struct _GtkNative GtkNative;
typedef struct _GtkRequisition GtkRequisition;
typedef struct _GtkRoot GtkRoot;
typedef struct _GtkScrollInfo GtkScrollInfo;
typedef struct _GtkSettings GtkSettings;
typedef struct _GtkShortcut GtkShortcut;
typedef struct _GtkShortcutAction GtkShortcutAction;

View File

@@ -30,11 +30,13 @@
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkscrollable.h"
#include "gtkscrollinfo.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkbuildable.h"
#include "gtktext.h"
#include <math.h>
/**
* GtkViewport:
@@ -600,30 +602,12 @@ gtk_viewport_set_scroll_to_focus (GtkViewport *viewport,
g_object_notify (G_OBJECT (viewport), "scroll-to-focus");
}
static void
scroll_to_view (GtkAdjustment *adj,
double pos,
double size)
{
double value, page_size;
value = gtk_adjustment_get_value (adj);
page_size = gtk_adjustment_get_page_size (adj);
if (pos < 0)
gtk_adjustment_animate_to_value (adj, value + pos);
else if (pos + size >= page_size)
gtk_adjustment_animate_to_value (adj, value + pos + size - page_size);
}
static void
focus_change_handler (GtkWidget *widget)
{
GtkViewport *viewport = GTK_VIEWPORT (widget);
GtkRoot *root;
GtkWidget *focus_widget;
graphene_rect_t rect;
graphene_point_t p;
if ((gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_FOCUS_WITHIN) == 0)
return;
@@ -637,16 +621,7 @@ focus_change_handler (GtkWidget *widget)
if (GTK_IS_TEXT (focus_widget))
focus_widget = gtk_widget_get_parent (focus_widget);
if (!gtk_widget_compute_bounds (focus_widget, viewport->child, &rect))
return;
if (!gtk_widget_compute_point (viewport->child, widget,
&GRAPHENE_POINT_INIT (rect.origin.x, rect.origin.y),
&p))
return;
scroll_to_view (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL], p.x, rect.size.width);
scroll_to_view (viewport->adjustment[GTK_ORIENTATION_VERTICAL], p.y, rect.size.height);
gtk_viewport_scroll_to (viewport, focus_widget, NULL);
}
static void
@@ -718,3 +693,54 @@ gtk_viewport_get_child (GtkViewport *viewport)
return viewport->child;
}
/**
* gtk_viewport_scroll_to:
* @viewport: a `GtkViewport`
* @descendant: a descendant widget of the viewport
* @scroll: (nullable) (transfer full): details of how to perform
* the scroll operation or NULL to scroll into view
*
* Scrolls a descendant of the viewport into view.
*
* The viewport and the descendant must be visible and mapped for
* this function to work, otherwise no scrolling will be performed.
*
* Since: 4.12
**/
void
gtk_viewport_scroll_to (GtkViewport *viewport,
GtkWidget *descendant,
GtkScrollInfo *scroll)
{
graphene_rect_t bounds;
int x, y;
double adj_x, adj_y;
g_return_if_fail (GTK_IS_VIEWPORT (viewport));
g_return_if_fail (GTK_IS_WIDGET (descendant));
if (!gtk_widget_compute_bounds (descendant, GTK_WIDGET (viewport), &bounds))
return;
adj_x = gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL]);
adj_y = gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL]);
gtk_scroll_info_compute_scroll (scroll,
&(GdkRectangle) {
floor (bounds.origin.x + adj_x),
floor (bounds.origin.y + adj_y),
ceil (bounds.origin.x + bounds.size.width) - floor (bounds.origin.x),
ceil (bounds.origin.y + bounds.size.height) - floor (bounds.origin.y)
},
&(GdkRectangle) {
adj_x,
adj_y,
gtk_widget_get_width (GTK_WIDGET (viewport)),
gtk_widget_get_height (GTK_WIDGET (viewport))
},
&x, &y);
gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL], x);
gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL], y);
}

View File

@@ -61,6 +61,11 @@ void gtk_viewport_set_child (GtkViewport *viewport,
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_viewport_get_child (GtkViewport *viewport);
GDK_AVAILABLE_IN_4_12
void gtk_viewport_scroll_to (GtkViewport *viewport,
GtkWidget *descendant,
GtkScrollInfo *scroll);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkViewport, g_object_unref)
G_END_DECLS

View File

@@ -910,7 +910,11 @@ search (GtkInspectorObjectTree *wt,
{
if (match_object (child, text))
{
gtk_single_selection_set_selected (priv->selection, row);
gtk_column_view_scroll_to (GTK_COLUMN_VIEW (wt->priv->list),
row,
NULL,
GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS,
NULL);
g_object_unref (child);
g_object_unref (row_item);
return TRUE;
@@ -1302,8 +1306,12 @@ gtk_inspector_object_tree_select_object (GtkInspectorObjectTree *wt,
if (row_item == NULL)
return;
gtk_single_selection_set_selected (wt->priv->selection,
gtk_tree_list_row_get_position (row_item));
gtk_column_view_scroll_to (GTK_COLUMN_VIEW (wt->priv->list),
gtk_tree_list_row_get_position (row_item),
NULL,
GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS,
NULL);
g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object); // FIXME
g_object_unref (row_item);
}

View File

@@ -271,6 +271,9 @@ gtk_public_sources = files([
'gtklinkbutton.c',
'gtklistbox.c',
'gtklistfactorywidget.c',
'gtklistheader.c',
'gtklistheaderbase.c',
'gtklistheaderwidget.c',
'gtklistitem.c',
'gtklistitembase.c',
'gtklistitemfactory.c',
@@ -330,9 +333,11 @@ gtk_public_sources = files([
'gtkscalebutton.c',
'gtkscrollable.c',
'gtkscrollbar.c',
'gtkscrollinfo.c',
'gtkscrolledwindow.c',
'gtksearchbar.c',
'gtksearchentry.c',
'gtksectionmodel.c',
'gtkselectionfiltermodel.c',
'gtkselectionmodel.c',
'gtkseparator.c',
@@ -518,6 +523,7 @@ gtk_public_headers = files([
'gtklinkbutton.h',
'gtklistbase.h',
'gtklistbox.h',
'gtklistheader.h',
'gtklistitem.h',
'gtklistitemfactory.h',
'gtklistview.h',
@@ -562,9 +568,11 @@ gtk_public_headers = files([
'gtkscalebutton.h',
'gtkscrollable.h',
'gtkscrollbar.h',
'gtkscrollinfo.h',
'gtkscrolledwindow.h',
'gtksearchbar.h',
'gtksearchentry.h',
'gtksectionmodel.h',
'gtkselectionfiltermodel.h',
'gtkselectionmodel.h',
'gtkseparator.h',

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,497 @@
/*
* Copyright © 2023 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
#include "gtk/gtklistitemmanagerprivate.h"
#include "gtk/gtklistbaseprivate.h"
static GListModel *
create_source_model (guint min_size, guint max_size)
{
GtkStringList *list;
guint i, size;
size = g_test_rand_int_range (min_size, max_size + 1);
list = gtk_string_list_new (NULL);
for (i = 0; i < size; i++)
gtk_string_list_append (list, g_test_rand_bit () ? "A" : "B");
return G_LIST_MODEL (list);
}
void
print_list_item_manager_tiles (GtkListItemManager *items)
{
GString *string;
GtkListTile *tile;
string = g_string_new ("");
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_ITEM:
if (tile->widget)
g_string_append_c (string, 'W');
else if (tile->n_items == 1)
g_string_append_c (string, 'x');
else
g_string_append_printf (string, "%u,", tile->n_items);
break;
case GTK_LIST_TILE_HEADER:
g_string_append_c (string, '[');
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_string_append_c (string, '(');
break;
case GTK_LIST_TILE_FOOTER:
g_string_append_c (string, ']');
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_string_append_c (string, ')');
break;
case GTK_LIST_TILE_FILLER:
g_string_append_c (string, '_');
break;
case GTK_LIST_TILE_REMOVED:
g_string_append_c (string, '.');
break;
default:
g_assert_not_reached ();
break;
}
}
g_print ("%s\n", string->str);
g_string_free (string, TRUE);
}
static void
check_list_item_manager (GtkListItemManager *items,
GtkListItemTracker **trackers,
gsize n_trackers)
{
GListModel *model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
GtkListTile *tile;
guint n_items = 0;
guint i;
gboolean has_sections;
enum {
NO_SECTION,
MATCHED_SECTION,
UNMATCHED_SECTION
} section_state = NO_SECTION;
has_sections = gtk_list_item_manager_get_has_sections (items);
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
GObject *item = g_list_model_get_item (model, n_items);
if (has_sections)
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
else
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmphex (GPOINTER_TO_SIZE (item), ==, GPOINTER_TO_SIZE (gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget))));
g_object_unref (item);
g_assert_cmpint (n_items, ==, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (tile->widget)));
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
/* We don't add fillers */
g_assert_not_reached ();
break;
case GTK_LIST_TILE_REMOVED:
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
break;
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
for (tile = gtk_list_tile_gc (items, gtk_list_item_manager_get_first (items));
tile != NULL;
tile = gtk_list_tile_gc (items, gtk_rb_tree_node_get_next (tile)))
;
n_items = 0;
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
case GTK_LIST_TILE_REMOVED:
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
}
static GtkListTile *
split_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
GtkListItemManager *items = g_object_get_data (G_OBJECT (widget), "the-items");
return gtk_list_tile_split (items, tile, n_items);
}
static void
prepare_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
}
static GtkListItemBase *
create_simple_item (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_ITEM_BASE, NULL);
}
static GtkListHeaderBase *
create_simple_header (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_HEADER_BASE, NULL);
}
static void
test_create (void)
{
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
gtk_window_destroy (GTK_WINDOW (widget));
}
static void
test_create_with_items (void)
{
GListModel *source;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
source = create_source_model (1, 50);
selection = gtk_no_selection_new (G_LIST_MODEL (source));
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
#define N_TRACKERS 3
#define N_WIDGETS_PER_TRACKER 10
#define N_RUNS 500
static void
print_changes_cb (GListModel *model,
guint position,
guint removed,
guint added,
gpointer unused)
{
if (!g_test_verbose ())
return;
if (removed == 0)
g_test_message ("%u/%u: adding %u items", position, g_list_model_get_n_items (model), added);
else if (added == 0)
g_test_message ("%u/%u: removing %u items", position, g_list_model_get_n_items (model), removed);
else
g_test_message ("%u/%u: removing %u and adding %u items", position, g_list_model_get_n_items (model), removed, added);
}
static void
test_exhaustive (void)
{
GtkListItemTracker *trackers[N_TRACKERS];
GListStore *store;
GtkFlattenListModel *flatten;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
gsize i;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
for (i = 0; i < N_TRACKERS; i++)
trackers[i] = gtk_list_item_tracker_new (items);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
selection = gtk_no_selection_new (G_LIST_MODEL (flatten));
g_signal_connect (selection, "items-changed", G_CALLBACK (print_changes_cb), NULL);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
for (i = 0; i < N_RUNS; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position, n_items;
if (g_test_verbose ())
print_list_item_manager_tiles (items);
switch (g_test_rand_int_range (0, 6))
{
case 0:
if (g_test_verbose ())
g_test_message ("GC and checking");
check_list_item_manager (items, trackers, N_TRACKERS);
break;
case 1:
/* remove a model */
remove = TRUE;
break;
case 2:
/* add a model */
add = TRUE;
break;
case 3:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
case 4:
n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
if (n_items > 0)
{
guint tracker_id = g_test_rand_int_range (0, N_TRACKERS);
guint pos = g_test_rand_int_range (0, n_items);
guint n_before = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
guint n_after = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
if (g_test_verbose ())
g_test_message ("setting tracker %u to %u -%u + %u", tracker_id, pos, n_before, n_after);
gtk_list_item_tracker_set_position (items,
trackers [tracker_id],
pos,
n_before, n_after);
}
break;
case 5:
{
gboolean has_sections = g_test_rand_bit ();
if (g_test_verbose ())
g_test_message ("Setting has_sections to %s", has_sections ? "true" : "false");
gtk_list_item_manager_set_has_sections (items, has_sections);
}
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the filters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
}
check_list_item_manager (items, trackers, N_TRACKERS);
for (i = 0; i < N_TRACKERS; i++)
gtk_list_item_tracker_free (items, trackers[i]);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
int
main (int argc, char *argv[])
{
gtk_test_init (&argc, &argv);
g_test_add_func ("/listitemmanager/create", test_create);
g_test_add_func ("/listitemmanager/create_with_items", test_create_with_items);
g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive);
return g_test_run ();
}

View File

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

View File

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

View File

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