Compare commits

...

2 Commits

Author SHA1 Message Date
Matthias Clasen
0f2bce941a fontchooser: Flip models around
Make the fontchooser use a GtkFilterListModel
wrapping a GtkSingleSelection, instead of the
other way around. This exercises the new
selection model support in GtkFilterListModel.
2023-06-04 20:35:50 -04:00
Matthias Clasen
0a7fb26909 filterlistmodel: Implement GtkSelectionModel
Pass through selections from an underlying selection
model.

Tests included.
2023-06-04 20:35:50 -04:00
4 changed files with 419 additions and 29 deletions

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkprivate.h"
#include "gtkselectionmodel.h"
#include "gtksectionmodelprivate.h"
/**
@@ -39,8 +40,12 @@
* [method@Gtk.FilterListModel.set_incremental] for details.
*
* `GtkFilterListModel` passes through sections from the underlying model.
*
* Since 4.12, `GtkFilterListModel` also implements `GtkSelectionModel`
* and passes through selections from the underlying model. Any changes
* to the selection that are done through the filter model will cause
* filtered-out items to be unselected.
*/
enum {
PROP_0,
PROP_FILTER,
@@ -138,6 +143,240 @@ gtk_filter_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_filter_list_model_get_item;
}
static gboolean
gtk_filter_list_model_is_selected (GtkSelectionModel *model,
guint position)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
guint unfiltered;
if (!GTK_IS_SELECTION_MODEL (self->model))
return FALSE;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return FALSE;
case GTK_FILTER_MATCH_ALL:
unfiltered = position;
break;
case GTK_FILTER_MATCH_SOME:
unfiltered = gtk_bitset_get_nth (self->matches, position);
if (unfiltered == 0 && position >= gtk_bitset_get_size (self->matches))
return FALSE;
break;
default:
g_assert_not_reached ();
}
return gtk_selection_model_is_selected (GTK_SELECTION_MODEL (self->model), unfiltered);
}
static GtkBitset *
gtk_filter_list_model_get_selection_in_range (GtkSelectionModel *model,
guint pos,
guint n_items)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
if (!GTK_IS_SELECTION_MODEL (self->model))
return gtk_bitset_new_empty ();
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return gtk_bitset_new_empty ();
case GTK_FILTER_MATCH_ALL:
return gtk_selection_model_get_selection_in_range (GTK_SELECTION_MODEL (self->model),
pos,
n_items);
case GTK_FILTER_MATCH_SOME:
{
GtkBitset *result;
GtkBitset *selected;
unsigned int last;
unsigned int start, end;
if (pos >= gtk_bitset_get_size (self->matches))
return gtk_bitset_new_empty ();
last = MIN (pos + n_items, gtk_bitset_get_size (self->matches));
start = gtk_bitset_get_nth (self->matches, pos);
end = gtk_bitset_get_nth (self->matches, last - 1);
selected = gtk_selection_model_get_selection_in_range (GTK_SELECTION_MODEL (self->model),
start, end - start + 1);
result = gtk_bitset_new_empty ();
for (unsigned int i = pos; i < last; i++)
{
if (gtk_bitset_contains (selected, gtk_bitset_get_nth (self->matches, i)))
gtk_bitset_add (result, i);
}
gtk_bitset_unref (selected);
return result;
}
default:
g_assert_not_reached ();
}
}
static gboolean
gtk_filter_list_model_select_item (GtkSelectionModel *model,
guint position,
gboolean unselect_rest)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
unsigned int unfiltered;
if (!GTK_IS_SELECTION_MODEL (self->model))
return TRUE;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return TRUE;
case GTK_FILTER_MATCH_ALL:
unfiltered = position;
break;
case GTK_FILTER_MATCH_SOME:
unfiltered = gtk_bitset_get_nth (self->matches, position);
if (unfiltered == 0 && position >= gtk_bitset_get_size (self->matches))
return TRUE;
break;
default:
g_assert_not_reached ();
}
return gtk_selection_model_select_item (GTK_SELECTION_MODEL (self->model),
unfiltered,
unselect_rest);
}
static gboolean
gtk_filter_list_model_unselect_item (GtkSelectionModel *model,
guint position)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
unsigned int unfiltered;
if (!GTK_IS_SELECTION_MODEL (self->model))
return TRUE;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return TRUE;
case GTK_FILTER_MATCH_ALL:
unfiltered = position;
break;
case GTK_FILTER_MATCH_SOME:
unfiltered = gtk_bitset_get_nth (self->matches, position);
if (unfiltered == 0 && position >= gtk_bitset_get_size (self->matches))
return TRUE;
break;
default:
g_assert_not_reached ();
}
return gtk_selection_model_unselect_item (GTK_SELECTION_MODEL (self->model),
unfiltered);
}
static gboolean
gtk_filter_list_model_set_selection (GtkSelectionModel *model,
GtkBitset *selected,
GtkBitset *mask)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
if (!GTK_IS_SELECTION_MODEL (self->model))
return TRUE;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return TRUE;
case GTK_FILTER_MATCH_ALL:
return gtk_selection_model_set_selection (GTK_SELECTION_MODEL (self->model),
selected,
mask);
case GTK_FILTER_MATCH_SOME:
{
GtkBitset *selected2;
GtkBitset *mask2;
GtkBitset *unmatched;
GtkBitsetIter iter;
unsigned int unfiltered;
gboolean ret;
selected2 = gtk_bitset_new_empty ();
mask2 = gtk_bitset_new_empty ();
if (gtk_bitset_iter_init_first (&iter, self->matches, &unfiltered))
{
unsigned int i = 0;
do
{
if (gtk_bitset_contains (selected, i))
gtk_bitset_add (selected2, unfiltered);
if (gtk_bitset_contains (mask, i))
gtk_bitset_add (mask2, unfiltered);
i++;
}
while (gtk_bitset_iter_next (&iter, &unfiltered));
}
unmatched = gtk_bitset_new_range (0, g_list_model_get_n_items (self->model));
gtk_bitset_subtract (unmatched, self->matches);
gtk_bitset_union (mask2, unmatched);
ret = gtk_selection_model_set_selection (GTK_SELECTION_MODEL (self->model),
selected2,
mask2);
gtk_bitset_unref (unmatched);
gtk_bitset_unref (selected2);
gtk_bitset_unref (mask2);
return ret;
}
default:
g_assert_not_reached ();
}
}
static void
gtk_filter_list_model_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = gtk_filter_list_model_is_selected;
iface->select_item = gtk_filter_list_model_select_item;
iface->unselect_item = gtk_filter_list_model_unselect_item;
iface->get_selection_in_range = gtk_filter_list_model_get_selection_in_range;
iface->set_selection = gtk_filter_list_model_set_selection;
}
static void
gtk_filter_list_model_get_section (GtkSectionModel *model,
guint position,
@@ -190,6 +429,39 @@ gtk_filter_list_model_get_section (GtkSectionModel *model,
*out_end = *out_start + gtk_bitset_get_size_in_range (self->matches, start, end - 1);
}
static void
gtk_filter_list_model_selection_changed_cb (GtkSelectionModel *model,
unsigned int position,
unsigned int n_items,
gpointer user_data)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (user_data);
unsigned int start, end;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
return;
case GTK_FILTER_MATCH_ALL:
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), position, n_items);
break;
case GTK_FILTER_MATCH_SOME:
if (position > 0)
start = gtk_bitset_get_size_in_range (self->matches, 0, position - 1);
else
start = 0;
end = gtk_bitset_get_size_in_range (self->matches, 0, position + n_items - 1);
if (end - start > 0)
gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), start, end - start);
break;
default:
g_assert_not_reached ();
}
}
static void
gtk_filter_list_model_sections_changed_cb (GtkSectionModel *model,
unsigned int position,
@@ -231,6 +503,7 @@ gtk_filter_list_model_section_model_init (GtkSectionModelInterface *iface)
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 (GTK_TYPE_SELECTION_MODEL, gtk_filter_list_model_selection_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_filter_list_model_section_model_init))
static gboolean
@@ -498,6 +771,7 @@ gtk_filter_list_model_clear_model (GtkFilterListModel *self)
gtk_filter_list_model_stop_filtering (self);
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_selection_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_sections_changed_cb, self);
g_clear_object (&self->model);
if (self->matches)
@@ -863,6 +1137,8 @@ gtk_filter_list_model_set_model (GtkFilterListModel *self,
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self);
if (GTK_IS_SELECTION_MODEL (model))
g_signal_connect (model, "selection-changed", G_CALLBACK (gtk_filter_list_model_selection_changed_cb), self);
if (GTK_IS_SECTION_MODEL (model))
g_signal_connect (model, "sections-changed", G_CALLBACK (gtk_filter_list_model_sections_changed_cb), self);
if (removed == 0)

