[gtk/wip/ebassi/shortcut: 70/85] shortcuts: Manage managed shortcuts with a custom model



commit c4e57a94ebde961ff68eb145ec078c0f35c3e350
Author: Benjamin Otte <otte redhat com>
Date:   Mon Aug 20 06:42:22 2018 +0200

    shortcuts: Manage managed shortcuts with a custom model
    
    Reduce the amount of special casing by using a ListModel for global and
    managed shortcuts, too.
    
    This way, the ListModel API will work for the ShortcutController in the
    GtkShortcutManager and GtkRoot.
    
    The only special case remaining is shortcut activation, which needs to
    pass the right widget to the controller in the global/managed case.

 gtk/gtkconcatmodel.c               | 254 +++++++++++++++++++++++++
 gtk/gtkconcatmodelprivate.h        |  52 ++++++
 gtk/gtkshortcutcontroller.c        |  41 ++--
 gtk/gtkshortcutcontrollerprivate.h |   3 -
 gtk/gtkshortcutmanager.c           |  41 ++--
 gtk/gtkwidget.c                    |  13 +-
 gtk/meson.build                    |   1 +
 testsuite/gtk/concatmodel.c        | 372 +++++++++++++++++++++++++++++++++++++
 testsuite/gtk/meson.build          |   1 +
 9 files changed, 732 insertions(+), 46 deletions(-)
