1/*
2 * Copyright © 2018 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkflattenlistmodel.h"
23
24#include "gtkrbtreeprivate.h"
25#include "gtkintl.h"
26#include "gtkprivate.h"
27
28/**
29 * GtkFlattenListModel:
30 *
31 * `GtkFlattenListModel` is a list model that concatenates other list models.
32 *
33 * `GtkFlattenListModel` takes a list model containing list models,
34 * and flattens it into a single model.
35 */
36
37enum {
38 PROP_0,
39 PROP_MODEL,
40 NUM_PROPERTIES
41};
42
43typedef struct _FlattenNode FlattenNode;
44typedef struct _FlattenAugment FlattenAugment;
45
46struct _FlattenNode
47{
48 GListModel *model;
49 GtkFlattenListModel *list;
50};
51
52struct _FlattenAugment
53{
54 guint n_items;
55 guint n_models;
56};
57
58struct _GtkFlattenListModel
59{
60 GObject parent_instance;
61
62 GListModel *model;
63 GtkRbTree *items; /* NULL if model == NULL */
64};
65
66struct _GtkFlattenListModelClass
67{
68 GObjectClass parent_class;
69};
70
71static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
72
73static FlattenNode *
74gtk_flatten_list_model_get_nth (GtkRbTree *tree,
75 guint position,
76 guint *model_position)
77{
78 FlattenNode *node, *tmp;
79 guint model_n_items;
80
81 node = gtk_rb_tree_get_root (tree);
82
83 while (node)
84 {
85 tmp = gtk_rb_tree_node_get_left (node);
86 if (tmp)
87 {
88 FlattenAugment *aug = gtk_rb_tree_get_augment (tree, node: tmp);
89 if (position < aug->n_items)
90 {
91 node = tmp;
92 continue;
93 }
94 position -= aug->n_items;
95 }
96
97 model_n_items = g_list_model_get_n_items (list: node->model);
98 if (position < model_n_items)
99 break;
100 position -= model_n_items;
101
102 node = gtk_rb_tree_node_get_right (node);
103 }
104
105 if (model_position)
106 *model_position = node ? position : 0;
107
108 return node;
109}
110
111static FlattenNode *
112gtk_flatten_list_model_get_nth_model (GtkRbTree *tree,
113 guint position,
114 guint *items_before)
115{
116 FlattenNode *node, *tmp;
117 guint before;
118
119 node = gtk_rb_tree_get_root (tree);
120 before = 0;
121
122 while (node)
123 {
124 tmp = gtk_rb_tree_node_get_left (node);
125 if (tmp)
126 {
127 FlattenAugment *aug = gtk_rb_tree_get_augment (tree, node: tmp);
128 if (position < aug->n_models)
129 {
130 node = tmp;
131 continue;
132 }
133 position -= aug->n_models;
134 before += aug->n_items;
135 }
136
137 if (position == 0)
138 break;
139 position--;
140 before += g_list_model_get_n_items (list: node->model);
141
142 node = gtk_rb_tree_node_get_right (node);
143 }
144
145 if (items_before)
146 *items_before = before;
147
148 return node;
149}
150
151static GType
152gtk_flatten_list_model_get_item_type (GListModel *list)
153{
154 return G_TYPE_OBJECT;
155}
156
157static guint
158gtk_flatten_list_model_get_n_items (GListModel *list)
159{
160 GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (ptr: list);
161 FlattenAugment *aug;
162 FlattenNode *node;
163
164 if (!self->items)
165 return 0;
166
167 node = gtk_rb_tree_get_root (tree: self->items);
168 if (node == NULL)
169 return 0;
170
171 aug = gtk_rb_tree_get_augment (tree: self->items, node);
172 return aug->n_items;
173}
174
175static gpointer
176gtk_flatten_list_model_get_item (GListModel *list,
177 guint position)
178{
179 GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (ptr: list);
180 FlattenNode *node;
181 guint model_pos;
182
183 if (!self->items)
184 return NULL;
185
186 node = gtk_flatten_list_model_get_nth (tree: self->items, position, model_position: &model_pos);
187 if (node == NULL)
188 return NULL;
189
190 return g_list_model_get_item (list: node->model, position: model_pos);
191}
192
193static void
194gtk_flatten_list_model_model_init (GListModelInterface *iface)
195{
196 iface->get_item_type = gtk_flatten_list_model_get_item_type;
197 iface->get_n_items = gtk_flatten_list_model_get_n_items;
198 iface->get_item = gtk_flatten_list_model_get_item;
199}
200
201G_DEFINE_TYPE_WITH_CODE (GtkFlattenListModel, gtk_flatten_list_model, G_TYPE_OBJECT,
202 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_flatten_list_model_model_init))
203
204static void
205gtk_flatten_list_model_items_changed_cb (GListModel *model,
206 guint position,
207 guint removed,
208 guint added,
209 gpointer _node)
210{
211 FlattenNode *node = _node, *parent, *left;
212 GtkFlattenListModel *self = node->list;
213 guint real_position;
214
215 gtk_rb_tree_node_mark_dirty (node);
216 real_position = position;
217
218 left = gtk_rb_tree_node_get_left (node);
219 if (left)
220 {
221 FlattenAugment *aug = gtk_rb_tree_get_augment (tree: self->items, node: left);
222 real_position += aug->n_items;
223 }
224
225 for (;
226 (parent = gtk_rb_tree_node_get_parent (node)) != NULL;
227 node = parent)
228 {
229 left = gtk_rb_tree_node_get_left (node: parent);
230 if (left != node)
231 {
232 if (left)
233 {
234 FlattenAugment *aug = gtk_rb_tree_get_augment (tree: self->items, node: left);
235 real_position += aug->n_items;
236 }
237 real_position += g_list_model_get_n_items (list: parent->model);
238 }
239 }
240
241 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: real_position, removed, added);
242}
243
244static void
245gtk_flatten_list_model_clear_node (gpointer _node)
246{
247 FlattenNode *node= _node;
248
249 g_signal_handlers_disconnect_by_func (node->model, gtk_flatten_list_model_items_changed_cb, node);
250 g_object_unref (object: node->model);
251}
252
253static void
254gtk_flatten_list_model_augment (GtkRbTree *flatten,
255 gpointer _aug,
256 gpointer _node,
257 gpointer left,
258 gpointer right)
259{
260 FlattenNode *node = _node;
261 FlattenAugment *aug = _aug;
262
263 aug->n_items = g_list_model_get_n_items (list: node->model);
264 aug->n_models = 1;
265
266 if (left)
267 {
268 FlattenAugment *left_aug = gtk_rb_tree_get_augment (tree: flatten, node: left);
269 aug->n_items += left_aug->n_items;
270 aug->n_models += left_aug->n_models;
271 }
272 if (right)
273 {
274 FlattenAugment *right_aug = gtk_rb_tree_get_augment (tree: flatten, node: right);
275 aug->n_items += right_aug->n_items;
276 aug->n_models += right_aug->n_models;
277 }
278}
279
280static guint
281gtk_flatten_list_model_add_items (GtkFlattenListModel *self,
282 FlattenNode *after,
283 guint position,
284 guint n)
285{
286 FlattenNode *node;
287 guint added, i;
288
289 added = 0;
290 for (i = 0; i < n; i++)
291 {
292 node = gtk_rb_tree_insert_before (tree: self->items, node: after);
293 node->model = g_list_model_get_item (list: self->model, position: position + i);
294 g_signal_connect (node->model,
295 "items-changed",
296 G_CALLBACK (gtk_flatten_list_model_items_changed_cb),
297 node);
298 node->list = self;
299 added += g_list_model_get_n_items (list: node->model);
300 }
301
302 return added;
303}
304
305static void
306gtk_flatten_list_model_set_property (GObject *object,
307 guint prop_id,
308 const GValue *value,
309 GParamSpec *pspec)
310{
311 GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (ptr: object);
312
313 switch (prop_id)
314 {
315 case PROP_MODEL:
316 gtk_flatten_list_model_set_model (self, model: g_value_get_object (value));
317 break;
318
319 default:
320 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321 break;
322 }
323}
324
325static void
326gtk_flatten_list_model_get_property (GObject *object,
327 guint prop_id,
328 GValue *value,
329 GParamSpec *pspec)
330{
331 GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (ptr: object);
332
333 switch (prop_id)
334 {
335 case PROP_MODEL:
336 g_value_set_object (value, v_object: self->model);
337 break;
338
339 default:
340 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
341 break;
342 }
343}
344
345static void
346gtk_flatten_list_model_model_items_changed_cb (GListModel *model,
347 guint position,
348 guint removed,
349 guint added,
350 GtkFlattenListModel *self)
351{
352 FlattenNode *node;
353 guint i, real_position, real_removed, real_added;
354
355 node = gtk_flatten_list_model_get_nth_model (tree: self->items, position, items_before: &real_position);
356
357 real_removed = 0;
358 for (i = 0; i < removed; i++)
359 {
360 FlattenNode *next = gtk_rb_tree_node_get_next (node);
361 real_removed += g_list_model_get_n_items (list: node->model);
362 gtk_rb_tree_remove (tree: self->items, node);
363 node = next;
364 }
365
366 real_added = gtk_flatten_list_model_add_items (self, after: node, position, n: added);
367
368 if (real_removed > 0 || real_added > 0)
369 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: real_position, removed: real_removed, added: real_added);
370}
371
372static void
373gtk_flatten_list_clear_model (GtkFlattenListModel *self)
374{
375 if (self->model)
376 {
377 g_signal_handlers_disconnect_by_func (self->model, gtk_flatten_list_model_model_items_changed_cb, self);
378 g_clear_object (&self->model);
379 g_clear_pointer (&self->items, gtk_rb_tree_unref);
380 }
381}
382
383static void
384gtk_flatten_list_model_dispose (GObject *object)
385{
386 GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (ptr: object);
387
388 gtk_flatten_list_clear_model (self);
389
390 G_OBJECT_CLASS (gtk_flatten_list_model_parent_class)->dispose (object);
391}
392
393static void
394gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class)
395{
396 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
397
398 gobject_class->set_property = gtk_flatten_list_model_set_property;
399 gobject_class->get_property = gtk_flatten_list_model_get_property;
400 gobject_class->dispose = gtk_flatten_list_model_dispose;
401
402 /**
403 * GtkFlattenListModel:model: (attributes org.gtk.Property.get=gtk_flatten_list_model_get_model org.gtk.Property.set=gtk_flatten_list_model_set_model)
404 *
405 * The model being flattened.
406 */
407 properties[PROP_MODEL] =
408 g_param_spec_object (name: "model",
409 P_("Model"),
410 P_("The model being flattened"),
411 G_TYPE_LIST_MODEL,
412 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
413
414 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
415}
416
417static void
418gtk_flatten_list_model_init (GtkFlattenListModel *self)
419{
420}
421
422/**
423 * gtk_flatten_list_model_new:
424 * @model: (nullable) (transfer full): the model to be flattened
425 *
426 * Creates a new `GtkFlattenListModel` that flattens @list.
427 *
428 * Returns: a new `GtkFlattenListModel`
429 */
430GtkFlattenListModel *
431gtk_flatten_list_model_new (GListModel *model)
432{
433 GtkFlattenListModel *result;
434
435 g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
436
437 result = g_object_new (GTK_TYPE_FLATTEN_LIST_MODEL,
438 first_property_name: "model", model,
439 NULL);
440
441 /* we consume the reference */
442 g_clear_object (&model);
443
444 return result;
445}
446
447/**
448 * gtk_flatten_list_model_set_model: (attributes org.gtk.Method.set_property=model)
449 * @self: a `GtkFlattenListModel`
450 * @model: (nullable) (transfer none): the new model
451 *
452 * Sets a new model to be flattened.
453 */
454void
455gtk_flatten_list_model_set_model (GtkFlattenListModel *self,
456 GListModel *model)
457{
458 guint removed, added = 0;
459
460 g_return_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self));
461 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
462
463 if (self->model == model)
464 return;
465
466 removed = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
467 gtk_flatten_list_clear_model (self);
468
469 self->model = model;
470
471 if (model)
472 {
473 g_object_ref (model);
474 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_flatten_list_model_model_items_changed_cb), self);
475 self->items = gtk_rb_tree_new (FlattenNode,
476 FlattenAugment,
477 gtk_flatten_list_model_augment,
478 gtk_flatten_list_model_clear_node,
479 NULL);
480
481 added = gtk_flatten_list_model_add_items (self, NULL, position: 0, n: g_list_model_get_n_items (list: model));
482 }
483
484 if (removed > 0 || added > 0)
485 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed, added);
486
487 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
488}
489
490/**
491 * gtk_flatten_list_model_get_model: (attributes org.gtk.Method.get_property=model)
492 * @self: a `GtkFlattenListModel`
493 *
494 * Gets the model set via gtk_flatten_list_model_set_model().
495 *
496 * Returns: (nullable) (transfer none): The model flattened by @self
497 **/
498GListModel *
499gtk_flatten_list_model_get_model (GtkFlattenListModel *self)
500{
501 g_return_val_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self), NULL);
502
503 return self->model;
504}
505
506/**
507 * gtk_flatten_list_model_get_model_for_item:
508 * @self: a `GtkFlattenListModel`
509 * @position: a position
510 *
511 * Returns the model containing the item at the given position.
512 *
513 * Returns: (transfer none) (nullable): the model containing the item at @position
514 */
515GListModel *
516gtk_flatten_list_model_get_model_for_item (GtkFlattenListModel *self,
517 guint position)
518{
519 FlattenNode *node;
520
521 if (!self->items)
522 return NULL;
523
524 node = gtk_flatten_list_model_get_nth (tree: self->items, position, NULL);
525 if (node == NULL)
526 return NULL;
527
528 return node->model;
529}
530

source code of gtk/gtk/gtkflattenlistmodel.c