Compare commits
7 Commits
wip/kabus/
...
wip/otte/s
Author | SHA1 | Date | |
---|---|---|---|
|
df8475155c | ||
|
e2547be696 | ||
|
c696ff6f3c | ||
|
4cdc078db4 | ||
|
59f801a02a | ||
|
f4106a8fe2 | ||
|
5abaa9c989 |
@@ -77,6 +77,11 @@ gtk_string_sorter_get_key (GtkExpression *expression,
|
||||
return NULL;
|
||||
|
||||
string = g_value_get_string (&value);
|
||||
if (string == NULL)
|
||||
{
|
||||
g_value_unset (&value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ignore_case)
|
||||
s = g_utf8_casefold (string, -1);
|
||||
|
593
testsuite/gtk/gtklistmodelvalidator.c
Normal file
593
testsuite/gtk/gtklistmodelvalidator.c
Normal file
@@ -0,0 +1,593 @@
|
||||
/*
|
||||
* 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 "gtklistmodelvalidator.h"
|
||||
|
||||
#include "gtkprivate.h"
|
||||
|
||||
#define GDK_ARRAY_TYPE_NAME Items
|
||||
#define GDK_ARRAY_NAME items
|
||||
#define GDK_ARRAY_ELEMENT_TYPE gpointer
|
||||
#define GDK_ARRAY_FREE_FUNC g_object_unref
|
||||
#define GDK_ARRAY_NO_MEMSET 1
|
||||
|
||||
#include "gdk/gdkarrayimpl.c"
|
||||
|
||||
#define GDK_ARRAY_TYPE_NAME Sections
|
||||
#define GDK_ARRAY_NAME sections
|
||||
#define GDK_ARRAY_ELEMENT_TYPE guint
|
||||
#define GDK_ARRAY_NO_MEMSET 1
|
||||
|
||||
#include "gdk/gdkarrayimpl.c"
|
||||
|
||||
/*<private>
|
||||
* GtkListModelValidator:
|
||||
*
|
||||
* `GtkListModelValidator` is an object that can be attached to a given list model
|
||||
* and ten checks that it conforms to the listmodel API guarantees.
|
||||
*
|
||||
* This is useful when implementing a new listmodel or when writing a testsuite.
|
||||
*
|
||||
* Note that these checks are expensive and can cause signficant slow downs. So it
|
||||
* is recommended to only use them when testing.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_MODEL,
|
||||
NUM_PROPERTIES
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GTK_LIST_VALIDATION_CHANGES = (1 << 0),
|
||||
GTK_LIST_VALIDATION_SECTION_CHANGES = (1 << 1),
|
||||
GTK_LIST_VALIDATION_MINIMAL_CHANGES = (1 << 2),
|
||||
GTK_LIST_VALIDATION_N_ITEMS = (1 << 3),
|
||||
GTK_LIST_VALIDATION_N_ITEMS_MINIMAL_NOTIFY = (1 << 4),
|
||||
} GtkListValidationFlags;
|
||||
|
||||
struct _GtkListModelValidator
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GListModel *model;
|
||||
GtkListValidationFlags flags;
|
||||
|
||||
guint notified_n_items;
|
||||
Items items;
|
||||
Sections sections;
|
||||
};
|
||||
|
||||
struct _GtkListModelValidatorClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE (GtkListModelValidator, gtk_list_model_validator, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_error (GtkListModelValidator *self,
|
||||
GtkListValidationFlags flags,
|
||||
const char *format,
|
||||
...) G_GNUC_PRINTF(3, 4);
|
||||
static void
|
||||
gtk_list_model_validator_error (GtkListModelValidator *self,
|
||||
GtkListValidationFlags flags,
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start (args, format);
|
||||
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, format, args);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_self_check_sections (GtkListModelValidator *self)
|
||||
{
|
||||
guint sum, i;
|
||||
|
||||
sum = 0;
|
||||
for (i = 0; i < sections_get_size (&self->sections); i++)
|
||||
{
|
||||
g_assert (sections_get (&self->sections, i) > 0);
|
||||
sum += sections_get (&self->sections, i);
|
||||
}
|
||||
|
||||
g_assert (sum == items_get_size (&self->items));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_validate_different (GtkListModelValidator *self,
|
||||
guint self_position,
|
||||
guint model_position)
|
||||
{
|
||||
gpointer o = g_list_model_get_item (self->model, model_position);
|
||||
if (o == items_get (&self->items, self_position))
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_MINIMAL_CHANGES,
|
||||
"item at position %u did not change but was part of items-changed",
|
||||
self_position);
|
||||
g_object_unref (o);
|
||||
}
|
||||
|
||||
static gsize
|
||||
gtk_list_model_validator_find_section_index (GtkListModelValidator *self,
|
||||
guint position,
|
||||
guint *offset)
|
||||
{
|
||||
gsize i;
|
||||
|
||||
for (i = 0; i < sections_get_size (&self->sections); i++)
|
||||
{
|
||||
guint items = sections_get (&self->sections, i);
|
||||
if (position < items)
|
||||
{
|
||||
if (offset)
|
||||
*offset = position;
|
||||
return i;
|
||||
}
|
||||
position -= items;
|
||||
}
|
||||
|
||||
if (offset)
|
||||
*offset = position;
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_validate_section_range (GtkListModelValidator *self,
|
||||
guint position,
|
||||
guint n_items)
|
||||
{
|
||||
guint section, section_items, offset, start, end, i;
|
||||
|
||||
if (n_items == 0)
|
||||
return;
|
||||
|
||||
section = gtk_list_model_validator_find_section_index (self, position, &offset);
|
||||
section_items = sections_get (&self->sections, section);
|
||||
|
||||
for (i = 0; i < n_items; i++)
|
||||
{
|
||||
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), position + i, &start, &end);
|
||||
if (start != position + i - offset ||
|
||||
end != position + i - offset + section_items)
|
||||
{
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_SECTION_CHANGES,
|
||||
"item at %u reports wrong section: [%u, %u) but should be [%u, %u)",
|
||||
position + i,
|
||||
start, end,
|
||||
position + i - offset, position + i - offset + section_items);
|
||||
}
|
||||
offset++;
|
||||
if (offset == section_items)
|
||||
{
|
||||
section++;
|
||||
if (section < sections_get_size (&self->sections))
|
||||
section_items = sections_get (&self->sections, section);
|
||||
else
|
||||
section_items = 0;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_validate_range (GtkListModelValidator *self,
|
||||
guint self_position,
|
||||
guint model_position,
|
||||
guint n_items)
|
||||
{
|
||||
guint i;
|
||||
|
||||
if (n_items == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < n_items; i++)
|
||||
{
|
||||
gpointer o = g_list_model_get_item (self->model, model_position + i);
|
||||
if (o != items_get (&self->items, self_position + i))
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_CHANGES,
|
||||
"item at %u did change but was not included in items-changed",
|
||||
self_position + i);
|
||||
g_object_unref (o);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_invalidate_sections (GtkListModelValidator *self,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added)
|
||||
{
|
||||
guint section, offset, remaining;
|
||||
|
||||
section = gtk_list_model_validator_find_section_index (self, position, &offset);
|
||||
|
||||
/* first, delete all the removed items from the sections */
|
||||
remaining = removed;
|
||||
while (remaining > 0)
|
||||
{
|
||||
guint section_items = sections_get (&self->sections, section);
|
||||
if (remaining >= section_items - offset)
|
||||
{
|
||||
if (offset)
|
||||
{
|
||||
remaining -= section_items - offset;
|
||||
*sections_index (&self->sections, section) = offset;
|
||||
section++;
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
remaining -= section_items;
|
||||
sections_splice (&self->sections, section, 1, FALSE, NULL, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*sections_index (&self->sections, section) -= remaining;
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* now add all the new items into their sections */
|
||||
if (offset > 0 && sections_get (&self->sections, section) > offset)
|
||||
{
|
||||
*sections_index (&self->sections, section) += added;
|
||||
}
|
||||
else if (added > 0)
|
||||
{
|
||||
guint start, end;
|
||||
guint pos = position;
|
||||
|
||||
remaining = added;
|
||||
if (offset == 0 && section > 0)
|
||||
{
|
||||
section--;
|
||||
offset = sections_get (&self->sections, section);
|
||||
}
|
||||
|
||||
if (offset)
|
||||
g_assert (offset == sections_get (&self->sections, section));
|
||||
else
|
||||
g_assert (section == 0);
|
||||
|
||||
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), pos, &start, &end);
|
||||
if (start < pos)
|
||||
{
|
||||
g_assert (remaining >= end - start - offset);
|
||||
remaining -= end - start - offset;
|
||||
*sections_index (&self->sections, section) = end - start;
|
||||
section++;
|
||||
pos = end;
|
||||
}
|
||||
else if (offset)
|
||||
section++;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), pos, &start, &end);
|
||||
if (end - start <= remaining)
|
||||
{
|
||||
sections_splice (&self->sections, section, 0, FALSE, (guint[1]) { end - start }, 1);
|
||||
section++;
|
||||
remaining -= end - start;
|
||||
pos = end;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert (end - start == remaining + sections_get (&self->sections, section));
|
||||
*sections_index (&self->sections, section) = end - start;
|
||||
section++;
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gtk_list_model_validator_self_check_sections (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_sections_changed_cb (GtkSectionModel *model,
|
||||
guint position,
|
||||
guint n_items,
|
||||
GtkListModelValidator *self)
|
||||
{
|
||||
gtk_list_model_validator_invalidate_sections (self, position, n_items, n_items);
|
||||
|
||||
gtk_list_model_validator_validate_section_range (self, 0, items_get_size (&self->items));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_items_changed_cb (GListModel *model,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added,
|
||||
GtkListModelValidator *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
if (self->notified_n_items != items_get_size (&self->items))
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_N_ITEMS,
|
||||
"notify::n-items wasn't emitted before new modifications: %u, should be %zu",
|
||||
self->notified_n_items, items_get_size (&self->items));
|
||||
|
||||
gtk_list_model_validator_validate_range (self, 0, 0, position);
|
||||
gtk_list_model_validator_validate_range (self,
|
||||
position + removed,
|
||||
position + added,
|
||||
items_get_size (&self->items) - removed - position);
|
||||
if (removed > 0 && added > 0)
|
||||
{
|
||||
gtk_list_model_validator_validate_different (self, position, position);
|
||||
gtk_list_model_validator_validate_different (self, position + removed - 1, position + added - 1);
|
||||
}
|
||||
|
||||
items_splice (&self->items, position, removed, FALSE, NULL, added);
|
||||
for (i = 0; i < added; i++)
|
||||
{
|
||||
*items_index (&self->items, position + i) = g_list_model_get_item (model, position + i);
|
||||
}
|
||||
|
||||
if (GTK_IS_SECTION_MODEL (model))
|
||||
{
|
||||
gtk_list_model_validator_invalidate_sections (self, position, removed, added);
|
||||
|
||||
/* Should we only validate unchanged items here?
|
||||
* Because this also validates the newly added ones */
|
||||
gtk_list_model_validator_validate_section_range (self, 0, items_get_size (&self->items));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_notify_n_items_cb (GListModel *model,
|
||||
GParamSpec *pspec,
|
||||
GtkListModelValidator *self)
|
||||
{
|
||||
guint new_n_items = g_list_model_get_n_items (model);
|
||||
|
||||
if (items_get_size (&self->items) != new_n_items)
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_N_ITEMS,
|
||||
"notify::n-items reports wrong item count: %u, should be %zu",
|
||||
new_n_items, items_get_size (&self->items));
|
||||
|
||||
if (self->notified_n_items == new_n_items)
|
||||
gtk_list_model_validator_error (self, GTK_LIST_VALIDATION_N_ITEMS_MINIMAL_NOTIFY,
|
||||
"notify::n-items unchanged from last emission: %u items",
|
||||
new_n_items);
|
||||
|
||||
self->notified_n_items = new_n_items;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkListModelValidator *self = GTK_LIST_MODEL_VALIDATOR (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MODEL:
|
||||
gtk_list_model_validator_set_model (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkListModelValidator *self = GTK_LIST_MODEL_VALIDATOR (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MODEL:
|
||||
g_value_set_object (value, self->model);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_weak_unref_cb (gpointer data,
|
||||
GObject *not_the_model_anymore)
|
||||
{
|
||||
GtkListModelValidator *self = data;
|
||||
|
||||
g_assert (G_OBJECT (self->model) == not_the_model_anymore);
|
||||
|
||||
items_set_size (&self->items, 0);
|
||||
self->model = NULL;
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
|
||||
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_clear_model (GtkListModelValidator *self)
|
||||
{
|
||||
if (self->model == NULL)
|
||||
return;
|
||||
|
||||
items_set_size (&self->items, 0);
|
||||
sections_set_size (&self->sections, 0);
|
||||
g_signal_handlers_disconnect_by_func (self->model, gtk_list_model_validator_items_changed_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->model, gtk_list_model_validator_notify_n_items_cb, self);
|
||||
if (GTK_IS_SECTION_MODEL (self->model))
|
||||
g_signal_handlers_disconnect_by_func (self->model, gtk_list_model_validator_sections_changed_cb, self);
|
||||
g_object_weak_unref (G_OBJECT (self->model), gtk_list_model_validator_weak_unref_cb, self);
|
||||
self->model = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_dispose (GObject *object)
|
||||
{
|
||||
GtkListModelValidator *self = GTK_LIST_MODEL_VALIDATOR (object);
|
||||
|
||||
g_assert (self->model == NULL);
|
||||
|
||||
G_OBJECT_CLASS (gtk_list_model_validator_parent_class)->dispose (object);
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_class_init (GtkListModelValidatorClass *class)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
||||
|
||||
gobject_class->set_property = gtk_list_model_validator_set_property;
|
||||
gobject_class->get_property = gtk_list_model_validator_get_property;
|
||||
gobject_class->dispose = gtk_list_model_validator_dispose;
|
||||
|
||||
/*<private>
|
||||
* GtkListModelValidator:model: (attributes org.gtk.Property.get=gtk_list_model_validator_get_model org.gtk.Property.set=gtk_list_model_validator_set_model)
|
||||
*
|
||||
* Child model to validate.
|
||||
*/
|
||||
properties[PROP_MODEL] =
|
||||
g_param_spec_object ("model", NULL, NULL,
|
||||
G_TYPE_LIST_MODEL,
|
||||
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_model_validator_init (GtkListModelValidator *self)
|
||||
{
|
||||
items_init (&self->items);
|
||||
sections_init (&self->sections);
|
||||
}
|
||||
|
||||
/*<private>
|
||||
* gtk_list_model_validator_new:
|
||||
* @model: (transfer full) (nullable): The model to use
|
||||
*
|
||||
* Creates a new validation model.
|
||||
*
|
||||
* Returns: A new `GtkListModelValidator`
|
||||
*/
|
||||
GtkListModelValidator *
|
||||
gtk_list_model_validator_new (GListModel *model)
|
||||
{
|
||||
GtkListModelValidator *self;
|
||||
|
||||
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
|
||||
|
||||
self = g_object_new (GTK_TYPE_LIST_MODEL_VALIDATOR,
|
||||
"model", model,
|
||||
NULL);
|
||||
|
||||
/* consume the reference */
|
||||
g_clear_object (&model);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*<private>
|
||||
* gtk_list_model_validator_set_model: (attributes org.gtk.Method.set_property=model)
|
||||
* @self: a `GtkListModelValidator`
|
||||
* @model: (nullable): The model to be validated
|
||||
*
|
||||
* Sets the model to validate.
|
||||
*/
|
||||
void
|
||||
gtk_list_model_validator_set_model (GtkListModelValidator *self,
|
||||
GListModel *model)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (GTK_IS_LIST_MODEL_VALIDATOR (self));
|
||||
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
||||
|
||||
if (self->model == model)
|
||||
return;
|
||||
|
||||
if (self->model)
|
||||
gtk_list_model_validator_clear_model (self);
|
||||
else
|
||||
g_object_ref (self);
|
||||
|
||||
if (model)
|
||||
{
|
||||
self->model = g_object_ref (model);
|
||||
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_list_model_validator_items_changed_cb), self);
|
||||
g_signal_connect (model, "notify::n-items", G_CALLBACK (gtk_list_model_validator_notify_n_items_cb), self);
|
||||
if (GTK_IS_SECTION_MODEL (model))
|
||||
g_signal_connect (model, "sections-changed", G_CALLBACK (gtk_list_model_validator_sections_changed_cb), self);
|
||||
g_object_weak_ref (G_OBJECT (model), gtk_list_model_validator_weak_unref_cb, self);
|
||||
|
||||
self->notified_n_items = g_list_model_get_n_items (G_LIST_MODEL (model));
|
||||
items_set_size (&self->items, self->notified_n_items);
|
||||
for (i = 0; i < self->notified_n_items; i++)
|
||||
{
|
||||
*items_index (&self->items, i) = g_list_model_get_item (model, i);
|
||||
}
|
||||
if (GTK_IS_SECTION_MODEL (self->model))
|
||||
{
|
||||
guint start, end;
|
||||
|
||||
for (i = 0; i < self->notified_n_items; i = end)
|
||||
{
|
||||
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), i, &start, &end);
|
||||
sections_append (&self->sections, end - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
|
||||
}
|
||||
|
||||
/*<private>
|
||||
* gtk_list_model_validator_get_model: (attributes org.gtk.Method.get_property=model)
|
||||
* @self: a `GtkListModelValidator`
|
||||
*
|
||||
* Gets the model that is currently being used or %NULL if none.
|
||||
*
|
||||
* Returns: (nullable) (transfer none): The model in use
|
||||
*/
|
||||
GListModel *
|
||||
gtk_list_model_validator_get_model (GtkListModelValidator *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_LIST_MODEL_VALIDATOR (self), NULL);
|
||||
|
||||
return self->model;
|
||||
}
|
38
testsuite/gtk/gtklistmodelvalidator.h
Normal file
38
testsuite/gtk/gtklistmodelvalidator.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <gtk/gtk.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_LIST_MODEL_VALIDATOR (gtk_list_model_validator_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkListModelValidator, gtk_list_model_validator, GTK, LIST_MODEL_VALIDATOR, GObject)
|
||||
|
||||
GtkListModelValidator * gtk_list_model_validator_new (GListModel *model);
|
||||
|
||||
void gtk_list_model_validator_set_model (GtkListModelValidator *self,
|
||||
GListModel *model);
|
||||
GListModel * gtk_list_model_validator_get_model (GtkListModelValidator *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -76,7 +76,12 @@ tests = [
|
||||
{ 'name': 'slicelistmodel' },
|
||||
{ 'name': 'sorter' },
|
||||
{ 'name': 'sortlistmodel' },
|
||||
{ 'name': 'sortlistmodel-exhaustive' },
|
||||
{ 'name': 'sortlistmodel-exhaustive',
|
||||
'sources': [
|
||||
'sortlistmodel-exhaustive.c',
|
||||
'gtklistmodelvalidator.c',
|
||||
],
|
||||
},
|
||||
{ 'name': 'spinbutton' },
|
||||
{ 'name': 'stringlist' },
|
||||
{ 'name': 'templates' },
|
||||
|
@@ -18,6 +18,9 @@
|
||||
#include <locale.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "gtklistmodelvalidator.h"
|
||||
|
||||
#define MAX_CHARS 4
|
||||
|
||||
#define ensure_updated() G_STMT_START{ \
|
||||
while (g_main_context_pending (NULL)) \
|
||||
@@ -90,124 +93,6 @@ model_to_string (GListModel *model)
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_items_changed_correctly (GListModel *model,
|
||||
guint position,
|
||||
guint removed,
|
||||
guint added,
|
||||
GListModel *compare)
|
||||
{
|
||||
guint i, n_items;
|
||||
|
||||
//sanity check that we got all notifies
|
||||
g_assert_cmpuint (g_list_model_get_n_items (compare), ==, GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (compare), "last-notified-n-items")));
|
||||
|
||||
//g_print ("%s => %u -%u +%u => %s\n", model_to_string (compare), position, removed, added, model_to_string (model));
|
||||
|
||||
g_assert_cmpuint (g_list_model_get_n_items (model), ==, g_list_model_get_n_items (compare) - removed + added);
|
||||
n_items = g_list_model_get_n_items (model);
|
||||
|
||||
if (position != 0 || removed != n_items)
|
||||
{
|
||||
/* Check that all unchanged items are indeed unchanged */
|
||||
for (i = 0; i < position; i++)
|
||||
{
|
||||
gpointer o1 = g_list_model_get_item (model, i);
|
||||
gpointer o2 = g_list_model_get_item (compare, i);
|
||||
g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
|
||||
g_object_unref (o1);
|
||||
g_object_unref (o2);
|
||||
}
|
||||
for (i = position + added; i < n_items; i++)
|
||||
{
|
||||
gpointer o1 = g_list_model_get_item (model, i);
|
||||
gpointer o2 = g_list_model_get_item (compare, i - added + removed);
|
||||
g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
|
||||
g_object_unref (o1);
|
||||
g_object_unref (o2);
|
||||
}
|
||||
|
||||
/* Check that the first and last added item are different from
|
||||
* first and last removed item.
|
||||
* Otherwise we could have kept them as-is
|
||||
*/
|
||||
if (removed > 0 && added > 0)
|
||||
{
|
||||
gpointer o1 = g_list_model_get_item (model, position);
|
||||
gpointer o2 = g_list_model_get_item (compare, position);
|
||||
g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
|
||||
g_object_unref (o1);
|
||||
g_object_unref (o2);
|
||||
|
||||
o1 = g_list_model_get_item (model, position + added - 1);
|
||||
o2 = g_list_model_get_item (compare, position + removed - 1);
|
||||
g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
|
||||
g_object_unref (o1);
|
||||
g_object_unref (o2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, perform the same change as the signal indicates */
|
||||
g_list_store_splice (G_LIST_STORE (compare), position, removed, NULL, 0);
|
||||
for (i = position; i < position + added; i++)
|
||||
{
|
||||
gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
|
||||
g_list_store_insert (G_LIST_STORE (compare), i, item);
|
||||
g_object_unref (item);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_n_items_notified_properly (GListModel *model,
|
||||
GParamSpec *pspec,
|
||||
GListModel *compare)
|
||||
{
|
||||
g_assert_cmpuint (g_list_model_get_n_items (model), !=, GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (compare), "last-notified-n-items")));
|
||||
|
||||
/* These should hve been updated in items-changed, which should have been emitted first */
|
||||
g_assert_cmpuint (g_list_model_get_n_items (model), ==, g_list_model_get_n_items (compare));
|
||||
|
||||
g_object_set_data (G_OBJECT (compare),
|
||||
"last-notified-n-items",
|
||||
GUINT_TO_POINTER (g_list_model_get_n_items (model)));
|
||||
}
|
||||
|
||||
static GtkSortListModel *
|
||||
sort_list_model_new (GListModel *source,
|
||||
GtkSorter *sorter)
|
||||
{
|
||||
GtkSortListModel *model;
|
||||
GListStore *check;
|
||||
guint i;
|
||||
|
||||
model = gtk_sort_list_model_new (source, sorter);
|
||||
check = g_list_store_new (G_TYPE_OBJECT);
|
||||
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++)
|
||||
{
|
||||
gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
|
||||
g_list_store_append (check, item);
|
||||
g_object_unref (item);
|
||||
}
|
||||
g_signal_connect_data (model,
|
||||
"items-changed",
|
||||
G_CALLBACK (assert_items_changed_correctly),
|
||||
check,
|
||||
(GClosureNotify) g_object_unref,
|
||||
0);
|
||||
|
||||
g_object_set_data (G_OBJECT (check),
|
||||
"last-notified-n-items",
|
||||
GUINT_TO_POINTER (g_list_model_get_n_items (G_LIST_MODEL (check))));
|
||||
g_signal_connect_data (model,
|
||||
"notify::n-items",
|
||||
G_CALLBACK (assert_n_items_notified_properly),
|
||||
g_object_ref (check),
|
||||
(GClosureNotify) g_object_unref,
|
||||
0);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
#define N_MODELS 8
|
||||
|
||||
static char *
|
||||
@@ -242,10 +127,12 @@ create_sort_list_model (gconstpointer model_id,
|
||||
GtkSortListModel *model;
|
||||
guint id = GPOINTER_TO_UINT (model_id);
|
||||
|
||||
model = gtk_sort_list_model_new (((id & 1) || !source) ? NULL : g_object_ref (source), ((id & 2) || !sorter) ? NULL : g_object_ref (sorter));
|
||||
if (track_changes)
|
||||
model = sort_list_model_new (((id & 1) || !source) ? NULL : g_object_ref (source), ((id & 2) || !sorter) ? NULL : g_object_ref (sorter));
|
||||
else
|
||||
model = gtk_sort_list_model_new (((id & 1) || !source) ? NULL : g_object_ref (source), ((id & 2) || !sorter) ? NULL : g_object_ref (sorter));
|
||||
{
|
||||
GtkListModelValidator *validator = gtk_list_model_validator_new (G_LIST_MODEL (model));
|
||||
g_object_unref (validator);
|
||||
}
|
||||
|
||||
switch (id >> 2)
|
||||
{
|
||||
@@ -272,7 +159,7 @@ create_sort_list_model (gconstpointer model_id,
|
||||
static GListModel *
|
||||
create_source_model (guint min_size, guint max_size)
|
||||
{
|
||||
const char *strings[] = { "A", "a", "B", "b" };
|
||||
const char chars[] = { 'A', 'a', 'B', 'b' };
|
||||
GtkStringList *list;
|
||||
guint i, size;
|
||||
|
||||
@@ -280,13 +167,56 @@ create_source_model (guint min_size, guint max_size)
|
||||
list = gtk_string_list_new (NULL);
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
gtk_string_list_append (list, strings[g_test_rand_int_range (0, G_N_ELEMENTS (strings))]);
|
||||
{
|
||||
char string[MAX_CHARS + 1];
|
||||
int j, string_len;
|
||||
string_len = g_test_rand_int_range (1, MAX_CHARS + 1);
|
||||
for (j = 0; j < string_len; j++)
|
||||
{
|
||||
string[j] = chars[g_test_rand_int_range (0, G_N_ELEMENTS (chars))];
|
||||
}
|
||||
string[j] = 0;
|
||||
gtk_string_list_append (list, string);
|
||||
}
|
||||
|
||||
return G_LIST_MODEL (list);
|
||||
}
|
||||
|
||||
#define N_SORTERS 3
|
||||
|
||||
static char *
|
||||
get_substring (GObject *this,
|
||||
guint offset,
|
||||
guint len)
|
||||
{
|
||||
const char *string;
|
||||
|
||||
if (!GTK_IS_STRING_OBJECT (this))
|
||||
return NULL;
|
||||
string = gtk_string_object_get_string (GTK_STRING_OBJECT (this));
|
||||
|
||||
if (offset >= strlen (string))
|
||||
return NULL;
|
||||
string += offset;
|
||||
|
||||
return g_strndup (string, len);
|
||||
}
|
||||
|
||||
static GtkExpression *
|
||||
create_random_substring_expression (void)
|
||||
{
|
||||
return gtk_cclosure_expression_new (G_TYPE_STRING,
|
||||
NULL,
|
||||
2,
|
||||
(GtkExpression *[2]) {
|
||||
gtk_constant_expression_new (G_TYPE_UINT, (guint) g_test_rand_int_range (0, MAX_CHARS)),
|
||||
gtk_constant_expression_new (G_TYPE_UINT, (guint) g_test_rand_int_range (1, MAX_CHARS + 1)),
|
||||
},
|
||||
G_CALLBACK (get_substring),
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static GtkSorter *
|
||||
create_sorter (gsize id)
|
||||
{
|
||||
@@ -300,7 +230,7 @@ create_sorter (gsize id)
|
||||
case 1:
|
||||
case 2:
|
||||
/* match all As, Bs and nothing */
|
||||
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
|
||||
sorter = GTK_SORTER (gtk_string_sorter_new (create_random_substring_expression ()));
|
||||
if (id == 1)
|
||||
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
|
||||
return sorter;
|
||||
@@ -360,12 +290,12 @@ test_two_sorters (gconstpointer model_id)
|
||||
|
||||
for (j = 0; j < N_SORTERS; j++)
|
||||
{
|
||||
sorter = create_sorter (i);
|
||||
sorter = create_sorter (j);
|
||||
gtk_sort_list_model_set_sorter (model2, sorter);
|
||||
gtk_multi_sorter_append (GTK_MULTI_SORTER (every), sorter);
|
||||
|
||||
ensure_updated ();
|
||||
assert_model_equal (G_LIST_MODEL (model2), G_LIST_MODEL (compare));
|
||||
assert_model_equal (G_LIST_MODEL (model1), G_LIST_MODEL (compare));
|
||||
|
||||
for (k = 0; k < 10; k++)
|
||||
{
|
||||
@@ -606,7 +536,7 @@ test_sections (gconstpointer model_id)
|
||||
|
||||
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);
|
||||
sort = create_sort_list_model (model_id, TRUE, G_LIST_MODEL (flatten), NULL);
|
||||
|
||||
for (i = 0; i < 500; i++)
|
||||
{
|
||||
|
Reference in New Issue
Block a user