---
diff --git a/gtk/gtkconcatmodel.c b/gtk/gtkconcatmodel.c
new file mode 100644
index 0000000000..d96290e6f3
--- /dev/null
+++ b/gtk/gtkconcatmodel.c
@@ -0,0 +1,254 @@
+/*
+ * 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:gtkconcatmodel
+ * @Short_description: concatenation list model
+ * @Title: GtkConcatModel
+ * @See_also: #GListModel
+ *
+ * #GtkConcatModel is a #GListModel implementation that takes a list of list models
+ * and presents them as one concatenated list.
+ *
+ * Node that all the types of the passed in list models must match the concat model's
+ * type. If they are not, you must use a common ancestor type for the #GtkConcatModel,
+ * %G_TYPE_OBJECT being the ultimate option.
+ **/
+
+#include "config.h"
+
+#include "gtkconcatmodelprivate.h"
+
+struct _GtkConcatModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  guint n_items;
+
+  GList *models;
+};
+
+struct _GtkConcatModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GType
+gtk_concat_model_list_model_get_item_type (GListModel *list)
+{
+  GtkConcatModel *self = GTK_CONCAT_MODEL (list);
+
+  return self->item_type;
+}
+
+static guint
+gtk_concat_model_list_model_get_n_items (GListModel *list)
+{
+  GtkConcatModel *self = GTK_CONCAT_MODEL (list);
+
+  return self->n_items;
+}
+
+static gpointer
+gtk_concat_model_list_model_get_item (GListModel *list,
+                                      guint       position)
+{
+  GtkConcatModel *self = GTK_CONCAT_MODEL (list);
+  GList *l;
+
+  /* FIXME: Use an RBTree to make this O(log N) */
+  for (l = self->models; l; l = l->next)
+    {
+      guint n = g_list_model_get_n_items (l->data);
+
+      if (position < n)
+        return g_list_model_get_item (l->data, position);
+
+      position -= n;
+    }
+
+  return NULL;
+}
+
+static void
+gtk_concat_model_list_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_concat_model_list_model_get_item_type;
+  iface->get_n_items = gtk_concat_model_list_model_get_n_items;
+  iface->get_item = gtk_concat_model_list_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkConcatModel, gtk_concat_model,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_concat_model_list_model_init))
+
+static void
+gtk_concat_model_items_changed (GListModel     *model,
+                                guint           position,
+                                guint           removed,
+                                guint           added,
+                                GtkConcatModel *self)
+{
+  GList *l;
+
+  for (l = self->models; l; l = l->next)
+    {
+      if (l->data == model)
+        break;
+
+      position += g_list_model_get_n_items (l->data);
+    }
+
+  self->n_items -= removed;
+  self->n_items += added;
+
+  g_list_model_items_changed (G_LIST_MODEL (self),
+                              position,
+                              removed,
+                              added);
+}
+
+static void
+gtk_concat_model_remove_internal (GtkConcatModel *self,
+                                  GListModel     *model,
+                                  gboolean        emit_signals)
+{
+  guint n_items, position;
+  GList *l;
+
+  position = 0;
+  for (l = self->models; l; l = l->next)
+    {
+      if (l->data == model)
+        break;
+
+      position += g_list_model_get_n_items (l->data);
+    }
+
+  g_return_if_fail (l != NULL);
+
+  self->models = g_list_delete_link (self->models, l);
+  n_items = g_list_model_get_n_items (model);
+  self->n_items -= n_items;
+  g_signal_handlers_disconnect_by_func (model, gtk_concat_model_items_changed, self);
+  g_object_unref (model);
+  
+  if (n_items && emit_signals)
+    g_list_model_items_changed (G_LIST_MODEL (self),
+                                position,
+                                n_items,
+                                0);
+}
+
+static void
+gtk_concat_model_dispose (GObject *object)
+{
+  GtkConcatModel *self = GTK_CONCAT_MODEL (object);
+
+  /* FIXME: Make this work without signal emissions */
+  while (self->models)
+    gtk_concat_model_remove_internal (self, self->models->data, FALSE);
+
+  G_OBJECT_CLASS (gtk_concat_model_parent_class)->dispose (object);
+}
+
+static void
+gtk_concat_model_class_init (GtkConcatModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gtk_concat_model_dispose;
+}
+
+static void
+gtk_concat_model_init (GtkConcatModel *self)
+{
+}
+
+GtkConcatModel *
+gtk_concat_model_new (GType item_type)
+{
+  GtkConcatModel *self;
+
+  g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL);
+
+  self = g_object_new (GTK_TYPE_CONCAT_MODEL, NULL);
+
+  self->item_type = item_type;
+
+  return self;
+}
+
+void
+gtk_concat_model_append (GtkConcatModel *self,
+                         GListModel     *model)
+{
+  guint n_items;
+
+  g_return_if_fail (GTK_IS_CONCAT_MODEL (self));
+  g_return_if_fail (G_IS_LIST_MODEL (model));
+  g_return_if_fail (g_type_is_a (g_list_model_get_item_type (model), self->item_type));
+
+  g_object_ref (model);
+  g_signal_connect (model, "items-changed", G_CALLBACK (gtk_concat_model_items_changed), self);
+  self->models = g_list_append (self->models, model);
+  n_items = g_list_model_get_n_items (model);
+  self->n_items += n_items;
+  
+  if (n_items)
+    g_list_model_items_changed (G_LIST_MODEL (self),
+                                self->n_items - n_items,
+                                0,
+                                n_items);
+}
+
+void
+gtk_concat_model_remove (GtkConcatModel *self,
+                         GListModel     *model)
+{
+  g_return_if_fail (GTK_IS_CONCAT_MODEL (self));
+  g_return_if_fail (G_IS_LIST_MODEL (model));
+
+  gtk_concat_model_remove_internal (self, model, TRUE);
+}
+
+GListModel *
+gtk_concat_model_get_model_for_item (GtkConcatModel *self,
+                                     guint           position)
+{
+  GList *l;
+
+  g_return_val_if_fail (GTK_IS_CONCAT_MODEL (self), NULL);
+
+  /* FIXME: Use an RBTree to make this O(log N) */
+  for (l = self->models; l; l = l->next)
+    {
+      guint n = g_list_model_get_n_items (l->data);
+
+      if (position < n)
+        return l->data;
+
+      position -= n;
+    }
+
+  return NULL;
+}
+
diff --git a/gtk/gtkconcatmodelprivate.h b/gtk/gtkconcatmodelprivate.h
new file mode 100644
index 0000000000..005a9f3da8
--- /dev/null
+++ b/gtk/gtkconcatmodelprivate.h
@@ -0,0 +1,52 @@
+/*
+ * 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_CONCAT_MODEL_H__
+#define __GTK_CONCAT_MODEL_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CONCAT_MODEL         (gtk_concat_model_get_type ())
+#define GTK_CONCAT_MODEL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_CONCAT_MODEL, 
GtkConcatModel))
+#define GTK_CONCAT_MODEL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_CONCAT_MODEL, 
GtkConcatModelClass))
+#define GTK_IS_CONCAT_MODEL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_CONCAT_MODEL))
+#define GTK_IS_CONCAT_MODEL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_CONCAT_MODEL))
+#define GTK_CONCAT_MODEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_CONCAT_MODEL, 
GtkConcatModelClass))
+
+typedef struct _GtkConcatModel GtkConcatModel;
+typedef struct _GtkConcatModelClass GtkConcatModelClass;
+
+GType                   gtk_concat_model_get_type                (void) G_GNUC_CONST;
+
+GtkConcatModel *        gtk_concat_model_new                     (GType                  item_type);
+
+void                    gtk_concat_model_append                  (GtkConcatModel        *self,
+                                                                  GListModel            *model);
+void                    gtk_concat_model_remove                  (GtkConcatModel        *self,
+                                                                  GListModel            *model);
+
+GListModel *            gtk_concat_model_get_model_for_item      (GtkConcatModel        *self,
+                                                                  guint                  position);
+
+G_END_DECLS
+
+#endif /* __GTK_CONCAT_MODEL_H__ */
diff --git a/gtk/gtkshortcutcontroller.c b/gtk/gtkshortcutcontroller.c
index b2417d15bc..c1a0d80848 100644
--- a/gtk/gtkshortcutcontroller.c
+++ b/gtk/gtkshortcutcontroller.c
@@ -34,6 +34,7 @@
 
 #include "gtkshortcutcontrollerprivate.h"
 
