[libdazzle] listbox: fix caching listbox implementation



commit 8abf0d11748096eab781ff15c4aa610913cd6c69
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jun 6 17:24:36 2017 -0700

    listbox: fix caching listbox implementation
    
    This was not working as expected. This changes how we deal with
    destruction by requiring that you subclass the list box row too.
    
    When gtk_widget_destroy() is called, g_object_run_dispose() gets called.
    We hook GObjectClass.dispose to check with the parent if we should first
    hook into the cache.

 src/meson.build                    |    3 +
 src/widgets/dzl-list-box-private.h |   32 +++++++++++++++
 src/widgets/dzl-list-box-row.c     |   60 ++++++++++++++++++++++++++++
 src/widgets/dzl-list-box-row.h     |   39 ++++++++++++++++++
 src/widgets/dzl-list-box.c         |   76 ++++++++++++++++++++++--------------
 5 files changed, 181 insertions(+), 29 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 44df2af..8ecf237 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -150,6 +150,7 @@ libdazzle_public_headers = [
   'widgets/dzl-entry-box.h',
   'widgets/dzl-file-chooser-entry.h',
   'widgets/dzl-list-box.h',
+  'widgets/dzl-list-box-row.h',
   'widgets/dzl-multi-paned.h',
   'widgets/dzl-pill-box.h',
   'widgets/dzl-priority-box.h',
@@ -289,6 +290,7 @@ libdazzle_public_sources = [
   'widgets/dzl-entry-box.c',
   'widgets/dzl-file-chooser-entry.c',
   'widgets/dzl-list-box.c',
+  'widgets/dzl-list-box-row.c',
   'widgets/dzl-multi-paned.c',
   'widgets/dzl-pill-box.c',
   'widgets/dzl-priority-box.c',
@@ -336,6 +338,7 @@ libdazzle_sources = [
 
   'util/dzl-util-private.h',
 
+  'widgets/dzl-list-box-private.h',
   'widgets/dzl-rect-helper.c',
   'widgets/dzl-rect-helper.h',
 
diff --git a/src/widgets/dzl-list-box-private.h b/src/widgets/dzl-list-box-private.h
new file mode 100644
index 0000000..eb3687b
--- /dev/null
+++ b/src/widgets/dzl-list-box-private.h
@@ -0,0 +1,32 @@
+/* dzl-list-box-private.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you *privatean redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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/>.
+ */
+
+#ifndef DZL_LIST_BOX_PRIVATE_H
+#define DZL_LIST_BOX_PRIVATE_H
+
+#include "dzl-list-box.h"
+#include "dzl-list-box-row.h"
+
+G_BEGIN_DECLS
+
+gboolean _dzl_list_box_cache (DzlListBox    *self,
+                              DzlListBoxRow *row);
+
+G_END_DECLS
+
+#endif /* DZL_LIST_BOX_PRIVATE_H */
diff --git a/src/widgets/dzl-list-box-row.c b/src/widgets/dzl-list-box-row.c
new file mode 100644
index 0000000..e0e2f2f
--- /dev/null
+++ b/src/widgets/dzl-list-box-row.c
@@ -0,0 +1,60 @@
+/* dzl-list-box-row.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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/>.
+ */
+
+#define G_LOG_DOMAIN "dzl-list-box-row"
+
+#include "dzl-list-box.h"
+#include "dzl-list-box-private.h"
+#include "dzl-list-box-row.h"
+
+G_DEFINE_ABSTRACT_TYPE (DzlListBoxRow, dzl_list_box_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+dzl_list_box_row_dispose (GObject *object)
+{
+  DzlListBoxRow *self = (DzlListBoxRow *)object;
+  GtkWidget *parent;
+
+  /*
+   * Chaining up will cause a lot of things to be deleted such as
+   * our widget tree starting from this instance. We don't want that
+   * to happen so that we can reuse the widgets. This should only
+   * need to be done if the caller has not notified us they will be
+   * caching the widget first.
+   */
+
+  parent = gtk_widget_get_parent (GTK_WIDGET (self));
+  if (DZL_IS_LIST_BOX (parent) &&
+      _dzl_list_box_cache (DZL_LIST_BOX (parent), self))
+    return;
+
+  G_OBJECT_CLASS (dzl_list_box_row_parent_class)->dispose (object);
+}
+
+static void
+dzl_list_box_row_class_init (DzlListBoxRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = dzl_list_box_row_dispose;
+}
+
+static void
+dzl_list_box_row_init (DzlListBoxRow *self)
+{
+}
diff --git a/src/widgets/dzl-list-box-row.h b/src/widgets/dzl-list-box-row.h
new file mode 100644
index 0000000..e867e42
--- /dev/null
+++ b/src/widgets/dzl-list-box-row.h
@@ -0,0 +1,39 @@
+/* dzl-list-box-row.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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/>.
+ */
+
+#ifndef DZL_LIST_BOX_ROW_H
+#define DZL_LIST_BOX_ROW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_LIST_BOX_ROW (dzl_list_box_row_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (DzlListBoxRow, dzl_list_box_row, DZL, LIST_BOX_ROW, GtkListBoxRow)
+
+struct _DzlListBoxRowClass
+{
+  GtkListBoxRowClass parent_class;
+};
+
+GtkWidget *dzl_list_box_row_new (void);
+
+G_END_DECLS
+
+#endif /* DZL_LIST_BOX_ROW_H */
diff --git a/src/widgets/dzl-list-box.c b/src/widgets/dzl-list-box.c
index 3e0a8e7..bf6f830 100644
--- a/src/widgets/dzl-list-box.c
+++ b/src/widgets/dzl-list-box.c
@@ -28,9 +28,12 @@
  *
  * This mostly just avoids the overhead of reparsing the template XML
  * on every widget (re)creation.
+ *
+ * You must subclass DzlListBoxRow for your rows.
  */
 
 #include "dzl-list-box.h"
+#include "dzl-list-box-row.h"
 
 typedef struct
 {
@@ -53,54 +56,72 @@ enum {
 
 static GParamSpec *properties [N_PROPS];
 
-static GtkWidget *
-dzl_list_box_create_row (gpointer item,
-                         gpointer user_data)
+gboolean
+_dzl_list_box_cache (DzlListBox    *self,
+                     DzlListBoxRow *row)
 {
-  DzlListBox *self = user_data;
   DzlListBoxPrivate *priv = dzl_list_box_get_instance_private (self);
 
-  g_assert (G_IS_OBJECT (item));
   g_assert (DZL_IS_LIST_BOX (self));
+  g_assert (DZL_IS_LIST_BOX_ROW (row));
 
-  if (priv->trashed_rows.length > 0)
+  if (gtk_widget_get_parent (GTK_WIDGET (row)) != GTK_WIDGET (self))
     {
-      GtkListBoxRow *row = g_queue_pop_tail (&priv->trashed_rows);
+      g_warning ("Attempt to cache row not belonging to list box");
+      return FALSE;
+    }
 
-      g_object_set (row, priv->property_name, item, NULL);
-      g_object_force_floating (G_OBJECT (row));
-      g_object_unref (row);
+  if (gtk_widget_in_destruction (GTK_WIDGET (self)))
+    return FALSE;
 
-      return GTK_WIDGET (row);
+  if (priv->trashed_rows.length < priv->recycle_max)
+    {
+      g_autoptr(GtkWidget) held = g_object_ref (row);
+
+      gtk_list_box_unselect_row (GTK_LIST_BOX (self), GTK_LIST_BOX_ROW (row));
+      gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (row));
+      g_object_set (held, priv->property_name, NULL, NULL);
+      g_object_force_floating (G_OBJECT (held));
+      g_queue_push_head (&priv->trashed_rows, g_steal_pointer (&held));
+
+      return TRUE;
     }
 
-  return g_object_new (priv->row_type,
-                       "visible", TRUE,
-                       priv->property_name, item,
-                       NULL);
+  return FALSE;
 }
 
-static void
-dzl_list_box_remove (GtkContainer *container,
-                     GtkWidget    *widget)
+static GtkWidget *
+dzl_list_box_create_row (gpointer item,
+                         gpointer user_data)
 {
-  DzlListBox *self = (DzlListBox *)container;
+  DzlListBox *self = user_data;
   DzlListBoxPrivate *priv = dzl_list_box_get_instance_private (self);
+  GtkListBoxRow *row;
 
+  g_assert (G_IS_OBJECT (item));
   g_assert (DZL_IS_LIST_BOX (self));
-  g_assert (GTK_IS_LIST_BOX_ROW (widget));
 
-  g_object_ref (widget);
+  if (priv->trashed_rows.length > 0)
+    {
+      row = g_queue_pop_tail (&priv->trashed_rows);
 
-  GTK_CONTAINER_CLASS (dzl_list_box_parent_class)->remove (container, widget);
+      g_assert (DZL_IS_LIST_BOX_ROW (row));
+      g_assert (priv->property_name != NULL);
+      g_assert (item != NULL);
 
-  if (priv->trashed_rows.length < priv->recycle_max)
+      g_object_set (row, priv->property_name, item, NULL);
+    }
+  else
     {
-      g_object_set (widget, priv->property_name, NULL, NULL);
-      g_queue_push_head (&priv->trashed_rows, g_steal_pointer (&widget));
+      row = g_object_new (priv->row_type,
+                          "visible", TRUE,
+                          priv->property_name, item,
+                          NULL);
     }
 
-  g_clear_object (&widget);
+  g_return_val_if_fail (DZL_IS_LIST_BOX_ROW (row), NULL);
+
+  return GTK_WIDGET (row);
 }
 
 static void
@@ -224,7 +245,6 @@ dzl_list_box_class_init (DzlListBoxClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
 
   object_class->constructed = dzl_list_box_constructed;
   object_class->finalize = dzl_list_box_finalize;
@@ -233,8 +253,6 @@ dzl_list_box_class_init (DzlListBoxClass *klass)
 
   widget_class->destroy = dzl_list_box_destroy;
 
-  container_class->remove = dzl_list_box_remove;
-
   properties [PROP_ROW_TYPE] =
     g_param_spec_gtype ("row-type",
                         "Row Type",


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