[gtk] widget: Add gtk_widget_observe_children()



commit dd94129e27b0ad4f4f635053f8b09e94d94c29ae
Author: Benjamin Otte <otte redhat com>
Date:   Mon Aug 27 19:50:01 2018 +0200

    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.

 docs/reference/gtk/gtk4-sections.txt |   3 +
 gtk/gtklistlistmodel.c               | 297 +++++++++++++++++++++++++++++++++++
 gtk/gtklistlistmodelprivate.h        |  73 +++++++++
 gtk/gtkwidget.c                      |  60 ++++++-
 gtk/gtkwidget.h                      |   2 +
 gtk/gtkwidgetprivate.h               |   5 +
 gtk/meson.build                      |   1 +
 7 files changed, 440 insertions(+), 1 deletion(-)
---
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',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]