diff options
author | Benjamin Otte <otte@redhat.com> | 2018-08-27 19:50:01 +0200 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2018-09-16 18:50:17 +0200 |
commit | dd94129e27b0ad4f4f635053f8b09e94d94c29ae (patch) | |
tree | 04fd527f8cc6bc7f344e3ba9f70f85b616002d82 | |
parent | 63e5b827eddc2f208886b004ab97363edaf2bde9 (diff) | |
download | gtk+-dd94129e27b0ad4f4f635053f8b09e94d94c29ae.tar.gz |
widget: Add gtk_widget_observe_children()
This creates a listmodel that tracks a widget's children. Doing so turns
adding/removing children from O(1) to O(N) though, so use with caution.
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 3 | ||||
-rw-r--r-- | gtk/gtklistlistmodel.c | 297 | ||||
-rw-r--r-- | gtk/gtklistlistmodelprivate.h | 73 | ||||
-rw-r--r-- | gtk/gtkwidget.c | 60 | ||||
-rw-r--r-- | gtk/gtkwidget.h | 2 | ||||
-rw-r--r-- | gtk/gtkwidgetprivate.h | 5 | ||||
-rw-r--r-- | gtk/meson.build | 1 |
7 files changed, 440 insertions, 1 deletions
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index dbb179d3b1..382305663c 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4586,6 +4586,9 @@ gtk_window_set_titlebar gtk_window_get_titlebar gtk_window_set_interactive_debugging +<SUBSECTION> +gtk_widget_observe_children + <SUBSECTION Standard> GTK_WINDOW GTK_IS_WINDOW diff --git a/gtk/gtklistlistmodel.c b/gtk/gtklistlistmodel.c new file mode 100644 index 0000000000..3d10f49280 --- /dev/null +++ b/gtk/gtklistlistmodel.c @@ -0,0 +1,297 @@ +/* + * Copyright © 2018 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> + */ + + +/* + * SECTION:gtklistlistmodel + * @Short_description: a List model for lists + * @Title: GtkListListModel + * @See_also: #GListModel, #GList + * + * #GtkListListModel is a #GListModel implementation that takes a list API and provides + * it as a GListModel. + **/ + +#include "config.h" + +#include "gtklistlistmodelprivate.h" + +struct _GtkListListModel +{ + GObject parent_instance; + + GType item_type; + guint n_items; + gpointer (* get_first) (gpointer); + gpointer (* get_next) (gpointer, gpointer); + gpointer (* get_previous) (gpointer, gpointer); + gpointer (* get_last) (gpointer); + gpointer (* get_item) (gpointer, gpointer); + gpointer data; + GDestroyNotify notify; +}; + +struct _GtkListListModelClass +{ + GObjectClass parent_class; +}; + +static GType +gtk_list_list_model_get_item_type (GListModel *list) +{ + GtkListListModel *self = GTK_LIST_LIST_MODEL (list); + + return self->item_type; +} + +static guint +gtk_list_list_model_get_n_items (GListModel *list) +{ + GtkListListModel *self = GTK_LIST_LIST_MODEL (list); + + return self->n_items; +} + +static gpointer +gtk_list_list_model_get_item (GListModel *list, + guint position) +{ + GtkListListModel *self = GTK_LIST_LIST_MODEL (list); + gpointer result; + guint i; + + if (position >= self->n_items) + { + return NULL; + } + else if (self->get_last && + position >= self->n_items / 2) + { + result = self->get_last (self->data); + + for (i = self->n_items - 1; i > position; i--) + { + result = self->get_previous (result, self->data); + } + } + else + { + result = self->get_first (self->data); + + for (i = 0; i < position; i++) + { + result = self->get_next (result, self->data); + } + } + + return self->get_item (result, self->data); +} + +static void +gtk_list_list_model_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_list_list_model_get_item_type; + iface->get_n_items = gtk_list_list_model_get_n_items; + iface->get_item = gtk_list_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkListListModel, gtk_list_list_model, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_list_list_model_list_model_init)) + +static void +gtk_list_list_model_dispose (GObject *object) +{ + GtkListListModel *self = GTK_LIST_LIST_MODEL (object); + + if (self->notify) + self->notify (self->data); + + self->n_items = 0; + self->notify = NULL; + + G_OBJECT_CLASS (gtk_list_list_model_parent_class)->dispose (object); +} + +static void +gtk_list_list_model_class_init (GtkListListModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtk_list_list_model_dispose; +} + +static void +gtk_list_list_model_init (GtkListListModel *self) +{ +} + +GtkListListModel * +gtk_list_list_model_new (GType item_type, + gpointer (* get_first) (gpointer), + gpointer (* get_next) (gpointer, gpointer), + gpointer (* get_previous) (gpointer, gpointer), + gpointer (* get_last) (gpointer), + gpointer (* get_item) (gpointer, gpointer), + gpointer data, + GDestroyNotify notify) +{ + guint n_items; + gpointer item; + + n_items = 0; + for (item = get_first (data); + item != NULL; + item = get_next (item, data)) + n_items++; + + return gtk_list_list_model_new_with_size (item_type, + n_items, + get_first, + get_next, + get_previous, + get_last, + get_item, + data, + notify); +} + +GtkListListModel * +gtk_list_list_model_new_with_size (GType item_type, + guint n_items, + gpointer (* get_first) (gpointer), + gpointer (* get_next) (gpointer, gpointer), + gpointer (* get_previous) (gpointer, gpointer), + gpointer (* get_last) (gpointer), + gpointer (* get_item) (gpointer, gpointer), + gpointer data, + GDestroyNotify notify) +{ + GtkListListModel *result; + + g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (get_first != NULL, NULL); + g_return_val_if_fail (get_next != NULL, NULL); + g_return_val_if_fail (get_previous != NULL, NULL); + g_return_val_if_fail (get_item != NULL, NULL); + + result = g_object_new (GTK_TYPE_LIST_LIST_MODEL, NULL); + + result->item_type = item_type; + result->n_items = n_items; + result->get_first = get_first; + result->get_next = get_next; + result->get_previous = get_previous; + result->get_last = get_last; + result->get_item = get_item; + result->data = data; + result->notify = notify; + + return result; +} + +void +gtk_list_list_model_item_added (GtkListListModel *self, + gpointer item) +{ + gpointer x; + guint position; + + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + g_return_if_fail (item != NULL); + + position = 0; + for (x = self->get_first (self->data); + x != item; + x = self->get_next (x, self->data)) + position++; + + gtk_list_list_model_item_added_at (self, position); +} + +void +gtk_list_list_model_item_added_at (GtkListListModel *self, + guint position) +{ + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + g_return_if_fail (position <= self->n_items); + + self->n_items += 1; + + g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); +} + +void +gtk_list_list_model_item_removed (GtkListListModel *self, + gpointer previous) +{ + gpointer x; + guint position; + + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + + if (previous == NULL) + { + position = 0; + } + else + { + position = 1; + + for (x = self->get_first (self->data); + x != previous; + x = self->get_next (x, self->data)) + position++; + } + + gtk_list_list_model_item_removed_at (self, position); +} + +void +gtk_list_list_model_item_removed_at (GtkListListModel *self, + guint position) +{ + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + g_return_if_fail (position < self->n_items); + + self->n_items -= 1; + + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); +} + +void +gtk_list_list_model_clear (GtkListListModel *self) +{ + guint n_items; + + g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self)); + + n_items = self->n_items; + + if (self->notify) + self->notify (self->data); + + self->n_items = 0; + self->notify = NULL; + + if (n_items > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0); +} + + diff --git a/gtk/gtklistlistmodelprivate.h b/gtk/gtklistlistmodelprivate.h new file mode 100644 index 0000000000..3103b3c7c3 --- /dev/null +++ b/gtk/gtklistlistmodelprivate.h @@ -0,0 +1,73 @@ +/* + * Copyright © 2018 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> + */ + + +#ifndef __GTK_LIST_LIST_MODEL_H__ +#define __GTK_LIST_LIST_MODEL_H__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_LIST_LIST_MODEL (gtk_list_list_model_get_type ()) +#define GTK_LIST_LIST_MODEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_LIST_MODEL, GtkListListModel)) +#define GTK_LIST_LIST_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_LIST_MODEL, GtkListListModelClass)) +#define GTK_IS_LIST_LIST_MODEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_LIST_MODEL)) +#define GTK_IS_LIST_LIST_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_LIST_MODEL)) +#define GTK_LIST_LIST_MODEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_LIST_MODEL, GtkListListModelClass)) + +typedef struct _GtkListListModel GtkListListModel; +typedef struct _GtkListListModelClass GtkListListModelClass; + +GType gtk_list_list_model_get_type (void) G_GNUC_CONST; + +GtkListListModel * gtk_list_list_model_new (GType item_type, + gpointer (* get_first) (gpointer), + gpointer (* get_next) (gpointer, gpointer), + gpointer (* get_previous) (gpointer, gpointer), + gpointer (* get_last) (gpointer), + gpointer (* get_item) (gpointer, gpointer), + gpointer data, + GDestroyNotify notify); + +GtkListListModel * gtk_list_list_model_new_with_size (GType item_type, + guint n_items, + gpointer (* get_first) (gpointer), + gpointer (* get_next) (gpointer, gpointer), + gpointer (* get_previous) (gpointer, gpointer), + gpointer (* get_last) (gpointer), + gpointer (* get_item) (gpointer, gpointer), + gpointer data, + GDestroyNotify notify); + +void gtk_list_list_model_item_added (GtkListListModel *self, + gpointer item); +void gtk_list_list_model_item_added_at (GtkListListModel *self, + guint position); +void gtk_list_list_model_item_removed (GtkListListModel *self, + gpointer previous); +void gtk_list_list_model_item_removed_at (GtkListListModel *self, + guint position); + +void gtk_list_list_model_clear (GtkListListModel *self); + + +G_END_DECLS + +#endif /* __GTK_LIST_LIST_MODEL_H__ */ diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index a6c56292d9..c684d51b33 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -3022,6 +3022,7 @@ gtk_widget_unparent (GtkWidget *widget) GObjectNotifyQueue *nqueue; GtkWidget *toplevel; GtkWidget *old_parent; + GtkWidget *old_prev_sibling; g_return_if_fail (GTK_IS_WIDGET (widget)); @@ -3085,6 +3086,7 @@ gtk_widget_unparent (GtkWidget *widget) if (priv->next_sibling) priv->next_sibling->priv->prev_sibling = priv->prev_sibling; } + old_prev_sibling = priv->prev_sibling; priv->parent = NULL; priv->prev_sibling = NULL; priv->next_sibling = NULL; @@ -6613,10 +6615,11 @@ gtk_widget_reposition_after (GtkWidget *widget, { GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); GtkStateFlags parent_flags; - GtkWidget *prev_parent; + GtkWidget *prev_parent, *prev_previous; GtkStateData data; prev_parent = priv->parent; + prev_previous = priv->prev_sibling; if (priv->parent != NULL && priv->parent != parent) { @@ -6719,6 +6722,14 @@ gtk_widget_reposition_after (GtkWidget *widget, _gtk_widget_update_parent_muxer (widget); + if (parent->priv->children_observer) + { + if (prev_previous) + g_warning ("oops"); + else + gtk_list_list_model_item_added (parent->priv->children_observer, widget); + } + if (priv->parent->priv->anchored && prev_parent == NULL) _gtk_widget_propagate_hierarchy_changed (widget, NULL); @@ -8274,6 +8285,9 @@ gtk_widget_dispose (GObject *object) while (priv->paintables) gtk_widget_paintable_set_widget (priv->paintables->data, NULL); + if (priv->children_observer) + gtk_list_list_model_clear (priv->children_observer); + priv->visible = FALSE; if (_gtk_widget_get_realized (widget)) gtk_widget_unrealize (widget); @@ -13204,6 +13218,50 @@ gtk_widget_render (GtkWidget *widget, } } +static void +gtk_widget_child_observer_destroyed (gpointer widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + priv->children_observer = NULL; +} +/** + * gtk_widget_observe_children: + * @widget: a #GtkWidget + * + * Returns a #GListModel to track the children of @widget. + * + * Calling this function will enable extra internal bookkeeping to track + * children and emit signals on the returned listmodel. It may slow down + * operations a lot. + * + * Applications should try hard to avoid calling this function because of + * the slowdowns. + * + * Returns: (transfer full): a #GListModel tracking @widget's children + **/ +GListModel * +gtk_widget_observe_children (GtkWidget *widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + if (priv->children_observer) + return g_object_ref (G_LIST_MODEL (priv->children_observer)); + + priv->children_observer = gtk_list_list_model_new (GTK_TYPE_WIDGET, + (gpointer) gtk_widget_get_first_child, + (gpointer) gtk_widget_get_next_sibling, + (gpointer) gtk_widget_get_prev_sibling, + (gpointer) gtk_widget_get_last_child, + (gpointer) g_object_ref, + widget, + gtk_widget_child_observer_destroyed); + + return G_LIST_MODEL (priv->children_observer); +} + /** * gtk_widget_get_first_child: * @widget: a #GtkWidget diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index 5c240d0fa2..e00f832bcb 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -1031,6 +1031,8 @@ GtkWidget * gtk_widget_get_next_sibling (GtkWidget *widget); GDK_AVAILABLE_IN_ALL GtkWidget * gtk_widget_get_prev_sibling (GtkWidget *widget); GDK_AVAILABLE_IN_ALL +GListModel * gtk_widget_observe_children (GtkWidget *widget); +GDK_AVAILABLE_IN_ALL void gtk_widget_insert_after (GtkWidget *widget, GtkWidget *parent, GtkWidget *previous_sibling); diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 8acf071e92..5e8ff545f0 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -31,6 +31,7 @@ #include "gtkcontainer.h" #include "gtkcsstypesprivate.h" #include "gtkeventcontroller.h" +#include "gtklistlistmodelprivate.h" #include "gtksizerequestcacheprivate.h" #include "gtkwindowprivate.h" #include "gtkinvisible.h" @@ -167,6 +168,10 @@ struct _GtkWidgetPrivate GtkWidget *first_child; GtkWidget *last_child; + /* only created on-demand */ + GtkListListModel *children_observer; + GtkListListModel *controller_observer; + GtkWidget *focus_child; /* Pointer cursor */ diff --git a/gtk/meson.build b/gtk/meson.build index 63d92174b5..d191922f5b 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -265,6 +265,7 @@ gtk_public_sources = files([ 'gtklevelbar.c', 'gtklinkbutton.c', 'gtklistbox.c', + 'gtklistlistmodel.c', 'gtkliststore.c', 'gtklockbutton.c', 'gtkmain.c', |