+#include "gtkconcatmodelprivate.h"
 #include "gtkeventcontrollerprivate.h"
 #include "gtkintl.h"
 #include "gtkshortcut.h"
@@ -53,7 +54,6 @@ struct _GtkShortcutController
   GdkModifierType mnemonics_modifiers;
 
   guint custom_shortcuts : 1;
-  guint run_managed : 1;
 };
 
 struct _GtkShortcutControllerClass
@@ -212,15 +212,27 @@ gtk_shortcut_controller_finalize (GObject *object)
 static gboolean
 gtk_shortcut_controller_trigger_shortcut (GtkShortcutController *self,
                                           GtkShortcut           *shortcut,
+                                          guint                  position,
                                           const GdkEvent        *event,
                                           gboolean               enable_mnemonics)
 {
+  GtkWidget *widget;
+
   if (!gtk_shortcut_trigger_trigger (gtk_shortcut_get_trigger (shortcut), event, enable_mnemonics))
     return FALSE;
 
+  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self));
+  if (!self->custom_shortcuts &&
+      GTK_IS_CONCAT_MODEL (self->shortcuts))
+    {
+      GListModel *model = gtk_concat_model_get_model_for_item (GTK_CONCAT_MODEL (self->shortcuts), position);
+      if (GTK_IS_SHORTCUT_CONTROLLER (model))
+        widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (model));
+    }
+
   return gtk_shortcut_action_activate (gtk_shortcut_get_action (shortcut),
                                        GTK_SHORTCUT_ACTION_EXCLUSIVE, /* FIXME */
-                                       gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)),
+                                       widget,
                                        gtk_shortcut_get_arguments (shortcut));
 }
 
@@ -230,34 +242,18 @@ gtk_shortcut_controller_run_controllers (GtkEventController *controller,
                                          gboolean            enable_mnemonics)
 {
   GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller);
-  GtkWidget *widget;
-  const GSList *l;
   guint i;
 
   for (i = 0; i < g_list_model_get_n_items (self->shortcuts); i++)
     {
       if (gtk_shortcut_controller_trigger_shortcut (self, 
                                                     g_list_model_get_item (self->shortcuts, i),
+                                                    i,
                                                     event,
                                                     enable_mnemonics))
         return TRUE;
     }
 
-  if (self->run_managed)
-    {
-      GtkPropagationPhase current_phase = gtk_event_controller_get_propagation_phase (controller);
-      widget = gtk_event_controller_get_widget (controller); 
-      
-      for (l = g_object_get_data (G_OBJECT (widget), "gtk-shortcut-controllers"); l; l = l->next)
-        {
-          if (gtk_event_controller_get_propagation_phase (l->data) != current_phase)
-            continue;
-
-          if (gtk_shortcut_controller_run_controllers (l->data, event, enable_mnemonics))
-            return TRUE;
-        }
-    }
-
   return FALSE;
 }
 
@@ -455,13 +451,6 @@ gtk_shortcut_controller_new_for_model (GListModel *model)
                        NULL);
 }
 