View File

@@ -1162,7 +1162,7 @@ update_fontlist (GtkFontChooserWidget *self)
model = G_LIST_MODEL (gtk_slice_list_model_new (model, 0, 20));
gtk_widget_add_tick_callback (GTK_WIDGET (self), add_to_fontlist, g_object_ref (model), g_object_unref);
gtk_filter_list_model_set_model (self->filter_model, model);
gtk_single_selection_set_model (self->selection, model);
g_object_unref (model);
}

View File

@@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<object class="GtkSingleSelection" id="selection">
<signal name="notify::selected-item" handler="selection_changed_cb" object="GtkFontChooserWidget" swapped="0"/>
<signal name="items-changed" handler="rows_changed_cb" object="GtkFontChooserWidget" swapped="1"/>
<property name="model">
<object class="GtkFilterListModel" id="filter_model">
<signal name="notify::pending" handler="rows_changed_cb" object="GtkFontChooserWidget" swapped="1"/>
<property name="incremental">1</property>
<property name="filter">
<object class="GtkEveryFilter" id="multi_filter">
<child>
<object class="GtkStringFilter">
<binding name="search">
<lookup name="text">search_entry</lookup>
</binding>
<property name="expression">
<closure type="gchararray" swapped="1" function="get_font_name"/>
</property>
</object>
</child>
<child>
<object class="GtkCustomFilter" id="custom_filter"/>
</child>
<child>
<object class="GtkCustomFilter" id="user_filter"/>
</child>
<object class="GtkFilterListModel" id="filter_model">
<signal name="notify::pending" handler="rows_changed_cb" object="GtkFontChooserWidget" swapped="1"/>
<property name="incremental">1</property>
<property name="filter">
<object class="GtkEveryFilter" id="multi_filter">
<child>
<object class="GtkStringFilter">
<binding name="search">
<lookup name="text">search_entry</lookup>
</binding>
<property name="expression">
<closure type="gchararray" swapped="1" function="get_font_name"/>
</property>
</object>
</property>
</child>
<child>
<object class="GtkCustomFilter" id="custom_filter"/>
</child>
<child>
<object class="GtkCustomFilter" id="user_filter"/>
</child>
</object>
</property>
<property name="model">
<object class="GtkSingleSelection" id="selection">
<signal name="notify::selected-item" handler="selection_changed_cb" object="GtkFontChooserWidget" swapped="0"/>
<signal name="items-changed" handler="rows_changed_cb" object="GtkFontChooserWidget" swapped="1"/>
</object>
</property>
</object>
@@ -141,7 +141,7 @@
<property name="has-frame">1</property>
<child>
<object class="GtkListView" id="family_face_list">
<property name="model">selection</property>
<property name="model">filter_model</property>
<signal name="activate" handler="row_activated_cb" swapped="no"/>
<property name="factory">
<object class="GtkBuilderListItemFactory">

