/* * 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 . * * Authors: Benjamin Otte */ /* * 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; }