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 | |
37 | enum { |
38 | PROP_0, |
39 | PROP_MODEL, |
40 | NUM_PROPERTIES |
41 | }; |
42 | |
43 | typedef struct _FlattenNode FlattenNode; |
44 | typedef struct _FlattenAugment FlattenAugment; |
45 | |
46 | struct _FlattenNode |
47 | { |
48 | GListModel *model; |
49 | GtkFlattenListModel *list; |
50 | }; |
51 | |
52 | struct _FlattenAugment |
53 | { |
54 | guint n_items; |
55 | guint n_models; |
56 | }; |
57 | |
58 | struct _GtkFlattenListModel |
59 | { |
60 | GObject parent_instance; |
61 | |
62 | GListModel *model; |
63 | GtkRbTree *items; /* NULL if model == NULL */ |
64 | }; |
65 | |
66 | struct _GtkFlattenListModelClass |
67 | { |
68 | GObjectClass parent_class; |
69 | }; |
70 | |
71 | static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; |
72 | |
73 | static FlattenNode * |
74 | gtk_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 | |
111 | static FlattenNode * |
112 | gtk_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 | |
151 | static GType |
152 | gtk_flatten_list_model_get_item_type (GListModel *list) |
153 | { |
154 | return G_TYPE_OBJECT; |
155 | } |
156 | |
157 | static guint |
158 | gtk_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 | |
175 | static gpointer |
176 | gtk_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 | |
193 | static void |
194 | gtk_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 | |
201 | G_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 | |
204 | static void |
205 | gtk_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 | |
244 | static void |
245 | gtk_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 | |
253 | static void |
254 | gtk_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 | |
280 | static guint |
281 | gtk_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 | |
305 | static void |
306 | gtk_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 | |
325 | static void |
326 | gtk_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 | |
345 | static void |
346 | gtk_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 | |
372 | static void |
373 | gtk_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 | |
383 | static void |
384 | gtk_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 | |
393 | static void |
394 | gtk_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 | |
417 | static void |
418 | gtk_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 | */ |
430 | GtkFlattenListModel * |
431 | gtk_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 | */ |
454 | void |
455 | gtk_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 | **/ |
498 | GListModel * |
499 | gtk_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 | */ |
515 | GListModel * |
516 | gtk_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 | |