View File

@@ -23,6 +23,7 @@
static GQuark number_quark;
static GQuark changes_quark;
static GQuark selection_quark;
static guint
get (GListModel *model,
@@ -52,6 +53,25 @@ model_to_string (GListModel *model)
return g_string_free (string, FALSE);
}
static char *
selection_to_string (GListModel *model)
{
GString *string = g_string_new (NULL);
guint i;
for (i = 0; i < g_list_model_get_n_items (model); i++)
{
if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), i))
continue;
if (string->len > 0)
g_string_append (string, " ");
g_string_append_printf (string, "%u", get (model, i));
}
return g_string_free (string, FALSE);
}
static GListStore *
new_store (guint start,
guint end,
@@ -93,6 +113,14 @@ add (GListStore *store,
g_string_set_size (changes, 0); \
}G_STMT_END
#define assert_selection(model, expected) G_STMT_START{ \
char *s = selection_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, s, "==", expected); \
g_free (s); \
}G_STMT_END
static GListStore *
new_empty_store (void)
{
@@ -561,6 +589,90 @@ test_sections (void)
g_object_unref (sorted);
}
static void
selection_changed (GListModel *model,
guint position,
guint n_items,
GString *changes)
{
if (changes->len)
g_string_append (changes, ", ");
g_string_append_printf (changes, "%u:%u", position, n_items);
}
#define assert_selection_changes(model, expected) G_STMT_START{ \
GString *chchanges = g_object_get_qdata (G_OBJECT (model), selection_quark); \
if (!g_str_equal (chchanges->str, expected)) \
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#model " == " #expected, changes->str, "==", expected); \
g_string_set_size (chchanges, 0); \
}G_STMT_END
static void
test_selections (void)
{
GListStore *store;
GtkSelectionModel *selection;
GtkFilter *filter;
GtkFilterListModel *filtermodel;
GString *changes;
store = new_store (1, 10, 1);
selection = GTK_SELECTION_MODEL (gtk_multi_selection_new (G_LIST_MODEL (store)));
filter = GTK_FILTER (gtk_custom_filter_new (is_larger_than, GUINT_TO_POINTER (5), NULL));
filtermodel = gtk_filter_list_model_new (G_LIST_MODEL (selection), filter);
changes = g_string_new ("");
g_object_set_qdata_full (G_OBJECT (filtermodel), selection_quark, changes, free_changes);
g_signal_connect (filtermodel, "selection-changed", G_CALLBACK (selection_changed), changes);
assert_selection (filtermodel, "");
g_assert_false (gtk_selection_model_is_selected (GTK_SELECTION_MODEL (filtermodel), 3));
gtk_selection_model_select_item (selection, 3, FALSE);
assert_selection (selection, "4");
assert_selection (filtermodel, "");
g_assert_false (gtk_selection_model_is_selected (GTK_SELECTION_MODEL (filtermodel), 3));
assert_selection_changes (filtermodel, "");
gtk_selection_model_select_item (selection, 8, FALSE);
assert_selection (selection, "4 9");
assert_selection (filtermodel, "9");
g_assert_true (gtk_selection_model_is_selected (GTK_SELECTION_MODEL (filtermodel), 3));
assert_selection_changes (filtermodel, "3:1");
gtk_selection_model_select_item (GTK_SELECTION_MODEL (filtermodel), 0, FALSE);
assert_selection (selection, "6 9");
assert_selection (filtermodel, "6 9");
assert_selection_changes (filtermodel, "0:1");
gtk_selection_model_select_item (GTK_SELECTION_MODEL (filtermodel), 1, TRUE);
assert_selection (selection, "7");
assert_selection (filtermodel, "7");
assert_selection_changes (filtermodel, "0:4");
gtk_selection_model_select_all (GTK_SELECTION_MODEL (filtermodel));
assert_selection (selection, "6 7 8 9 10");
assert_selection (filtermodel, "6 7 8 9 10");
assert_selection_changes (filtermodel, "0:5");
gtk_selection_model_unselect_range (GTK_SELECTION_MODEL (filtermodel), 1, 3);
assert_selection (selection, "6 10");
assert_selection (filtermodel, "6 10");
assert_selection_changes (filtermodel, "1:3");
gtk_selection_model_select_range (GTK_SELECTION_MODEL (filtermodel), 0, 3, TRUE);
assert_selection (selection, "6 7 8");
assert_selection (filtermodel, "6 7 8");
assert_selection_changes (filtermodel, "1:4");
g_object_unref (filtermodel);
}
int
main (int argc, char *argv[])
{
@@ -569,6 +681,7 @@ main (int argc, char *argv[])
number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
selection_quark = g_quark_from_static_string ("Mana mana, badibidibi");
g_test_add_func ("/filterlistmodel/create", test_create);
g_test_add_func ("/filterlistmodel/empty_set_filter", test_empty_set_filter);
@@ -577,6 +690,7 @@ main (int argc, char *argv[])
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);
g_test_add_func ("/filterlistmodel/selections", test_selections);
return g_test_run ();
}