diff options
author | Christian Hergert <chergert@redhat.com> | 2022-11-16 15:10:50 -0800 |
---|---|---|
committer | Christian Hergert <chergert@redhat.com> | 2022-11-16 15:10:50 -0800 |
commit | ca6c7587c2f67c9cc9adff4126d5aaf87ea10f4f (patch) | |
tree | 754aee0bd8327a550c176410c08aec2acb11e770 | |
parent | c082de7bfd0afa904e11d48c17586b21aa556469 (diff) | |
parent | dc73ece2b96886a778dfe34e808106e89469f1f0 (diff) | |
download | gtksourceview-ca6c7587c2f67c9cc9adff4126d5aaf87ea10f4f.tar.gz |
Merge branch 'wip/chergert/fix-completion-snapshots'
-rw-r--r-- | gtksourceview/gtksourcecompletionlistbox.c | 132 | ||||
-rw-r--r-- | gtksourceview/gtksourcelistsnapshot-private.h | 40 | ||||
-rw-r--r-- | gtksourceview/gtksourcelistsnapshot.c | 406 | ||||
-rw-r--r-- | gtksourceview/meson.build | 1 | ||||
-rw-r--r-- | testsuite/meson.build | 1 | ||||
-rw-r--r-- | testsuite/test-listsnapshot.c | 160 |
6 files changed, 711 insertions, 29 deletions
diff --git a/gtksourceview/gtksourcecompletionlistbox.c b/gtksourceview/gtksourcecompletionlistbox.c index a8f7d209..a7f6b810 100644 --- a/gtksourceview/gtksourcecompletionlistbox.c +++ b/gtksourceview/gtksourcecompletionlistbox.c @@ -27,12 +27,21 @@ #include "gtksourcecompletionlistboxrow-private.h" #include "gtksourcecompletionproposal.h" #include "gtksourcecompletionprovider.h" +#include "gtksourcelistsnapshot-private.h" #include "gtksourceview-private.h" struct _GtkSourceCompletionListBox { GtkWidget parent_instance; + /* Used to snapshot a GListModel of the visible range during a + * frame cycle, so that it cannot change underneath us. We hold() + * and release() the snapshot from frame clock callbacks. + */ + GtkSourceListSnapshot *list_snapshot; + gulong update_handler; + gulong after_paint_handler; + /* The box containing the rows. */ GtkBox *box; @@ -42,11 +51,6 @@ struct _GtkSourceCompletionListBox /* The completion context that is being displayed. */ GtkSourceCompletionContext *context; - /* The handler for GtkSourceCompletionContext::items-chaged which should - * be disconnected when no longer needed. - */ - gulong items_changed_handler; - /* The number of rows we expect to have visible to the user. */ guint n_rows; @@ -116,12 +120,21 @@ enum { N_SIGNALS }; -static void gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self); -static gboolean gtk_source_completion_list_box_update_cb (GtkWidget *widget, - GdkFrameClock *frame_clock, - gpointer user_data); -static void gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self, - gboolean update_selection); +static void gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self); +static gboolean gtk_source_completion_list_box_update_cb (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer user_data); +static void gtk_source_completion_list_box_after_update_cb (GtkWidget *widget, + GdkFrameClock *frame_clock); +static void gtk_source_completion_list_box_after_paint_cb (GtkWidget *widget, + GdkFrameClock *frame_clock); +static void gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self, + gboolean update_selection); +static void gtk_source_completion_list_box_items_changed_cb (GtkSourceCompletionListBox *self, + guint position, + guint removed, + guint added, + GListModel *model); G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionListBox, gtk_source_completion_list_box, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) @@ -548,6 +561,42 @@ _gtk_source_completion_list_box_key_pressed_cb (GtkSourceCompletionListBox *self } static void +gtk_source_completion_list_box_realize (GtkWidget *widget) +{ + GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (widget); + GdkFrameClock *frame_clock; + + GTK_WIDGET_CLASS (gtk_source_completion_list_box_parent_class)->realize (widget); + + frame_clock = gtk_widget_get_frame_clock (widget); + + self->update_handler = + g_signal_connect_data (frame_clock, + "update", + G_CALLBACK (gtk_source_completion_list_box_after_update_cb), + self, NULL, + G_CONNECT_AFTER | G_CONNECT_SWAPPED); + self->after_paint_handler = + g_signal_connect_swapped (frame_clock, + "after-paint", + G_CALLBACK (gtk_source_completion_list_box_after_paint_cb), + self); +} + +static void +gtk_source_completion_list_box_unrealize (GtkWidget *widget) +{ + GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (widget); + GdkFrameClock *frame_clock; + + frame_clock = gtk_widget_get_frame_clock (widget); + g_clear_signal_handler (&self->update_handler, frame_clock); + g_clear_signal_handler (&self->after_paint_handler, frame_clock); + + GTK_WIDGET_CLASS (gtk_source_completion_list_box_parent_class)->unrealize (widget); +} + +static void gtk_source_completion_list_box_constructed (GObject *object) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)object; @@ -582,6 +631,7 @@ gtk_source_completion_list_box_dispose (GObject *object) self->box = NULL; } + g_clear_object (&self->list_snapshot); g_clear_object (&self->before_size_group); g_clear_object (&self->typed_text_size_group); g_clear_object (&self->after_size_group); @@ -693,6 +743,9 @@ gtk_source_completion_list_box_class_init (GtkSourceCompletionListBoxClass *klas object_class->get_property = gtk_source_completion_list_box_get_property; object_class->set_property = gtk_source_completion_list_box_set_property; + widget_class->realize = gtk_source_completion_list_box_realize; + widget_class->unrealize = gtk_source_completion_list_box_unrealize; + properties [PROP_ALTERNATE] = g_param_spec_int ("alternate", "Alternate", @@ -804,6 +857,13 @@ gtk_source_completion_list_box_init (GtkSourceCompletionListBox *self) self); gtk_widget_add_controller (GTK_WIDGET (self), key); + self->list_snapshot = gtk_source_list_snapshot_new (); + g_signal_connect_object (self->list_snapshot, + "items-changed", + G_CALLBACK (gtk_source_completion_list_box_items_changed_cb), + self, + G_CONNECT_SWAPPED); + self->box = g_object_new (GTK_TYPE_BOX, "orientation", GTK_ORIENTATION_VERTICAL, "visible", TRUE, @@ -1158,25 +1218,10 @@ _gtk_source_completion_list_box_set_context (GtkSourceCompletionListBox *self, g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); - if (self->context == context) - return; - - if (self->context != NULL && self->items_changed_handler != 0) - { - g_signal_handler_disconnect (self->context, self->items_changed_handler); - self->items_changed_handler = 0; - } - - g_set_object (&self->context, context); - - if (self->context != NULL) + if (g_set_object (&self->context, context)) { - self->items_changed_handler = - g_signal_connect_object (self->context, - "items-changed", - G_CALLBACK (gtk_source_completion_list_box_items_changed_cb), - self, - G_CONNECT_SWAPPED); + gtk_source_list_snapshot_set_model (self->list_snapshot, + G_LIST_MODEL (context)); } gtk_source_completion_list_box_set_selected (self, -1); @@ -1340,3 +1385,32 @@ _gtk_source_completion_list_box_set_show_icons (GtkSourceCompletionListBox *self gtk_source_completion_list_box_queue_update (self); } + +static void +gtk_source_completion_list_box_after_update_cb (GtkWidget *widget, + GdkFrameClock *frame_clock) +{ + GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; + double lower; + double page_size; + + g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); + g_assert (GDK_IS_FRAME_CLOCK (frame_clock)); + + lower = gtk_adjustment_get_lower (self->vadjustment); + page_size = gtk_adjustment_get_page_size (self->vadjustment); + + gtk_source_list_snapshot_hold (self->list_snapshot, lower, lower + page_size); +} + +static void +gtk_source_completion_list_box_after_paint_cb (GtkWidget *widget, + GdkFrameClock *frame_clock) +{ + GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; + + g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); + g_assert (GDK_IS_FRAME_CLOCK (frame_clock)); + + gtk_source_list_snapshot_release (self->list_snapshot); +} diff --git a/gtksourceview/gtksourcelistsnapshot-private.h b/gtksourceview/gtksourcelistsnapshot-private.h new file mode 100644 index 00000000..456f2592 --- /dev/null +++ b/gtksourceview/gtksourcelistsnapshot-private.h @@ -0,0 +1,40 @@ +/* gtksourcelistsnapshot-private.h + * + * Copyright 2022 Christian Hergert <chergert@redhat.com> + * + * 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 General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GTK_SOURCE_TYPE_LIST_SNAPSHOT (gtk_source_list_snapshot_get_type()) + +G_DECLARE_FINAL_TYPE (GtkSourceListSnapshot, gtk_source_list_snapshot, GTK_SOURCE, LIST_SNAPSHOT, GObject) + +GtkSourceListSnapshot *gtk_source_list_snapshot_new (void); +GListModel *gtk_source_list_snapshot_get_model (GtkSourceListSnapshot *self); +void gtk_source_list_snapshot_set_model (GtkSourceListSnapshot *self, + GListModel *model); +void gtk_source_list_snapshot_hold (GtkSourceListSnapshot *self, + guint position, + guint length); +void gtk_source_list_snapshot_release (GtkSourceListSnapshot *self); + +G_END_DECLS diff --git a/gtksourceview/gtksourcelistsnapshot.c b/gtksourceview/gtksourcelistsnapshot.c new file mode 100644 index 00000000..733d720a --- /dev/null +++ b/gtksourceview/gtksourcelistsnapshot.c @@ -0,0 +1,406 @@ +/* gtksourcelistsnapshot.c + * + * Copyright 2022 Christian Hergert <chergert@redhat.com> + * + * 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 General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gtksourcelistsnapshot-private.h" + +/* + * GtkSourceListSnapshot: + * + * This classes purpose is to allow snapshoting a range of a GListModel + * and ensuring that no changes to the underlying model will cause that + * range to invalidate. + * + * The "hold" be done at the point where you want to avoid any model + * changes causing the widgetry to invalidate, and released after you've + * completed your snapshot work. + * + * If :model changes, or ::items-changed on the current model is emitted, + * that will be supressed until the hold is released. Objects for the + * hold range are retained so they may be returned from + * g_list_model_get_item(). + */ + +struct _GtkSourceListSnapshot +{ + GObject parent_instance; + GListModel *model; + GSignalGroup *signal_group; + GPtrArray *held_items; + guint held_position; + guint held_n_items; + guint real_n_items; + guint invalid : 1; +}; + +enum { + PROP_0, + PROP_MODEL, + N_PROPS +}; + +static GType +gtk_source_list_snapshot_get_item_type (GListModel *model) +{ + GtkSourceListSnapshot *self = GTK_SOURCE_LIST_SNAPSHOT (model); + + if (self->model != NULL) + { + return g_list_model_get_item_type (self->model); + } + + return G_TYPE_OBJECT; +} + +static guint +gtk_source_list_snapshot_get_n_items (GListModel *model) +{ + GtkSourceListSnapshot *self = GTK_SOURCE_LIST_SNAPSHOT (model); + + if (self->held_position == GTK_INVALID_LIST_POSITION) + { + return self->real_n_items; + } + + return self->held_n_items; +} + +static gpointer +gtk_source_list_snapshot_get_item (GListModel *model, + guint position) +{ + GtkSourceListSnapshot *self = GTK_SOURCE_LIST_SNAPSHOT (model); + + if (self->held_position == GTK_INVALID_LIST_POSITION) + { + if (self->model == NULL) + { + return NULL; + } + + return g_list_model_get_item (self->model, position); + } + + if (position >= self->held_position && + position < self->held_position + self->held_items->len) + { + return g_object_ref (g_ptr_array_index (self->held_items, position - self->held_position)); + } + + return NULL; +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_source_list_snapshot_get_item_type; + iface->get_n_items = gtk_source_list_snapshot_get_n_items; + iface->get_item = gtk_source_list_snapshot_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (GtkSourceListSnapshot, gtk_source_list_snapshot, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +gtk_source_list_snapshot_bind_cb (GtkSourceListSnapshot *self, + GListModel *model, + GSignalGroup *signal_group) +{ + guint old_n_items; + guint new_n_items; + + g_assert (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_assert (G_IS_LIST_MODEL (model)); + g_assert (G_IS_SIGNAL_GROUP (signal_group)); + + old_n_items = self->real_n_items; + new_n_items = g_list_model_get_n_items (model); + + if (self->held_position == GTK_INVALID_LIST_POSITION) + { + if (old_n_items || new_n_items) + { + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items); + } + } + else + { + self->invalid = TRUE; + } + + self->real_n_items = new_n_items; +} + +static void +gtk_source_list_snapshot_unbind_cb (GtkSourceListSnapshot *self, + GSignalGroup *signal_group) +{ + guint old_n_items; + guint new_n_items; + + g_assert (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_assert (G_IS_SIGNAL_GROUP (signal_group)); + + old_n_items = self->real_n_items; + new_n_items = 0; + + self->real_n_items = new_n_items; + + if (self->held_position == GTK_INVALID_LIST_POSITION) + { + if (old_n_items || new_n_items) + { + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items); + } + } + else + { + self->invalid = TRUE; + } +} + +static void +gtk_source_list_snapshot_items_changed_cb (GtkSourceListSnapshot *self, + guint position, + guint removed, + guint added, + GListModel *model) +{ + g_assert (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_assert (G_IS_LIST_MODEL (model)); + + self->real_n_items -= removed; + self->real_n_items += added; + + if (self->held_position == GTK_INVALID_LIST_POSITION) + { + if (removed || added) + { + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); + } + } + else + { + self->invalid = TRUE; + } +} + +static void +gtk_source_list_snapshot_dispose (GObject *object) +{ + GtkSourceListSnapshot *self = (GtkSourceListSnapshot *)object; + + g_clear_pointer (&self->held_items, g_ptr_array_unref); + + if (self->signal_group != NULL) + { + g_signal_group_set_target (self->signal_group, NULL); + g_clear_object (&self->signal_group); + } + + g_clear_object (&self->model); + + G_OBJECT_CLASS (gtk_source_list_snapshot_parent_class)->dispose (object); +} + +static void +gtk_source_list_snapshot_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkSourceListSnapshot *self = GTK_SOURCE_LIST_SNAPSHOT (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); + } +} + +static void +gtk_source_list_snapshot_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkSourceListSnapshot *self = GTK_SOURCE_LIST_SNAPSHOT (object); + + switch (prop_id) + { + case PROP_MODEL: + gtk_source_list_snapshot_set_model (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_source_list_snapshot_class_init (GtkSourceListSnapshotClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtk_source_list_snapshot_dispose; + object_class->get_property = gtk_source_list_snapshot_get_property; + object_class->set_property = gtk_source_list_snapshot_set_property; + + properties [PROP_MODEL] = + g_param_spec_object ("model", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gtk_source_list_snapshot_init (GtkSourceListSnapshot *self) +{ + self->signal_group = g_signal_group_new (G_TYPE_LIST_MODEL); + self->held_items = g_ptr_array_new_with_free_func (g_object_unref); + self->held_position = GTK_INVALID_LIST_POSITION; + + g_signal_connect_object (self->signal_group, + "bind", + G_CALLBACK (gtk_source_list_snapshot_bind_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->signal_group, + "unbind", + G_CALLBACK (gtk_source_list_snapshot_unbind_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->signal_group, + "items-changed", + G_CALLBACK (gtk_source_list_snapshot_items_changed_cb), + self, + G_CONNECT_SWAPPED); +} + +GtkSourceListSnapshot * +gtk_source_list_snapshot_new (void) +{ + return g_object_new (GTK_SOURCE_TYPE_LIST_SNAPSHOT, NULL); +} + +/** + * gtk_source_list_snapshot_get_model: + * @self: a #GtkSourceListSnapshot + * + * Gets the underlying model, if any. + * + * Returns: (transfer none) (nullable): a #GListModel or %NULL + */ +GListModel * +gtk_source_list_snapshot_get_model (GtkSourceListSnapshot *self) +{ + g_return_val_if_fail (GTK_SOURCE_IS_LIST_SNAPSHOT (self), NULL); + + return self->model; +} + +void +gtk_source_list_snapshot_set_model (GtkSourceListSnapshot *self, + GListModel *model) +{ + g_return_if_fail (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_return_if_fail (!model || G_IS_LIST_MODEL (model)); + + if (g_set_object (&self->model, model)) + { + g_signal_group_set_target (self->signal_group, model); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODEL]); + } +} + +void +gtk_source_list_snapshot_hold (GtkSourceListSnapshot *self, + guint position, + guint length) +{ + g_return_if_fail (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_return_if_fail (self->held_position == GTK_INVALID_LIST_POSITION); + g_return_if_fail (self->held_items != NULL); + g_return_if_fail (self->held_items->len == 0); + g_return_if_fail (self->held_n_items == 0); + + self->held_position = position; + + if (self->model != NULL) + { + self->held_n_items = g_list_model_get_n_items (self->model); + } + + if (position > self->held_n_items) + { + position = self->held_n_items; + } + + if (position + length > self->held_n_items) + { + length = self->held_n_items - position; + } + + for (guint i = 0; i < length; i++) + { + g_ptr_array_add (self->held_items, + g_list_model_get_item (self->model, position + i)); + } +} + +void +gtk_source_list_snapshot_release (GtkSourceListSnapshot *self) +{ + gboolean was_invalid; + guint old_n_items; + guint new_n_items; + + g_return_if_fail (GTK_SOURCE_IS_LIST_SNAPSHOT (self)); + g_return_if_fail (self->held_position != GTK_INVALID_LIST_POSITION); + g_return_if_fail (self->held_items != NULL); + + was_invalid = self->invalid; + old_n_items = self->held_n_items; + new_n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + + self->invalid = FALSE; + self->held_n_items = 0; + self->held_position = GTK_INVALID_LIST_POSITION; + + if (self->held_items->len > 0) + { + g_ptr_array_remove_range (self->held_items, 0, self->held_items->len); + } + + if (was_invalid) + { + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items); + } +} diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build index 5ba8f20e..37a660f8 100644 --- a/gtksourceview/meson.build +++ b/gtksourceview/meson.build @@ -128,6 +128,7 @@ core_private_c = files([ 'gtksourceinformative.c', 'gtksourceiter.c', 'gtksourcelanguage-parser-2.c', + 'gtksourcelistsnapshot.c', 'gtksourcemarkssequence.c', 'gtksourcepixbufhelper.c', 'gtksourceregex.c', diff --git a/testsuite/meson.build b/testsuite/meson.build index 8195b14d..adbe140b 100644 --- a/testsuite/meson.build +++ b/testsuite/meson.build @@ -31,6 +31,7 @@ testsuite_sources = [ ['test-language'], ['test-languagemanager'], ['test-language-specs', false], + ['test-listsnapshot'], ['test-mark'], ['test-printcompositor'], ['test-regex'], diff --git a/testsuite/test-listsnapshot.c b/testsuite/test-listsnapshot.c new file mode 100644 index 00000000..3edeaf38 --- /dev/null +++ b/testsuite/test-listsnapshot.c @@ -0,0 +1,160 @@ +/* test-listsnapshot.c + * + * Copyright 2022 Christian Hergert <chergert@redhat.com> + * + * 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 General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include <gtksourceview/gtksourcelistsnapshot-private.h> + +typedef struct +{ + guint call_count; + guint position; + guint removed; + guint added; +} ItemsChanged; + +static void +items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + ItemsChanged *state) +{ +#if 0 + g_printerr ("%d %d %d\n", position, removed, added); +#endif + + state->call_count++; + state->position = position; + state->removed = removed; + state->added = added; +} + +static void +test_list_snapshot (void) +{ + GtkSourceListSnapshot *list_snapshot; + GListStore *store; + GMenu *menu; + ItemsChanged state = {0}; + + list_snapshot = gtk_source_list_snapshot_new (); + g_signal_connect (list_snapshot, "items-changed", G_CALLBACK (items_changed_cb), &state); + g_assert_cmpint (0, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (G_TYPE_OBJECT, ==, g_list_model_get_item_type (G_LIST_MODEL (list_snapshot))); + + store = g_list_store_new (G_TYPE_MENU_MODEL); + + menu = g_menu_new (); + g_list_store_append (store, menu); + + /* initial model set (with items) */ + gtk_source_list_snapshot_set_model (list_snapshot, G_LIST_MODEL (store)); + g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (G_TYPE_MENU_MODEL, ==, g_list_model_get_item_type (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (state.call_count, ==, 1); + g_assert_cmpint (state.position, ==, 0); + g_assert_cmpint (state.removed, ==, 0); + g_assert_cmpint (state.added, ==, 1); + + /* try to reset, no changes/emission */ + gtk_source_list_snapshot_set_model (list_snapshot, G_LIST_MODEL (store)); + g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (G_TYPE_MENU_MODEL, ==, g_list_model_get_item_type (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (state.call_count, ==, 1); + + /* clear model */ + gtk_source_list_snapshot_set_model (list_snapshot, NULL); + g_assert_cmpint (0, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (G_TYPE_OBJECT, ==, g_list_model_get_item_type (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (state.call_count, ==, 2); + g_assert_cmpint (state.position, ==, 0); + g_assert_cmpint (state.removed, ==, 1); + g_assert_cmpint (state.added, ==, 0); + + /* set model again */ + gtk_source_list_snapshot_set_model (list_snapshot, G_LIST_MODEL (store)); + g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (G_TYPE_MENU_MODEL, ==, g_list_model_get_item_type (G_LIST_MODEL (list_snapshot))); + g_assert_cmpint (state.call_count, ==, 3); + g_assert_cmpint (state.position, ==, 0); + g_assert_cmpint (state.removed, ==, 0); + g_assert_cmpint (state.added, ==, 1); + + /* Add some more items so we can hold a range */ + for (guint i = 0; i < 100; i++) + { + GMenu *m = g_menu_new (); + state.call_count = 0; + g_list_store_append (store, m); + g_assert_cmpint (state.call_count, ==, 1); + g_assert_cmpint (state.position, ==, 1+i); + g_assert_cmpint (state.removed, ==, 0); + g_assert_cmpint (state.added, ==, 1); + g_object_unref (m); + } + g_assert_cmpint (101, ==, g_list_model_get_n_items (G_LIST_MODEL (store))); + g_assert_cmpint (101, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + + /* Hold a range so we can test changing things arround */ + gtk_source_list_snapshot_hold (list_snapshot, 10, 20); + g_assert_cmpint (101, ==, g_list_model_get_n_items (G_LIST_MODEL (list_snapshot))); + for (guint i = 0; i <= 100; i++) + { + GMenu *item = g_list_model_get_item (G_LIST_MODEL (list_snapshot), i); + + if (i < 10 || i >= 30) + { + g_assert_null (item); + } + else + { + g_assert_nonnull (item); + g_assert_true (G_IS_MENU (item)); + g_object_unref (item); + } + } + + /* Now remove everything and ensure we're still good */ + state.call_count = 0; + while (g_list_model_get_n_items (G_LIST_MODEL (store))) + { + g_list_store_remove (store, 0); + g_assert_cmpint (state.call_count, ==, 0); + } + + /* Now release our hold */ + gtk_source_list_snapshot_release (list_snapshot); + g_assert_cmpint (state.call_count, ==, 1); + g_assert_cmpint (state.position, ==, 0); + g_assert_cmpint (state.removed, ==, 101); + g_assert_cmpint (state.added, ==, 0); + + g_assert_finalize_object (list_snapshot); + g_assert_finalize_object (store); + g_assert_finalize_object (menu); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/GtkSource/ListSnapshot/basic", test_list_snapshot); + return g_test_run (); +} |