selectionmodels: Add set_model() support

Now that we don't care about item types anymore, we can make the child
models settable.

We try to retain the selection, even when the model changes.
This commit is contained in:
Benjamin Otte 2020-07-05 01:08:12 +02:00
parent 5080730728
commit 795d3122cc
9 changed files with 296 additions and 34 deletions

View File

@ -422,6 +422,7 @@ gtk_selection_model_get_type
GtkNoSelection
gtk_no_selection_new
gtk_no_selection_get_model
gtk_no_selection_set_model
<SUBSECTION Private>
gtk_no_selection_get_type
</SECTION>
@ -433,6 +434,7 @@ GtkSingleSelection
GTK_INVALID_LIST_POSITION
gtk_single_selection_new
gtk_single_selection_get_model
gtk_single_selection_set_model
gtk_single_selection_get_selected
gtk_single_selection_set_selected
gtk_single_selection_get_selected_item
@ -450,6 +452,7 @@ gtk_single_selection_get_type
GtkMultiSelection
gtk_multi_selection_new
gtk_multi_selection_get_model
gtk_multi_selection_set_model
<SUBSECTION Private>
gtk_multi_selection_get_type
</SECTION>

View File

@ -70,6 +70,9 @@ gtk_multi_selection_get_n_items (GListModel *list)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
@ -79,6 +82,9 @@ gtk_multi_selection_get_item (GListModel *list,
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
if (self->model == NULL)
return NULL;
return g_list_model_get_item (self->model, position);
}
@ -172,7 +178,7 @@ gtk_multi_selection_set_selection (GtkSelectionModel *model,
max = gtk_bitset_get_maximum (changes);
/* sanity check */
n_items = g_list_model_get_n_items (self->model);
n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
if (max >= n_items)
{
gtk_bitset_remove_range_closed (changes, n_items, max);
@ -289,12 +295,7 @@ gtk_multi_selection_set_property (GObject *object,
switch (prop_id)
{
case PROP_MODEL:
self->model = g_value_dup_object (value);
g_warn_if_fail (self->model != NULL);
g_signal_connect (self->model,
"items-changed",
G_CALLBACK (gtk_multi_selection_items_changed_cb),
self);
gtk_multi_selection_set_model (self, g_value_get_object (value));
break;
default:
@ -355,7 +356,7 @@ gtk_multi_selection_class_init (GtkMultiSelectionClass *klass)
P_("Model"),
P_("List managed by this selection"),
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
@ -400,3 +401,49 @@ gtk_multi_selection_get_model (GtkMultiSelection *self)
return self->model;
}
/**
* gtk_multi_selection_set_model:
* @self: a #GtkMultiSelection
* @model: (allow-none): A #GListModel to wrap
*
* Sets the model that @self should wrap. If @model is %NULL, @self
* will be empty.
**/
void
gtk_multi_selection_set_model (GtkMultiSelection *self,
GListModel *model)
{
guint n_items_before;
g_return_if_fail (GTK_IS_MULTI_SELECTION (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
gtk_multi_selection_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (self->model,
"items-changed",
G_CALLBACK (gtk_multi_selection_items_changed_cb),
self);
gtk_multi_selection_items_changed_cb (self->model,
0,
n_items_before,
g_list_model_get_n_items (model),
self);
}
else
{
gtk_bitset_remove_all (self->selected);
g_hash_table_remove_all (self->items);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items_before, 0);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}

View File

@ -35,6 +35,9 @@ GListModel * gtk_multi_selection_new (GListModel *mo
GDK_AVAILABLE_IN_ALL
GListModel * gtk_multi_selection_get_model (GtkMultiSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_multi_selection_set_model (GtkMultiSelection *self,
GListModel *model);
G_END_DECLS

View File

@ -68,15 +68,21 @@ gtk_no_selection_get_n_items (GListModel *list)
{
GtkNoSelection *self = GTK_NO_SELECTION (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_no_selection_get_item (GListModel *list,
guint position)
guint position)
{
GtkNoSelection *self = GTK_NO_SELECTION (list);
if (self->model == NULL)
return NULL;
return g_list_model_get_item (self->model, position);
}
@ -130,9 +136,9 @@ gtk_no_selection_clear_model (GtkNoSelection *self)
static void
gtk_no_selection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkNoSelection *self = GTK_NO_SELECTION (object);
@ -140,10 +146,7 @@ gtk_no_selection_set_property (GObject *object,
switch (prop_id)
{
case PROP_MODEL:
gtk_no_selection_clear_model (self);
self->model = g_value_dup_object (value);
g_signal_connect_swapped (self->model, "items-changed",
G_CALLBACK (g_list_model_items_changed), self);
gtk_no_selection_set_model (self, g_value_get_object (value));
break;
default:
@ -201,7 +204,7 @@ gtk_no_selection_class_init (GtkNoSelectionClass *klass)
P_("The model"),
P_("The model being managed"),
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
@ -245,3 +248,40 @@ gtk_no_selection_get_model (GtkNoSelection *self)
return self->model;
}
/**
* gtk_no_selection_set_model:
* @self: a #GtkNoSelection
* @model: (allow-none): A #GListModel to wrap
*
* Sets the model that @self should wrap. If @model is %NULL, this
* model will be empty.
**/
void
gtk_no_selection_set_model (GtkNoSelection *self,
GListModel *model)
{
guint n_items_before;
g_return_if_fail (GTK_IS_NO_SELECTION (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
gtk_no_selection_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect_swapped (self->model, "items-changed",
G_CALLBACK (g_list_model_items_changed), self);
}
g_list_model_items_changed (G_LIST_MODEL (self),
0,
n_items_before,
model ? g_list_model_get_n_items (self->model) : 0);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}

View File

@ -34,6 +34,9 @@ GtkNoSelection * gtk_no_selection_new (GListModel
GDK_AVAILABLE_IN_ALL
GListModel * gtk_no_selection_get_model (GtkNoSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_no_selection_set_model (GtkNoSelection *self,
GListModel *model);
G_END_DECLS

View File

@ -80,6 +80,9 @@ gtk_single_selection_get_n_items (GListModel *list)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
@ -89,6 +92,9 @@ gtk_single_selection_get_item (GListModel *list,
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
if (self->model == NULL)
return NULL;
return g_list_model_get_item (self->model, position);
}
@ -305,12 +311,7 @@ gtk_single_selection_set_property (GObject *object,
break;
case PROP_MODEL:
gtk_single_selection_clear_model (self);
self->model = g_value_dup_object (value);
g_signal_connect (self->model, "items-changed",
G_CALLBACK (gtk_single_selection_items_changed_cb), self);
if (self->autoselect)
gtk_single_selection_set_selected (self, 0);
gtk_single_selection_set_model (self, g_value_get_object (value));
break;
case PROP_SELECTED:
@ -438,7 +439,7 @@ gtk_single_selection_class_init (GtkSingleSelectionClass *klass)
P_("The model"),
P_("The model being managed"),
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
@ -484,6 +485,62 @@ gtk_single_selection_get_model (GtkSingleSelection *self)
return self->model;
}
/**
* gtk_single_selection_set_model:
* @self: a #GtkSingleSelection
* @model: (allow-none): A #GListModel to wrap
*
* Sets the model that @self should wrap. If @model is %NULL, @self
* will be empty.
**/
void
gtk_single_selection_set_model (GtkSingleSelection *self,
GListModel *model)
{
guint n_items_before;
g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
g_object_freeze_notify (G_OBJECT (self));
n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
gtk_single_selection_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (self->model, "items-changed",
G_CALLBACK (gtk_single_selection_items_changed_cb), self);
gtk_single_selection_items_changed_cb (self->model,
0,
n_items_before,
g_list_model_get_n_items (model),
self);
}
else
{
if (self->selected != GTK_INVALID_LIST_POSITION)
{
self->selected = GTK_INVALID_LIST_POSITION;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
if (self->selected_item)
{
g_clear_object (&self->selected_item);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
}
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items_before, 0);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_single_selection_get_selected:
* @self: a #GtkSingleSelection

View File

@ -35,22 +35,25 @@ GtkSingleSelection * gtk_single_selection_new (GListModel
GDK_AVAILABLE_IN_ALL
GListModel * gtk_single_selection_get_model (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
guint gtk_single_selection_get_selected (GtkSingleSelection *self);
void gtk_single_selection_set_model (GtkSingleSelection *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_selected (GtkSingleSelection *self,
guint position);
guint gtk_single_selection_get_selected (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
gpointer gtk_single_selection_get_selected_item (GtkSingleSelection *self);
void gtk_single_selection_set_selected (GtkSingleSelection *self,
guint position);
GDK_AVAILABLE_IN_ALL
gboolean gtk_single_selection_get_autoselect (GtkSingleSelection *self);
gpointer gtk_single_selection_get_selected_item (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_autoselect (GtkSingleSelection *self,
gboolean autoselect);
gboolean gtk_single_selection_get_autoselect (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_single_selection_get_can_unselect (GtkSingleSelection *self);
void gtk_single_selection_set_autoselect (GtkSingleSelection *self,
gboolean autoselect);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
gboolean can_unselect);
gboolean gtk_single_selection_get_can_unselect (GtkSingleSelection *self);
GDK_AVAILABLE_IN_ALL
void gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
gboolean can_unselect);
G_END_DECLS

View File

@ -613,6 +613,59 @@ test_selection_filter (void)
g_object_unref (store);
g_object_unref (selection);
}
static void
test_set_model (void)
{
GtkSelectionModel *selection;
GListStore *store;
GListModel *m1, *m2;
gboolean ret;
store = new_store (1, 5, 1);
m1 = G_LIST_MODEL (store);
m2 = G_LIST_MODEL (gtk_slice_list_model_new (m1, 0, 3));
selection = new_model (store);
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_range (selection, 1, 3, FALSE);
g_assert_true (ret);
assert_selection (selection, "2 3 4");
assert_selection_changes (selection, "1:3");
/* we retain the selected item across model changes */
gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m2);
assert_changes (selection, "0-5+3");
assert_selection (selection, "2 3");
assert_selection_changes (selection, "");
gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), NULL);
assert_changes (selection, "0-3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m2);
assert_changes (selection, "0+3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
ret = gtk_selection_model_select_all (selection);
g_assert_true (ret);
assert_selection (selection, "1 2 3");
assert_selection_changes (selection, "0:3");
/* we retain no selected item across model changes */
gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m1);
assert_changes (selection, "0-3+5");
assert_selection (selection, "1 2 3");
assert_selection_changes (selection, "");
g_object_unref (m2);
g_object_unref (m1);
g_object_unref (selection);
}
int
main (int argc, char *argv[])
{
@ -633,6 +686,7 @@ main (int argc, char *argv[])
g_test_add_func ("/multiselection/readd", test_readd);
g_test_add_func ("/multiselection/set_selection", test_set_selection);
g_test_add_func ("/multiselection/selection-filter", test_selection_filter);
g_test_add_func ("/multiselection/set-model", test_set_model);
return g_test_run ();
}

View File

@ -644,6 +644,57 @@ test_query_range (void)
g_object_unref (selection);
}
static void
test_set_model (void)
{
GtkSelectionModel *selection;
GListStore *store;
GListModel *m1, *m2;
store = new_store (1, 5, 1);
m1 = G_LIST_MODEL (store);
m2 = G_LIST_MODEL (gtk_slice_list_model_new (m1, 0, 3));
selection = new_model (store, TRUE, TRUE);
assert_selection (selection, "1");
assert_selection_changes (selection, "");
/* we retain the selected item across model changes */
gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
assert_changes (selection, "0-5+3");
assert_selection (selection, "1");
assert_selection_changes (selection, "");
gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), NULL);
assert_changes (selection, "0-3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), FALSE);
gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
assert_changes (selection, "0+3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
/* we retain no selected item across model changes */
gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m1);
assert_changes (selection, "0-3+5");
assert_selection (selection, "");
assert_selection_changes (selection, "");
gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (selection), 4);
assert_selection (selection, "5");
assert_selection_changes (selection, "4:1");
gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
assert_changes (selection, "0-5+3");
assert_selection (selection, "");
assert_selection_changes (selection, "");
g_object_unref (m2);
g_object_unref (m1);
g_object_unref (selection);
}
int
main (int argc, char *argv[])
{
@ -662,6 +713,7 @@ main (int argc, char *argv[])
g_test_add_func ("/singleselection/persistence", test_persistence);
g_test_add_func ("/singleselection/query-range", test_query_range);
g_test_add_func ("/singleselection/changes", test_changes);
g_test_add_func ("/singleselection/set-model", test_set_model);
return g_test_run ();
}