summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2018-08-27 19:50:01 +0200
committerBenjamin Otte <otte@redhat.com>2018-09-16 18:50:17 +0200
commitdd94129e27b0ad4f4f635053f8b09e94d94c29ae (patch)
tree04fd527f8cc6bc7f344e3ba9f70f85b616002d82
parent63e5b827eddc2f208886b004ab97363edaf2bde9 (diff)
downloadgtk+-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.txt3
-rw-r--r--gtk/gtklistlistmodel.c297
-rw-r--r--gtk/gtklistlistmodelprivate.h73
-rw-r--r--gtk/gtkwidget.c60
-rw-r--r--gtk/gtkwidget.h2
-rw-r--r--gtk/gtkwidgetprivate.h5
-rw-r--r--gtk/meson.build1
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',