-void
-gtk_shortcut_controller_set_run_managed (GtkShortcutController  *controller,
-                                         gboolean                run_managed)
-{
-  controller->run_managed = run_managed;
-}
-
 /**
  * gtk_shortcut_controller_add_shortcut:
  * @self: the controller
diff --git a/gtk/gtkshortcutcontrollerprivate.h b/gtk/gtkshortcutcontrollerprivate.h
index a7086cd678..db1b52cecc 100644
--- a/gtk/gtkshortcutcontrollerprivate.h
+++ b/gtk/gtkshortcutcontrollerprivate.h
@@ -22,9 +22,6 @@
 
 #include "gtkshortcutcontroller.h"
 
-void                    gtk_shortcut_controller_set_run_managed         (GtkShortcutController  *controller,
-                                                                         gboolean                
run_managed);
-
 void                    gtk_shortcut_controller_root                    (GtkShortcutController  *controller);
 void                    gtk_shortcut_controller_unroot                  (GtkShortcutController  *controller);
 
diff --git a/gtk/gtkshortcutmanager.c b/gtk/gtkshortcutmanager.c
index d3036f8772..bf9ba15052 100644
--- a/gtk/gtkshortcutmanager.c
+++ b/gtk/gtkshortcutmanager.c
@@ -21,36 +21,51 @@
 
 #include "gtkshortcutmanager.h"
 
+#include "gtkconcatmodelprivate.h"
+
 G_DEFINE_INTERFACE (GtkShortcutManager, gtk_shortcut_manager, G_TYPE_OBJECT)
 
-static void
-complain_if_reached (gpointer should_be_gone)
+static GtkConcatModel *
+gtk_shortcut_manager_get_model (GtkShortcutManager  *self,
+                                GtkPropagationPhase  phase)
 {
-  g_critical ("Shortcut controllers failed to clean up.");
+  switch (phase)
+    {
+    case GTK_PHASE_CAPTURE:
+      return g_object_get_data (G_OBJECT (self), "gtk-shortcut-manager-capture");
+    case GTK_PHASE_BUBBLE:
+      return g_object_get_data (G_OBJECT (self), "gtk-shortcut-manager-bubble");
+    case GTK_PHASE_NONE:
+    case GTK_PHASE_TARGET:
+      return NULL;
+    default:
+      g_assert_not_reached ();
+      return NULL;
+    }
 }
 
 static void
 gtk_shortcut_manager_default_add_controller (GtkShortcutManager    *self,
                                              GtkShortcutController *controller)
 {
-  GSList *controllers;
+  GtkConcatModel *model;
 
-  controllers = g_object_steal_data (G_OBJECT (self), "gtk-shortcut-controllers");
-  controllers = g_slist_prepend (controllers, g_object_ref (controller));
-  g_object_set_data_full (G_OBJECT (self), "gtk-shortcut-controllers", controllers, complain_if_reached);
+  model = gtk_shortcut_manager_get_model (self, 
+                                          gtk_event_controller_get_propagation_phase (GTK_EVENT_CONTROLLER 
(controller)));
+  if (model)
+    gtk_concat_model_append (model, G_LIST_MODEL (controller));
 }
 
 static void
 gtk_shortcut_manager_default_remove_controller (GtkShortcutManager    *self,
                                                 GtkShortcutController *controller)
 {
-  GSList *controllers;
+  GtkConcatModel *model;
 
-  controllers = g_object_steal_data (G_OBJECT (self), "gtk-shortcut-controllers");
-  controllers = g_slist_remove (controllers, controller);
-  if (controllers)
-    g_object_set_data_full (G_OBJECT (self), "gtk-shortcut-controllers", controllers, complain_if_reached);
-  g_object_unref (controller);
+  model = gtk_shortcut_manager_get_model (self, 
+                                          gtk_event_controller_get_propagation_phase (GTK_EVENT_CONTROLLER 
(controller)));
+  if (model)
+    gtk_concat_model_remove (model, G_LIST_MODEL (controller));
 }
 
 static void
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index cfc97cef6a..b0493f4eb1 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -31,6 +31,7 @@
 #include "gtkapplicationprivate.h"
 #include "gtkbuildable.h"
 #include "gtkbuilderprivate.h"
+#include "gtkconcatmodelprivate.h"
 #include "gtkcontainerprivate.h"
 #include "gtkcssboxesprivate.h"
 #include "gtkcssfiltervalueprivate.h"
@@ -2491,13 +2492,17 @@ gtk_widget_init (GTypeInstance *instance, gpointer g_class)
 
   if (g_type_is_a (G_TYPE_FROM_CLASS (g_class), GTK_TYPE_SHORTCUT_MANAGER))
     {
-      controller = gtk_shortcut_controller_new ();
-      gtk_shortcut_controller_set_run_managed (GTK_SHORTCUT_CONTROLLER (controller), TRUE);
+      GtkConcatModel *model;
+      
+      model = gtk_concat_model_new (GTK_TYPE_SHORTCUT);
+      g_object_set_data_full (G_OBJECT (widget), "gtk-shortcut-manager-bubble", model, g_object_unref);
+      controller = gtk_shortcut_controller_new_for_model (G_LIST_MODEL (model));
       gtk_widget_add_controller (widget, controller);
 
-      controller = gtk_shortcut_controller_new ();
+      model = gtk_concat_model_new (GTK_TYPE_SHORTCUT);
+      g_object_set_data_full (G_OBJECT (widget), "gtk-shortcut-manager-capture", model, g_object_unref);
+      controller = gtk_shortcut_controller_new_for_model (G_LIST_MODEL (model));
       gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
-      gtk_shortcut_controller_set_run_managed (GTK_SHORTCUT_CONTROLLER (controller), TRUE);
       gtk_widget_add_controller (widget, controller);
     }
 
diff --git a/gtk/meson.build b/gtk/meson.build
index e3c8ba5516..37ae515d60 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -202,6 +202,7 @@ gtk_public_sources = files([
   'gtkconstraintguide.c',
   'gtkconstraintlayout.c',
   'gtkconstraint.c',
+  'gtkconcatmodel.c',
   'gtkcontainer.c',
   'gtkcssprovider.c',
   'gtkdialog.c',
diff --git a/testsuite/gtk/concatmodel.c b/testsuite/gtk/concatmodel.c
new file mode 100644
index 0000000000..3e6292b536
--- /dev/null
+++ b/testsuite/gtk/concatmodel.c
@@ -0,0 +1,372 @@
+/* GtkRBTree tests.
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ * Authors: Benjamin Otte <otte gnome org>
+ *
+ * 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 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/>.
+ */
+
+#include <locale.h>
+
+#include "../../gtk/gtkconcatmodelprivate.h"
+
+/* _gtk_rbtree_test */
+
+static GQuark number_quark;
+static GQuark changes_quark;
+
+static guint
+get (GListModel *model,
+     guint       position)
+{
+  GObject *object = g_list_model_get_item (model, position);
+  g_assert (object != NULL);
+  return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
+}
+
+static char *
+model_to_string (GListModel *model)
+{
+  GString *string = g_string_new (NULL);
+  guint i;
+
+  for (i = 0; i < g_list_model_get_n_items (model); i++)
+    {
+      if (i > 0)
+        g_string_append (string, " ");
+      g_string_append_printf (string, "%u", get (model, i));
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+static void
+add (GListStore *store,
+     guint       number)
+{
+  GObject *object;
+
+  /* o cannot be differentiated from NULL, so don't use it */
+  g_assert (number != 0);
+
+  object = g_object_new (G_TYPE_OBJECT, NULL);
+  g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number));
+  g_list_store_append (store, object);
+  g_object_unref (object);
+}
+
+static void
+remove (GListStore *store,
+        guint       position)
+{
+  g_list_store_remove (store, position);
+}
+
+#define assert_model(model, expected) G_STMT_START{ \
+  char *s = model_to_string (G_LIST_MODEL (model)); \
+  if (!g_str_equal (s, expected)) \
+     g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+         #model " == " #expected, s, "==", expected); \
+  g_free (s); \
+}G_STMT_END
+
+#define assert_changes(model, expected) G_STMT_START{ \
+  GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
+  if (!g_str_equal (changes->str, expected)) \
+     g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+         #model " == " #expected, changes->str, "==", expected); \
+  g_string_set_size (changes, 0); \
+}G_STMT_END
+
+static GListStore *
+new_empty_store (void)
+{
+  return g_list_store_new (G_TYPE_OBJECT);
+}
+
+static GListStore *
+new_store (guint start,
+           guint end)
+{
+  GListStore *store = new_empty_store ();
+  guint i;
+
+  for (i = start; i <= end; i++)
+    add (store, i);
+
+  return store;
+}
+
+static void
+items_changed (GListModel *model,
+               guint       position,
+               guint       removed,
+               guint       added,
+               GString    *changes)
+{
+  g_assert (removed != 0 || added != 0);
+
+  if (changes->len)
+    g_string_append (changes, ", ");
+
+  if (removed == 1 && added == 0)
+    {
+      g_string_append_printf (changes, "-%u", position);
+    }
+  else if (removed == 0 && added == 1)
+    {
+      g_string_append_printf (changes, "+%u", position);
+    }
+  else
+    {
+      g_string_append_printf (changes, "%u", position);
+      if (removed > 0)
+        g_string_append_printf (changes, "-%u", removed);
+      if (added > 0)
+        g_string_append_printf (changes, "+%u", added);
+    }
+}
+
+static void
+free_changes (gpointer data)
+{
+  GString *changes = data;
+
+  /* all changes must have been checked via assert_changes() before */
+  g_assert_cmpstr (changes->str, ==, "");
+
+  g_string_free (changes, TRUE);
+}
+
+static GtkConcatModel *
+new_model (void)
+{
+  GtkConcatModel *model = gtk_concat_model_new (G_TYPE_OBJECT);
+  GString *changes;
+
+  changes = g_string_new ("");
+  g_object_set_qdata_full (G_OBJECT(model), changes_quark, changes, free_changes);
+  g_signal_connect (model, "items-changed", G_CALLBACK (items_changed), changes);
+
+  return model;
+}
+
+static void
+test_append (void)
+{
+  GListStore *store = new_store (1, 3);
+  GtkConcatModel *concat = new_model ();
+
+  gtk_concat_model_append (concat, G_LIST_MODEL (store));
+
+  assert_model (concat, "1 2 3");
+  assert_changes (concat, "0+3");
+
+  g_object_unref (store);
+  g_object_unref (concat);
+}
+
+static void
+test_append_and_add (void)
+{
+  GListStore *store = new_empty_store ();
+  GtkConcatModel *concat = new_model ();
+
+  gtk_concat_model_append (concat, G_LIST_MODEL (store));
+
+  add (store, 1);
+  add (store, 2);
+  add (store, 3);
+  assert_model (concat, "1 2 3");
+  assert_changes (concat, "+0, +1, +2");
+
+  g_object_unref (store);
+  g_object_unref (concat);
+}
+
+static void
+test_append_and_remove (void)
+{
+  GListStore *store = new_store (1, 3);
+  GtkConcatModel *concat = new_model ();
+
+  gtk_concat_model_append (concat, G_LIST_MODEL (store));
+  gtk_concat_model_remove (concat, G_LIST_MODEL (store));
+
+  assert_model (concat, "");
+  assert_changes (concat, "0+3, 0-3");
+
+  /* Check that all signal handlers are gone */
+  g_list_store_remove_all (store);
+
+  g_object_unref (store);
+  g_object_unref (concat);
+}
+
+static void
+test_append_and_remove_items (void)
+{
+  GListStore *store = new_empty_store ();
+  GtkConcatModel *concat = new_model ();
+
+  gtk_concat_model_append (concat, G_LIST_MODEL (store));
+
+  add (store, 1);
+  add (store, 2);
+  add (store, 3);
+  remove (store, 0);
+  remove (store, 1);
+  remove (store, 0);
+
+  assert_model (concat, "");
+  assert_changes (concat, "+0, +1, +2, -0, -1, -0");
+
+  g_object_unref (store);
+  g_object_unref (concat);
+}
+
+static void
+test_append_many (void)
+{
+  GListStore *store[5] = { new_store (1, 3), new_store (4, 4), new_store (5, 10), new_empty_store (), 
new_store (11, 20) };
+  GtkConcatModel *concat = new_model ();
+  guint i;
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    gtk_concat_model_append (concat, G_LIST_MODEL (store[i]));
+
+  assert_model (concat, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20");
+  assert_changes (concat, "0+3, +3, 4+6, 10+10");
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    g_object_unref (store[i]);
+  g_object_unref (concat);
+}
+
+static void
+test_append_many_and_add (void)
+{
+  GListStore *store[3];
+  GtkConcatModel *concat = new_model ();
+  guint i, j;
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    {
+      store[i] = new_empty_store ();
+      gtk_concat_model_append (concat, G_LIST_MODEL (store[i]));
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    {
+      for (j = 0; j < G_N_ELEMENTS (store); j++)
+        {
+          add (store[(i + j) % G_N_ELEMENTS (store)], i * 3 + j + 1);
+        }
+    }
+
+  assert_model (concat, "1 6 8 2 4 9 3 5 7");
+  assert_changes (concat, "+0, +1, +2, +2, +4, +1, +6, +2, +5");
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    g_object_unref (store[i]);
+  g_object_unref (concat);
+}
+
+static void
+test_append_many_and_remove (void)
+{
+  GListStore *store[5];
+  GtkConcatModel *concat = new_model ();
+  guint i;
+
+  store[0] = new_empty_store ();
+  gtk_concat_model_append (concat, G_LIST_MODEL (store[0]));
+  for (i = 1; i < G_N_ELEMENTS (store); i++)
+    {
+      store[i] = new_store (i * (i - 1) / 2 + 1, i * (i + 1) / 2);
+      gtk_concat_model_append (concat, G_LIST_MODEL (store[i]));
+    }
+
+  assert_model (concat, "1 2 3 4 5 6 7 8 9 10");
+  assert_changes (concat, "+0, 1+2, 3+3, 6+4");
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    {
+      gtk_concat_model_remove (concat, G_LIST_MODEL (store[(3 * i) % G_N_ELEMENTS (store)]));
+    }
+
+  assert_model (concat, "");
+  assert_changes (concat, "3-3, -0, 2-4, 0-2");
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    {
+      g_list_store_remove_all (store[i]);
+      g_object_unref (store[i]);
+    }
+
+  g_object_unref (concat);
+}
+
+static void
+test_append_many_and_remove_items (void)
+{
+  GListStore *store[5];
+  GtkConcatModel *concat = new_model ();
+  guint i;
+
+  store[0] = new_empty_store ();
+  gtk_concat_model_append (concat, G_LIST_MODEL (store[0]));
+  for (i = 1; i < G_N_ELEMENTS (store); i++)
+    {
+      store[i] = new_store (i * (i - 1) / 2 + 1, i * (i + 1) / 2);
+      gtk_concat_model_append (concat, G_LIST_MODEL (store[i]));
+    }
+
+  assert_model (concat, "1 2 3 4 5 6 7 8 9 10");
+  assert_changes (concat, "+0, 1+2, 3+3, 6+4");
+
+  for (i = 1; i < G_N_ELEMENTS (store); i++)
+    {
+      remove (store[i], 3 % i);
+    }
+
+  assert_model (concat, "2 5 6 7 8 9");
+  assert_changes (concat, "-0, -1, -1, -6");
+
+  for (i = 0; i < G_N_ELEMENTS (store); i++)
+    g_object_unref (store[i]);
+  g_object_unref (concat);
+}
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  setlocale (LC_ALL, "C");
+  g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s";);
+
+  number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
+  changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
+
+  g_test_add_func ("/compatmodel/append", test_append);
+  g_test_add_func ("/compatmodel/append_and_add", test_append_and_add);
+  g_test_add_func ("/compatmodel/append_and_remove", test_append_and_remove);
+  g_test_add_func ("/compatmodel/append_and_remove_items", test_append_and_remove_items);
+  g_test_add_func ("/compatmodel/append_many", test_append_many);
+  g_test_add_func ("/compatmodel/append_many_and_add", test_append_many_and_add);
+  g_test_add_func ("/compatmodel/append_many_and_remove", test_append_many_and_remove);
+  g_test_add_func ("/compatmodel/append_many_and_remove_items", test_append_many_and_remove_items);
+
+  return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index e721ceaf49..d5a88927f0 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -22,6 +22,7 @@ tests = [
       '../../gtk/gtkconstraintexpression.c',
     ], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']
   ],
+  ['concatmodel', ['../../gtk/gtkconcatmodel.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']],
   ['cssprovider'],
   ['rbtree-crash', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']],
   ['defaultvalue'],


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