Compare commits
46 Commits
main
...
wip/otte/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a6ce84635 | ||
|
|
34c258c93c | ||
|
|
3ec5bdeaa4 | ||
|
|
f2994cf826 | ||
|
|
e11fdd33d9 | ||
|
|
1b699f2ce7 | ||
|
|
5269f328af | ||
|
|
cca2f0527a | ||
|
|
4315289b04 | ||
|
|
f4d4151b13 | ||
|
|
1ae9b60339 | ||
|
|
0714da4d69 | ||
|
|
cc8f31a7bf | ||
|
|
9b749cee22 | ||
|
|
cc5a644205 | ||
|
|
2867a3bfe0 | ||
|
|
af56cb8a75 | ||
|
|
0a26820081 | ||
|
|
8c32cb7e88 | ||
|
|
34106bdda8 | ||
|
|
c474e0ac92 | ||
|
|
c93eaa49b5 | ||
|
|
c25d0bbac3 | ||
|
|
3353a0836b | ||
|
|
29d7c42fee | ||
|
|
176be81ede | ||
|
|
95effedead | ||
|
|
89cfc182a2 | ||
|
|
c51d82f444 | ||
|
|
cd2c64aff9 | ||
|
|
5f6796b83f | ||
|
|
77f3cd00a9 | ||
|
|
a1f41485f8 | ||
|
|
5afda9e732 | ||
|
|
d181fb11a1 | ||
|
|
37340cbebe | ||
|
|
38cf1e1895 | ||
|
|
d93e276f3f | ||
|
|
778321cb7a | ||
|
|
2259109e32 | ||
|
|
5ceb4c0815 | ||
|
|
3960098e72 | ||
|
|
849df30b50 | ||
|
|
f886f8e677 | ||
|
|
74c55de53d | ||
|
|
65a631a561 |
@@ -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>
|
||||
|
||||
@@ -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 (¤t_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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -78,6 +78,7 @@ demos = files([
|
||||
'revealer.c',
|
||||
'rotated_text.c',
|
||||
'scale.c',
|
||||
'scrollinfo.c',
|
||||
'search_entry.c',
|
||||
'search_entry2.c',
|
||||
'shadertoy.c',
|
||||
|
||||
91
demos/gtk-demo/scrollinfo.c
Normal file
91
demos/gtk-demo/scrollinfo.c
Normal 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;
|
||||
}
|
||||
208
demos/gtk-demo/scrollinfo.ui
Normal file
208
demos/gtk-demo/scrollinfo.ui
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
379
gtk/gtklistheader.c
Normal 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
50
gtk/gtklistheader.h
Normal 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
112
gtk/gtklistheaderbase.c
Normal 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;
|
||||
}
|
||||
|
||||
63
gtk/gtklistheaderbaseprivate.h
Normal file
63
gtk/gtklistheaderbaseprivate.h
Normal 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
|
||||
|
||||
52
gtk/gtklistheaderprivate.h
Normal file
52
gtk/gtklistheaderprivate.h
Normal 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
295
gtk/gtklistheaderwidget.c
Normal 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));
|
||||
}
|
||||
|
||||
61
gtk/gtklistheaderwidgetprivate.h
Normal file
61
gtk/gtklistheaderwidgetprivate.h
Normal 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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ struct _GtkListView
|
||||
|
||||
GtkListItemManager *item_manager;
|
||||
GtkListItemFactory *factory;
|
||||
GtkListItemFactory *header_factory;
|
||||
gboolean show_separators;
|
||||
gboolean single_click_activate;
|
||||
};
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
370
gtk/gtkscrollinfo.c
Normal 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
84
gtk/gtkscrollinfo.h
Normal 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
225
gtk/gtksectionmodel.c
Normal 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
72
gtk/gtksectionmodel.h
Normal 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
|
||||
|
||||
14
gtk/gtksectionmodelprivate.h
Normal file
14
gtk/gtksectionmodelprivate.h
Normal 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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
497
testsuite/gtk/listitemmanager.c
Normal file
497
testsuite/gtk/listitemmanager.c
Normal 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 ();
|
||||
}
|
||||
@@ -129,6 +129,7 @@ internal_tests = [
|
||||
{ 'name': 'texthistory' },
|
||||
{ 'name': 'fnmatch' },
|
||||
{ 'name': 'a11y' },
|
||||
{ 'name': 'listitemmanager' },
|
||||
]
|
||||
|
||||
is_debug = get_option('buildtype').startswith('debug')
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user