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 "gtktreelistmodel.h" |
23 | |
24 | #include "gtkrbtreeprivate.h" |
25 | #include "gtkintl.h" |
26 | #include "gtkprivate.h" |
27 | |
28 | /** |
29 | * GtkTreeListModel: |
30 | * |
31 | * `GtkTreeListModel` is a list model that can create child models on demand. |
32 | */ |
33 | |
34 | enum { |
35 | PROP_0, |
36 | PROP_AUTOEXPAND, |
37 | PROP_MODEL, |
38 | PROP_PASSTHROUGH, |
39 | NUM_PROPERTIES |
40 | }; |
41 | |
42 | typedef struct _TreeNode TreeNode; |
43 | typedef struct _TreeAugment TreeAugment; |
44 | |
45 | struct _TreeNode |
46 | { |
47 | GListModel *model; |
48 | GtkTreeListRow *row; |
49 | GtkRbTree *children; |
50 | union { |
51 | TreeNode *parent; |
52 | GtkTreeListModel *list; |
53 | }; |
54 | |
55 | guint empty : 1; |
56 | guint is_root : 1; |
57 | }; |
58 | |
59 | struct _TreeAugment |
60 | { |
61 | guint n_items; |
62 | guint n_local; |
63 | }; |
64 | |
65 | struct _GtkTreeListModel |
66 | { |
67 | GObject parent_instance; |
68 | |
69 | TreeNode root_node; |
70 | |
71 | GtkTreeListModelCreateModelFunc create_func; |
72 | gpointer user_data; |
73 | GDestroyNotify user_destroy; |
74 | |
75 | guint autoexpand : 1; |
76 | guint passthrough : 1; |
77 | }; |
78 | |
79 | struct _GtkTreeListModelClass |
80 | { |
81 | GObjectClass parent_class; |
82 | }; |
83 | |
84 | struct _GtkTreeListRow |
85 | { |
86 | GObject parent_instance; |
87 | |
88 | TreeNode *node; /* NULL when the row has been destroyed */ |
89 | }; |
90 | |
91 | struct _GtkTreeListRowClass |
92 | { |
93 | GObjectClass parent_class; |
94 | }; |
95 | |
96 | static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; |
97 | |
98 | static GtkTreeListModel * |
99 | tree_node_get_tree_list_model (TreeNode *node) |
100 | { |
101 | for (; !node->is_root; node = node->parent) |
102 | { } |
103 | |
104 | return node->list; |
105 | } |
106 | |
107 | static TreeNode * |
108 | tree_node_get_nth_child (TreeNode *node, |
109 | guint position) |
110 | { |
111 | GtkRbTree *tree; |
112 | TreeNode *child, *tmp; |
113 | TreeAugment *aug; |
114 | |
115 | tree = node->children; |
116 | child = gtk_rb_tree_get_root (tree); |
117 | |
118 | while (child) |
119 | { |
120 | tmp = gtk_rb_tree_node_get_left (node: child); |
121 | if (tmp) |
122 | { |
123 | aug = gtk_rb_tree_get_augment (tree, node: tmp); |
124 | if (position < aug->n_local) |
125 | { |
126 | child = tmp; |
127 | continue; |
128 | } |
129 | position -= aug->n_local; |
130 | } |
131 | |
132 | if (position == 0) |
133 | return child; |
134 | |
135 | position--; |
136 | |
137 | child = gtk_rb_tree_node_get_right (node: child); |
138 | } |
139 | |
140 | return NULL; |
141 | } |
142 | |
143 | static guint |
144 | tree_node_get_n_children (TreeNode *node) |
145 | { |
146 | TreeAugment *child_aug; |
147 | TreeNode *child_node; |
148 | |
149 | if (node->children == NULL) |
150 | return 0; |
151 | |
152 | child_node = gtk_rb_tree_get_root (tree: node->children); |
153 | if (child_node == NULL) |
154 | return 0; |
155 | |
156 | child_aug = gtk_rb_tree_get_augment (tree: node->children, node: child_node); |
157 | |
158 | return child_aug->n_items; |
159 | } |
160 | |
161 | static guint |
162 | tree_node_get_local_position (GtkRbTree *tree, |
163 | TreeNode *node) |
164 | { |
165 | TreeNode *left, *parent; |
166 | TreeAugment *left_aug; |
167 | guint n; |
168 | |
169 | left = gtk_rb_tree_node_get_left (node); |
170 | if (left) |
171 | { |
172 | left_aug = gtk_rb_tree_get_augment (tree, node: left); |
173 | n = left_aug->n_local; |
174 | } |
175 | else |
176 | { |
177 | n = 0; |
178 | } |
179 | |
180 | for (parent = gtk_rb_tree_node_get_parent (node); |
181 | parent; |
182 | parent = gtk_rb_tree_node_get_parent (node)) |
183 | { |
184 | left = gtk_rb_tree_node_get_left (node: parent); |
185 | if (left == node) |
186 | { |
187 | /* we are the left node, nothing changes */ |
188 | } |
189 | else |
190 | { |
191 | /* we are the right node */ |
192 | n++; |
193 | if (left) |
194 | { |
195 | left_aug = gtk_rb_tree_get_augment (tree, node: left); |
196 | n += left_aug->n_local; |
197 | } |
198 | } |
199 | node = parent; |
200 | } |
201 | |
202 | return n; |
203 | } |
204 | |
205 | static guint |
206 | tree_node_get_position (TreeNode *node) |
207 | { |
208 | GtkRbTree *tree; |
209 | TreeNode *left, *parent; |
210 | TreeAugment *left_aug; |
211 | guint n; |
212 | |
213 | for (n = 0; |
214 | !node->is_root; |
215 | node = node->parent, n++) |
216 | { |
217 | tree = node->parent->children; |
218 | |
219 | left = gtk_rb_tree_node_get_left (node); |
220 | if (left) |
221 | { |
222 | left_aug = gtk_rb_tree_get_augment (tree, node: left); |
223 | n += left_aug->n_items; |
224 | } |
225 | |
226 | for (parent = gtk_rb_tree_node_get_parent (node); |
227 | parent; |
228 | parent = gtk_rb_tree_node_get_parent (node)) |
229 | { |
230 | left = gtk_rb_tree_node_get_left (node: parent); |
231 | if (left == node) |
232 | { |
233 | /* we are the left node, nothing changes */ |
234 | } |
235 | else |
236 | { |
237 | /* we are the right node */ |
238 | n += 1 + tree_node_get_n_children (node: parent); |
239 | if (left) |
240 | { |
241 | left_aug = gtk_rb_tree_get_augment (tree, node: left); |
242 | n += left_aug->n_items; |
243 | } |
244 | } |
245 | node = parent; |
246 | } |
247 | } |
248 | /* the root isn't visible */ |
249 | n--; |
250 | |
251 | return n; |
252 | } |
253 | |
254 | static void |
255 | tree_node_mark_dirty (TreeNode *node) |
256 | { |
257 | for (; |
258 | !node->is_root; |
259 | node = node->parent) |
260 | { |
261 | gtk_rb_tree_node_mark_dirty (node); |
262 | } |
263 | } |
264 | |
265 | static TreeNode * |
266 | gtk_tree_list_model_get_nth (GtkTreeListModel *self, |
267 | guint position) |
268 | { |
269 | GtkRbTree *tree; |
270 | TreeNode *node, *tmp; |
271 | guint n_children; |
272 | |
273 | n_children = tree_node_get_n_children (node: &self->root_node); |
274 | if (n_children <= position) |
275 | return NULL; |
276 | |
277 | tree = self->root_node.children; |
278 | node = gtk_rb_tree_get_root (tree); |
279 | |
280 | while (TRUE) |
281 | { |
282 | tmp = gtk_rb_tree_node_get_left (node); |
283 | if (tmp) |
284 | { |
285 | TreeAugment *aug = gtk_rb_tree_get_augment (tree, node: tmp); |
286 | if (position < aug->n_items) |
287 | { |
288 | node = tmp; |
289 | continue; |
290 | } |
291 | position -= aug->n_items; |
292 | } |
293 | |
294 | if (position == 0) |
295 | return node; |
296 | |
297 | position--; |
298 | |
299 | n_children = tree_node_get_n_children (node); |
300 | if (position < n_children) |
301 | { |
302 | tree = node->children; |
303 | node = gtk_rb_tree_get_root (tree); |
304 | continue; |
305 | } |
306 | position -= n_children; |
307 | |
308 | node = gtk_rb_tree_node_get_right (node); |
309 | } |
310 | |
311 | g_return_val_if_reached (NULL); |
312 | } |
313 | |
314 | static GListModel * |
315 | tree_node_create_model (GtkTreeListModel *self, |
316 | TreeNode *node) |
317 | { |
318 | TreeNode *parent = node->parent; |
319 | GListModel *model; |
320 | GObject *item; |
321 | |
322 | item = g_list_model_get_item (list: parent->model, |
323 | position: tree_node_get_local_position (tree: parent->children, node)); |
324 | model = self->create_func (item, self->user_data); |
325 | g_object_unref (object: item); |
326 | if (model == NULL) |
327 | node->empty = TRUE; |
328 | |
329 | return model; |
330 | } |
331 | |
332 | static gpointer |
333 | tree_node_get_item (TreeNode *node) |
334 | { |
335 | TreeNode *parent; |
336 | |
337 | parent = node->parent; |
338 | return g_list_model_get_item (list: parent->model, |
339 | position: tree_node_get_local_position (tree: parent->children, node)); |
340 | } |
341 | |
342 | static GtkTreeListRow * |
343 | tree_node_get_row (TreeNode *node) |
344 | { |
345 | if (node->row) |
346 | { |
347 | return g_object_ref (node->row); |
348 | } |
349 | else |
350 | { |
351 | node->row = g_object_new (GTK_TYPE_TREE_LIST_ROW, NULL); |
352 | node->row->node = node; |
353 | |
354 | return node->row; |
355 | } |
356 | } |
357 | |
358 | static guint |
359 | gtk_tree_list_model_expand_node (GtkTreeListModel *self, |
360 | TreeNode *node); |
361 | |
362 | static void |
363 | gtk_tree_list_model_items_changed_cb (GListModel *model, |
364 | guint position, |
365 | guint removed, |
366 | guint added, |
367 | TreeNode *node) |
368 | { |
369 | GtkTreeListModel *self; |
370 | TreeNode *child; |
371 | guint i, tree_position, tree_removed, tree_added, n_local; |
372 | |
373 | self = tree_node_get_tree_list_model (node); |
374 | n_local = g_list_model_get_n_items (list: model) - added + removed; |
375 | |
376 | if (position < n_local) |
377 | { |
378 | child = tree_node_get_nth_child (node, position); |
379 | tree_position = tree_node_get_position (node: child); |
380 | } |
381 | else |
382 | { |
383 | child = NULL; |
384 | tree_position = tree_node_get_position (node) + tree_node_get_n_children (node) + 1; |
385 | } |
386 | |
387 | if (removed) |
388 | { |
389 | TreeNode *tmp; |
390 | |
391 | g_assert (child != NULL); |
392 | if (position + removed < n_local) |
393 | { |
394 | TreeNode *end = tree_node_get_nth_child (node, position: position + removed); |
395 | tree_removed = tree_node_get_position (node: end) - tree_position; |
396 | } |
397 | else |
398 | { |
399 | tree_removed = tree_node_get_position (node) + tree_node_get_n_children (node) + 1 - tree_position; |
400 | } |
401 | |
402 | for (i = 0; i < removed; i++) |
403 | { |
404 | tmp = child; |
405 | child = gtk_rb_tree_node_get_next (node: child); |
406 | gtk_rb_tree_remove (tree: node->children, node: tmp); |
407 | } |
408 | } |
409 | else |
410 | { |
411 | tree_removed = 0; |
412 | } |
413 | |
414 | tree_added = added; |
415 | for (i = 0; i < added; i++) |
416 | { |
417 | child = gtk_rb_tree_insert_before (tree: node->children, node: child); |
418 | child->parent = node; |
419 | } |
420 | if (self->autoexpand) |
421 | { |
422 | for (i = 0; i < added; i++) |
423 | { |
424 | tree_added += gtk_tree_list_model_expand_node (self, node: child); |
425 | child = gtk_rb_tree_node_get_next (node: child); |
426 | } |
427 | } |
428 | |
429 | tree_node_mark_dirty (node); |
430 | |
431 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), |
432 | position: tree_position, |
433 | removed: tree_removed, |
434 | added: tree_added); |
435 | } |
436 | |
437 | static void gtk_tree_list_row_destroy (GtkTreeListRow *row); |
438 | |
439 | static void |
440 | gtk_tree_list_model_clear_node_children (TreeNode *node) |
441 | { |
442 | if (node->model) |
443 | { |
444 | g_signal_handlers_disconnect_by_func (node->model, |
445 | gtk_tree_list_model_items_changed_cb, |
446 | node); |
447 | g_clear_object (&node->model); |
448 | } |
449 | |
450 | g_clear_pointer (&node->children, gtk_rb_tree_unref); |
451 | } |
452 | |
453 | static void |
454 | gtk_tree_list_model_clear_node (gpointer data) |
455 | { |
456 | TreeNode *node = data; |
457 | |
458 | if (node->row) |
459 | gtk_tree_list_row_destroy (row: node->row); |
460 | |
461 | gtk_tree_list_model_clear_node_children (node); |
462 | } |
463 | |
464 | static void |
465 | gtk_tree_list_model_augment (GtkRbTree *tree, |
466 | gpointer _aug, |
467 | gpointer _node, |
468 | gpointer left, |
469 | gpointer right) |
470 | { |
471 | TreeAugment *aug = _aug; |
472 | |
473 | aug->n_items = 1; |
474 | aug->n_items += tree_node_get_n_children (node: _node); |
475 | aug->n_local = 1; |
476 | |
477 | if (left) |
478 | { |
479 | TreeAugment *left_aug = gtk_rb_tree_get_augment (tree, node: left); |
480 | aug->n_items += left_aug->n_items; |
481 | aug->n_local += left_aug->n_local; |
482 | } |
483 | if (right) |
484 | { |
485 | TreeAugment *right_aug = gtk_rb_tree_get_augment (tree, node: right); |
486 | aug->n_items += right_aug->n_items; |
487 | aug->n_local += right_aug->n_local; |
488 | } |
489 | } |
490 | |
491 | static void |
492 | gtk_tree_list_model_init_node (GtkTreeListModel *list, |
493 | TreeNode *self, |
494 | GListModel *model) |
495 | { |
496 | gsize i, n; |
497 | TreeNode *node; |
498 | |
499 | self->model = model; |
500 | g_signal_connect (model, |
501 | "items-changed" , |
502 | G_CALLBACK (gtk_tree_list_model_items_changed_cb), |
503 | self); |
504 | self->children = gtk_rb_tree_new (TreeNode, |
505 | TreeAugment, |
506 | gtk_tree_list_model_augment, |
507 | gtk_tree_list_model_clear_node, |
508 | NULL); |
509 | |
510 | n = g_list_model_get_n_items (list: model); |
511 | node = NULL; |
512 | for (i = 0; i < n; i++) |
513 | { |
514 | node = gtk_rb_tree_insert_after (tree: self->children, node); |
515 | node->parent = self; |
516 | if (list->autoexpand) |
517 | gtk_tree_list_model_expand_node (self: list, node); |
518 | } |
519 | } |
520 | |
521 | static guint |
522 | gtk_tree_list_model_expand_node (GtkTreeListModel *self, |
523 | TreeNode *node) |
524 | { |
525 | GListModel *model; |
526 | |
527 | if (node->empty) |
528 | return 0; |
529 | |
530 | if (node->model != NULL) |
531 | return 0; |
532 | |
533 | model = tree_node_create_model (self, node); |
534 | |
535 | if (model == NULL) |
536 | return 0; |
537 | |
538 | gtk_tree_list_model_init_node (list: self, self: node, model); |
539 | |
540 | tree_node_mark_dirty (node); |
541 | |
542 | return tree_node_get_n_children (node); |
543 | } |
544 | |
545 | static guint |
546 | gtk_tree_list_model_collapse_node (GtkTreeListModel *self, |
547 | TreeNode *node) |
548 | { |
549 | guint n_items; |
550 | |
551 | if (node->model == NULL) |
552 | return 0; |
553 | |
554 | n_items = tree_node_get_n_children (node); |
555 | |
556 | gtk_tree_list_model_clear_node_children (node); |
557 | |
558 | tree_node_mark_dirty (node); |
559 | |
560 | return n_items; |
561 | } |
562 | |
563 | |
564 | static GType |
565 | gtk_tree_list_model_get_item_type (GListModel *list) |
566 | { |
567 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: list); |
568 | |
569 | if (self->passthrough) |
570 | return G_TYPE_OBJECT; |
571 | else |
572 | return GTK_TYPE_TREE_LIST_ROW; |
573 | } |
574 | |
575 | static guint |
576 | gtk_tree_list_model_get_n_items (GListModel *list) |
577 | { |
578 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: list); |
579 | |
580 | return tree_node_get_n_children (node: &self->root_node); |
581 | } |
582 | |
583 | static gpointer |
584 | gtk_tree_list_model_get_item (GListModel *list, |
585 | guint position) |
586 | { |
587 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: list); |
588 | TreeNode *node; |
589 | |
590 | node = gtk_tree_list_model_get_nth (self, position); |
591 | if (node == NULL) |
592 | return NULL; |
593 | |
594 | if (self->passthrough) |
595 | { |
596 | return tree_node_get_item (node); |
597 | } |
598 | else |
599 | { |
600 | return tree_node_get_row (node); |
601 | } |
602 | } |
603 | |
604 | static void |
605 | gtk_tree_list_model_model_init (GListModelInterface *iface) |
606 | { |
607 | iface->get_item_type = gtk_tree_list_model_get_item_type; |
608 | iface->get_n_items = gtk_tree_list_model_get_n_items; |
609 | iface->get_item = gtk_tree_list_model_get_item; |
610 | } |
611 | |
612 | G_DEFINE_TYPE_WITH_CODE (GtkTreeListModel, gtk_tree_list_model, G_TYPE_OBJECT, |
613 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tree_list_model_model_init)) |
614 | |
615 | static void |
616 | gtk_tree_list_model_set_property (GObject *object, |
617 | guint prop_id, |
618 | const GValue *value, |
619 | GParamSpec *pspec) |
620 | { |
621 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: object); |
622 | |
623 | switch (prop_id) |
624 | { |
625 | case PROP_AUTOEXPAND: |
626 | gtk_tree_list_model_set_autoexpand (self, autoexpand: g_value_get_boolean (value)); |
627 | break; |
628 | |
629 | case PROP_PASSTHROUGH: |
630 | self->passthrough = g_value_get_boolean (value); |
631 | break; |
632 | |
633 | default: |
634 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
635 | break; |
636 | } |
637 | } |
638 | |
639 | static void |
640 | gtk_tree_list_model_get_property (GObject *object, |
641 | guint prop_id, |
642 | GValue *value, |
643 | GParamSpec *pspec) |
644 | { |
645 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: object); |
646 | |
647 | switch (prop_id) |
648 | { |
649 | case PROP_AUTOEXPAND: |
650 | g_value_set_boolean (value, v_boolean: self->autoexpand); |
651 | break; |
652 | |
653 | case PROP_MODEL: |
654 | g_value_set_object (value, v_object: self->root_node.model); |
655 | break; |
656 | |
657 | case PROP_PASSTHROUGH: |
658 | g_value_set_boolean (value, v_boolean: self->passthrough); |
659 | break; |
660 | |
661 | default: |
662 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
663 | break; |
664 | } |
665 | } |
666 | |
667 | static void |
668 | gtk_tree_list_model_finalize (GObject *object) |
669 | { |
670 | GtkTreeListModel *self = GTK_TREE_LIST_MODEL (ptr: object); |
671 | |
672 | gtk_tree_list_model_clear_node (data: &self->root_node); |
673 | if (self->user_destroy) |
674 | self->user_destroy (self->user_data); |
675 | |
676 | G_OBJECT_CLASS (gtk_tree_list_model_parent_class)->finalize (object); |
677 | } |
678 | |
679 | static void |
680 | gtk_tree_list_model_class_init (GtkTreeListModelClass *class) |
681 | { |
682 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
683 | |
684 | gobject_class->set_property = gtk_tree_list_model_set_property; |
685 | gobject_class->get_property = gtk_tree_list_model_get_property; |
686 | gobject_class->finalize = gtk_tree_list_model_finalize; |
687 | |
688 | /** |
689 | * GtkTreeListModel:autoexpand: (attributes org.gtk.Property.get=gtk_tree_list_model_get_autoexpand org.gtk.Property.set=gtk_tree_list_model_set_autoexpand) |
690 | * |
691 | * If all rows should be expanded by default. |
692 | */ |
693 | properties[PROP_AUTOEXPAND] = |
694 | g_param_spec_boolean (name: "autoexpand" , |
695 | P_("autoexpand" ), |
696 | P_("If all rows should be expanded by default" ), |
697 | FALSE, |
698 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
699 | |
700 | /** |
701 | * GtkTreeListModel:model: (attributes org.gtk.Property.get=gtk_tree_list_model_get_model) |
702 | * |
703 | * The root model displayed. |
704 | */ |
705 | properties[PROP_MODEL] = |
706 | g_param_spec_object (name: "model" , |
707 | P_("Model" ), |
708 | P_("The root model displayed" ), |
709 | G_TYPE_LIST_MODEL, |
710 | GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); |
711 | |
712 | /** |
713 | * GtkTreeListModel:passthrough: (attributes org.gtk.Property.get=gtk_tree_list_model_get_passthrough) |
714 | * |
715 | * Gets whether the model is in passthrough mode. |
716 | * |
717 | * If %FALSE, the `GListModel` functions for this object return custom |
718 | * [class@Gtk.TreeListRow] objects. If %TRUE, the values of the child |
719 | * models are pass through unmodified. |
720 | */ |
721 | properties[PROP_PASSTHROUGH] = |
722 | g_param_spec_boolean (name: "passthrough" , |
723 | P_("passthrough" ), |
724 | P_("If child model values are passed through" ), |
725 | FALSE, |
726 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); |
727 | |
728 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties); |
729 | } |
730 | |
731 | static void |
732 | gtk_tree_list_model_init (GtkTreeListModel *self) |
733 | { |
734 | self->root_node.list = self; |
735 | self->root_node.is_root = TRUE; |
736 | } |
737 | |
738 | /** |
739 | * gtk_tree_list_model_new: |
740 | * @root: (transfer full): The `GListModel` to use as root |
741 | * @passthrough: %TRUE to pass through items from the models |
742 | * @autoexpand: %TRUE to set the autoexpand property and expand the @root model |
743 | * @create_func: Function to call to create the `GListModel` for the children |
744 | * of an item |
745 | * @user_data: (closure): Data to pass to @create_func |
746 | * @user_destroy: Function to call to free @user_data |
747 | * |
748 | * Creates a new empty `GtkTreeListModel` displaying @root |
749 | * with all rows collapsed. |
750 | * |
751 | * Returns: a newly created `GtkTreeListModel`. |
752 | */ |
753 | GtkTreeListModel * |
754 | gtk_tree_list_model_new (GListModel *root, |
755 | gboolean passthrough, |
756 | gboolean autoexpand, |
757 | GtkTreeListModelCreateModelFunc create_func, |
758 | gpointer user_data, |
759 | GDestroyNotify user_destroy) |
760 | { |
761 | GtkTreeListModel *self; |
762 | |
763 | g_return_val_if_fail (G_IS_LIST_MODEL (root), NULL); |
764 | g_return_val_if_fail (create_func != NULL, NULL); |
765 | |
766 | self = g_object_new (GTK_TYPE_TREE_LIST_MODEL, |
767 | first_property_name: "autoexpand" , autoexpand, |
768 | "passthrough" , passthrough, |
769 | NULL); |
770 | |
771 | self->create_func = create_func; |
772 | self->user_data = user_data; |
773 | self->user_destroy = user_destroy; |
774 | |
775 | gtk_tree_list_model_init_node (list: self, self: &self->root_node, model: root); |
776 | |
777 | return self; |
778 | } |
779 | |
780 | /** |
781 | * gtk_tree_list_model_get_model: (attributes org.gtk.Method.get_property=model) |
782 | * @self: a `GtkTreeListModel` |
783 | * |
784 | * Gets the root model that @self was created with. |
785 | * |
786 | * Returns: (transfer none): the root model |
787 | */ |
788 | GListModel * |
789 | gtk_tree_list_model_get_model (GtkTreeListModel *self) |
790 | { |
791 | g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL); |
792 | |
793 | return self->root_node.model; |
794 | } |
795 | |
796 | /** |
797 | * gtk_tree_list_model_get_passthrough: (attributes org.gtk.Method.get_property=passthrough) |
798 | * @self: a `GtkTreeListModel` |
799 | * |
800 | * Gets whether the model is passing through original row items. |
801 | * |
802 | * If this function returns %FALSE, the `GListModel` functions for @self |
803 | * return custom `GtkTreeListRow` objects. You need to call |
804 | * [method@Gtk.TreeListRow.get_item] on these objects to get the original |
805 | * item. |
806 | * |
807 | * If %TRUE, the values of the child models are passed through in their |
808 | * original state. You then need to call [method@Gtk.TreeListModel.get_row] |
809 | * to get the custom `GtkTreeListRow`s. |
810 | * |
811 | * Returns: %TRUE if the model is passing through original row items |
812 | **/ |
813 | gboolean |
814 | gtk_tree_list_model_get_passthrough (GtkTreeListModel *self) |
815 | { |
816 | g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE); |
817 | |
818 | return self->passthrough; |
819 | } |
820 | |
821 | /** |
822 | * gtk_tree_list_model_set_autoexpand: (attributes org.gtk.Method.set_property=autoexpand) |
823 | * @self: a `GtkTreeListModel` |
824 | * @autoexpand: %TRUE to make the model autoexpand its rows |
825 | * |
826 | * Sets whether the model should autoexpand. |
827 | * |
828 | * If set to %TRUE, the model will recursively expand all rows that |
829 | * get added to the model. This can be either rows added by changes |
830 | * to the underlying models or via [method@Gtk.TreeListRow.set_expanded]. |
831 | */ |
832 | void |
833 | gtk_tree_list_model_set_autoexpand (GtkTreeListModel *self, |
834 | gboolean autoexpand) |
835 | { |
836 | g_return_if_fail (GTK_IS_TREE_LIST_MODEL (self)); |
837 | |
838 | if (self->autoexpand == autoexpand) |
839 | return; |
840 | |
841 | self->autoexpand = autoexpand; |
842 | |
843 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_AUTOEXPAND]); |
844 | } |
845 | |
846 | /** |
847 | * gtk_tree_list_model_get_autoexpand: (attributes org.gtk.Method.get_property=autoexpand) |
848 | * @self: a `GtkTreeListModel` |
849 | * |
850 | * Gets whether the model is set to automatically expand new rows |
851 | * that get added. |
852 | * |
853 | * This can be either rows added by changes to the underlying |
854 | * models or via [method@Gtk.TreeListRow.set_expanded]. |
855 | * |
856 | * Returns: %TRUE if the model is set to autoexpand |
857 | */ |
858 | gboolean |
859 | gtk_tree_list_model_get_autoexpand (GtkTreeListModel *self) |
860 | { |
861 | g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE); |
862 | |
863 | return self->autoexpand; |
864 | } |
865 | |
866 | /** |
867 | * gtk_tree_list_model_get_row: |
868 | * @self: a `GtkTreeListModel` |
869 | * @position: the position of the row to fetch |
870 | * |
871 | * Gets the row object for the given row. |
872 | * |
873 | * If @position is greater than the number of items in @self, |
874 | * %NULL is returned. |
875 | * |
876 | * The row object can be used to expand and collapse rows as |
877 | * well as to inspect its position in the tree. See its |
878 | * documentation for details. |
879 | * |
880 | * This row object is persistent and will refer to the current |
881 | * item as long as the row is present in @self, independent of |
882 | * other rows being added or removed. |
883 | * |
884 | * If @self is set to not be passthrough, this function is |
885 | * equivalent to calling g_list_model_get_item(). |
886 | * |
887 | * Do not confuse this function with [method@Gtk.TreeListModel.get_child_row]. |
888 | * |
889 | * Returns: (nullable) (transfer full): The row item |
890 | */ |
891 | GtkTreeListRow * |
892 | gtk_tree_list_model_get_row (GtkTreeListModel *self, |
893 | guint position) |
894 | { |
895 | TreeNode *node; |
896 | |
897 | g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL); |
898 | |
899 | node = gtk_tree_list_model_get_nth (self, position); |
900 | if (node == NULL) |
901 | return NULL; |
902 | |
903 | return tree_node_get_row (node); |
904 | } |
905 | |
906 | /** |
907 | * gtk_tree_list_model_get_child_row: |
908 | * @self: a `GtkTreeListModel` |
909 | * @position: position of the child to get |
910 | * |
911 | * Gets the row item corresponding to the child at index @position for |
912 | * @self's root model. |
913 | * |
914 | * If @position is greater than the number of children in the root model, |
915 | * %NULL is returned. |
916 | * |
917 | * Do not confuse this function with [method@Gtk.TreeListModel.get_row]. |
918 | * |
919 | * Returns: (nullable) (transfer full): the child in @position |
920 | **/ |
921 | GtkTreeListRow * |
922 | gtk_tree_list_model_get_child_row (GtkTreeListModel *self, |
923 | guint position) |
924 | { |
925 | TreeNode *child; |
926 | |
927 | g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL); |
928 | |
929 | child = tree_node_get_nth_child (node: &self->root_node, position); |
930 | if (child == NULL) |
931 | return NULL; |
932 | |
933 | return tree_node_get_row (node: child); |
934 | } |
935 | |
936 | /** |
937 | * GtkTreeListRow: |
938 | * |
939 | * `GtkTreeListRow` is used by `GtkTreeListModel` to represent items. |
940 | * |
941 | * It allows navigating the model as a tree and modify the state of rows. |
942 | * |
943 | * `GtkTreeListRow` instances are created by a `GtkTreeListModel` only |
944 | * when the [property@Gtk.TreeListModel:passthrough] property is not set. |
945 | * |
946 | * There are various support objects that can make use of `GtkTreeListRow` |
947 | * objects, such as the [class@Gtk.TreeExpander] widget that allows displaying |
948 | * an icon to expand or collapse a row or [class@Gtk.TreeListRowSorter] that |
949 | * makes it possible to sort trees properly. |
950 | */ |
951 | |
952 | enum { |
953 | ROW_PROP_0, |
954 | ROW_PROP_CHILDREN, |
955 | ROW_PROP_DEPTH, |
956 | ROW_PROP_EXPANDABLE, |
957 | ROW_PROP_EXPANDED, |
958 | ROW_PROP_ITEM, |
959 | NUM_ROW_PROPERTIES |
960 | }; |
961 | |
962 | static GParamSpec *row_properties[NUM_ROW_PROPERTIES] = { NULL, }; |
963 | |
964 | G_DEFINE_TYPE (GtkTreeListRow, gtk_tree_list_row, G_TYPE_OBJECT) |
965 | |
966 | static void |
967 | gtk_tree_list_row_destroy (GtkTreeListRow *self) |
968 | { |
969 | g_object_freeze_notify (G_OBJECT (self)); |
970 | |
971 | /* FIXME: We could check some properties to avoid excess notifies */ |
972 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_DEPTH]); |
973 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_EXPANDABLE]); |
974 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_EXPANDED]); |
975 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_ITEM]); |
976 | |
977 | self->node = NULL; |
978 | g_object_thaw_notify (G_OBJECT (self)); |
979 | } |
980 | |
981 | static void |
982 | gtk_tree_list_row_set_property (GObject *object, |
983 | guint prop_id, |
984 | const GValue *value, |
985 | GParamSpec *pspec) |
986 | { |
987 | GtkTreeListRow *self = GTK_TREE_LIST_ROW (ptr: object); |
988 | |
989 | switch (prop_id) |
990 | { |
991 | case ROW_PROP_EXPANDED: |
992 | gtk_tree_list_row_set_expanded (self, expanded: g_value_get_boolean (value)); |
993 | break; |
994 | |
995 | default: |
996 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
997 | break; |
998 | } |
999 | } |
1000 | |
1001 | static void |
1002 | gtk_tree_list_row_get_property (GObject *object, |
1003 | guint prop_id, |
1004 | GValue *value, |
1005 | GParamSpec *pspec) |
1006 | { |
1007 | GtkTreeListRow *self = GTK_TREE_LIST_ROW (ptr: object); |
1008 | |
1009 | switch (prop_id) |
1010 | { |
1011 | case ROW_PROP_CHILDREN: |
1012 | g_value_set_object (value, v_object: gtk_tree_list_row_get_children (self)); |
1013 | break; |
1014 | |
1015 | case ROW_PROP_DEPTH: |
1016 | g_value_set_uint (value, v_uint: gtk_tree_list_row_get_depth (self)); |
1017 | break; |
1018 | |
1019 | case ROW_PROP_EXPANDABLE: |
1020 | g_value_set_boolean (value, v_boolean: gtk_tree_list_row_is_expandable (self)); |
1021 | break; |
1022 | |
1023 | case ROW_PROP_EXPANDED: |
1024 | g_value_set_boolean (value, v_boolean: gtk_tree_list_row_get_expanded (self)); |
1025 | break; |
1026 | |
1027 | case ROW_PROP_ITEM: |
1028 | g_value_take_object (value, v_object: gtk_tree_list_row_get_item (self)); |
1029 | break; |
1030 | |
1031 | default: |
1032 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
1033 | break; |
1034 | } |
1035 | } |
1036 | |
1037 | static void |
1038 | gtk_tree_list_row_dispose (GObject *object) |
1039 | { |
1040 | GtkTreeListRow *self = GTK_TREE_LIST_ROW (ptr: object); |
1041 | |
1042 | if (self->node) |
1043 | self->node->row = NULL; |
1044 | |
1045 | G_OBJECT_CLASS (gtk_tree_list_row_parent_class)->dispose (object); |
1046 | } |
1047 | |
1048 | static void |
1049 | gtk_tree_list_row_class_init (GtkTreeListRowClass *class) |
1050 | { |
1051 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
1052 | |
1053 | gobject_class->set_property = gtk_tree_list_row_set_property; |
1054 | gobject_class->get_property = gtk_tree_list_row_get_property; |
1055 | gobject_class->dispose = gtk_tree_list_row_dispose; |
1056 | |
1057 | /** |
1058 | * GtkTreeListRow:children: (attributes org.gtk.Property.get=gtk_tree_list_row_get_children) |
1059 | * |
1060 | * The model holding the row's children. |
1061 | */ |
1062 | row_properties[ROW_PROP_CHILDREN] = |
1063 | g_param_spec_object (name: "children" , |
1064 | P_("Children" ), |
1065 | P_("Model holding the row’s children" ), |
1066 | G_TYPE_LIST_MODEL, |
1067 | GTK_PARAM_READABLE); |
1068 | |
1069 | /** |
1070 | * GtkTreeListRow:depth: (attributes org.gtk.Property.get=gtk_tree_list_row_get_depth) |
1071 | * |
1072 | * The depth in the tree of this row. |
1073 | */ |
1074 | row_properties[ROW_PROP_DEPTH] = |
1075 | g_param_spec_uint (name: "depth" , |
1076 | P_("Depth" ), |
1077 | P_("Depth in the tree" ), |
1078 | minimum: 0, G_MAXUINT, default_value: 0, |
1079 | GTK_PARAM_READABLE); |
1080 | |
1081 | /** |
1082 | * GtkTreeListRow:expandable: (attributes org.gtk.Property.get=gtk_tree_list_row_is_expandable) |
1083 | * |
1084 | * If this row can ever be expanded. |
1085 | */ |
1086 | row_properties[ROW_PROP_EXPANDABLE] = |
1087 | g_param_spec_boolean (name: "expandable" , |
1088 | P_("Expandable" ), |
1089 | P_("If this row can ever be expanded" ), |
1090 | FALSE, |
1091 | GTK_PARAM_READABLE); |
1092 | |
1093 | /** |
1094 | * GtkTreeListRow:expanded: (attributes org.gtk.Property.get=gtk_tree_list_row_get_expanded org.gtk.Property.set=gtk_tree_list_row_set_expanded) |
1095 | * |
1096 | * If this row is currently expanded. |
1097 | */ |
1098 | row_properties[ROW_PROP_EXPANDED] = |
1099 | g_param_spec_boolean (name: "expanded" , |
1100 | P_("Expanded" ), |
1101 | P_("If this row is currently expanded" ), |
1102 | FALSE, |
1103 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
1104 | |
1105 | /** |
1106 | * GtkTreeListRow:item: (attributes org.gtk.Property.get=gtk_tree_list_row_get_item) |
1107 | * |
1108 | * The item held in this row. |
1109 | */ |
1110 | row_properties[ROW_PROP_ITEM] = |
1111 | g_param_spec_object (name: "item" , |
1112 | P_("Item" ), |
1113 | P_("The item held in this row" ), |
1114 | G_TYPE_OBJECT, |
1115 | GTK_PARAM_READABLE); |
1116 | |
1117 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_ROW_PROPERTIES, pspecs: row_properties); |
1118 | } |
1119 | |
1120 | static void |
1121 | gtk_tree_list_row_init (GtkTreeListRow *self) |
1122 | { |
1123 | } |
1124 | |
1125 | /** |
1126 | * gtk_tree_list_row_get_position: |
1127 | * @self: a `GtkTreeListRow` |
1128 | * |
1129 | * Returns the position in the `GtkTreeListModel` that @self occupies |
1130 | * at the moment. |
1131 | * |
1132 | * Returns: The position in the model |
1133 | */ |
1134 | guint |
1135 | gtk_tree_list_row_get_position (GtkTreeListRow *self) |
1136 | { |
1137 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), 0); |
1138 | |
1139 | if (self->node == NULL) |
1140 | return 0; |
1141 | |
1142 | return tree_node_get_position (node: self->node); |
1143 | } |
1144 | |
1145 | /** |
1146 | * gtk_tree_list_row_get_depth: (attributes org.gtk.Method.get_property=depth) |
1147 | * @self: a `GtkTreeListRow` |
1148 | * |
1149 | * Gets the depth of this row. |
1150 | * |
1151 | * Rows that correspond to items in the root model have a depth |
1152 | * of zero, rows corresponding to items of models of direct children |
1153 | * of the root model have a depth of 1 and so on. |
1154 | * |
1155 | * The depth of a row never changes until the row is destroyed. |
1156 | * |
1157 | * Returns: The depth of this row |
1158 | */ |
1159 | guint |
1160 | gtk_tree_list_row_get_depth (GtkTreeListRow *self) |
1161 | { |
1162 | TreeNode *node; |
1163 | guint depth; |
1164 | |
1165 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), 0); |
1166 | |
1167 | if (self->node == NULL) |
1168 | return 0; |
1169 | |
1170 | depth = 0; |
1171 | for (node = self->node->parent; |
1172 | !node->is_root; |
1173 | node = node->parent) |
1174 | depth++; |
1175 | |
1176 | return depth; |
1177 | } |
1178 | |
1179 | /** |
1180 | * gtk_tree_list_row_set_expanded: (attributes org.gtk.Method.set_property=expanded) |
1181 | * @self: a `GtkTreeListRow` |
1182 | * @expanded: %TRUE if the row should be expanded |
1183 | * |
1184 | * Expands or collapses a row. |
1185 | * |
1186 | * If a row is expanded, the model of calling the |
1187 | * [callback@Gtk.TreeListModelCreateModelFunc] for the row's |
1188 | * item will be inserted after this row. If a row is collapsed, |
1189 | * those items will be removed from the model. |
1190 | * |
1191 | * If the row is not expandable, this function does nothing. |
1192 | */ |
1193 | void |
1194 | gtk_tree_list_row_set_expanded (GtkTreeListRow *self, |
1195 | gboolean expanded) |
1196 | { |
1197 | GtkTreeListModel *list; |
1198 | gboolean was_expanded; |
1199 | guint n_items; |
1200 | |
1201 | g_return_if_fail (GTK_IS_TREE_LIST_ROW (self)); |
1202 | |
1203 | if (self->node == NULL) |
1204 | return; |
1205 | |
1206 | was_expanded = self->node->children != NULL; |
1207 | if (was_expanded == expanded) |
1208 | return; |
1209 | |
1210 | list = tree_node_get_tree_list_model (node: self->node); |
1211 | |
1212 | if (expanded) |
1213 | { |
1214 | n_items = gtk_tree_list_model_expand_node (self: list, node: self->node); |
1215 | if (n_items > 0) |
1216 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: list), position: tree_node_get_position (node: self->node) + 1, removed: 0, added: n_items); |
1217 | } |
1218 | else |
1219 | { |
1220 | n_items = gtk_tree_list_model_collapse_node (self: list, node: self->node); |
1221 | if (n_items > 0) |
1222 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: list), position: tree_node_get_position (node: self->node) + 1, removed: n_items, added: 0); |
1223 | } |
1224 | |
1225 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_EXPANDED]); |
1226 | g_object_notify_by_pspec (G_OBJECT (self), pspec: row_properties[ROW_PROP_CHILDREN]); |
1227 | } |
1228 | |
1229 | /** |
1230 | * gtk_tree_list_row_get_expanded: (attributes org.gtk.Method.get_property=expanded) |
1231 | * @self: a `GtkTreeListRow` |
1232 | * |
1233 | * Gets if a row is currently expanded. |
1234 | * |
1235 | * Returns: %TRUE if the row is expanded |
1236 | */ |
1237 | gboolean |
1238 | gtk_tree_list_row_get_expanded (GtkTreeListRow *self) |
1239 | { |
1240 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), FALSE); |
1241 | |
1242 | if (self->node == NULL) |
1243 | return FALSE; |
1244 | |
1245 | return self->node->children != NULL; |
1246 | } |
1247 | |
1248 | /** |
1249 | * gtk_tree_list_row_is_expandable: (attributes org.gtk.Method.get_property=expandable) |
1250 | * @self: a `GtkTreeListRow` |
1251 | * |
1252 | * Checks if a row can be expanded. |
1253 | * |
1254 | * This does not mean that the row is actually expanded, |
1255 | * this can be checked with [method@Gtk.TreeListRow.get_expanded]. |
1256 | * |
1257 | * If a row is expandable never changes until the row is destroyed. |
1258 | * |
1259 | * Returns: %TRUE if the row is expandable |
1260 | */ |
1261 | gboolean |
1262 | gtk_tree_list_row_is_expandable (GtkTreeListRow *self) |
1263 | { |
1264 | GtkTreeListModel *list; |
1265 | GListModel *model; |
1266 | |
1267 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), FALSE); |
1268 | |
1269 | if (self->node == NULL) |
1270 | return FALSE; |
1271 | |
1272 | if (self->node->empty) |
1273 | return FALSE; |
1274 | |
1275 | if (self->node->model) |
1276 | return TRUE; |
1277 | |
1278 | list = tree_node_get_tree_list_model (node: self->node); |
1279 | model = tree_node_create_model (self: list, node: self->node); |
1280 | if (model) |
1281 | { |
1282 | g_object_unref (object: model); |
1283 | return TRUE; |
1284 | } |
1285 | |
1286 | return FALSE; |
1287 | } |
1288 | |
1289 | /** |
1290 | * gtk_tree_list_row_get_item: (attributes org.gtk.Method.get_property=item) |
1291 | * @self: a `GtkTreeListRow` |
1292 | * |
1293 | * Gets the item corresponding to this row, |
1294 | * |
1295 | * The value returned by this function never changes until the |
1296 | * row is destroyed. |
1297 | * |
1298 | * Returns: (nullable) (type GObject) (transfer full): The item |
1299 | * of this row or %NULL when the row was destroyed |
1300 | */ |
1301 | gpointer |
1302 | gtk_tree_list_row_get_item (GtkTreeListRow *self) |
1303 | { |
1304 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), NULL); |
1305 | |
1306 | if (self->node == NULL) |
1307 | return NULL; |
1308 | |
1309 | return tree_node_get_item (node: self->node); |
1310 | } |
1311 | |
1312 | /** |
1313 | * gtk_tree_list_row_get_children: (attributes org.gtk.Method.get_property=children) |
1314 | * @self: a `GtkTreeListRow` |
1315 | * |
1316 | * If the row is expanded, gets the model holding the children of @self. |
1317 | * |
1318 | * This model is the model created by the |
1319 | * [callback@Gtk.TreeListModelCreateModelFunc] |
1320 | * and contains the original items, no matter what value |
1321 | * [property@Gtk.TreeListModel:passthrough] is set to. |
1322 | * |
1323 | * Returns: (nullable) (transfer none): The model containing the children |
1324 | */ |
1325 | GListModel * |
1326 | gtk_tree_list_row_get_children (GtkTreeListRow *self) |
1327 | { |
1328 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), NULL); |
1329 | |
1330 | if (self->node == NULL) |
1331 | return NULL; |
1332 | |
1333 | return self->node->model; |
1334 | } |
1335 | |
1336 | /** |
1337 | * gtk_tree_list_row_get_parent: |
1338 | * @self: a `GtkTreeListRow` |
1339 | * |
1340 | * Gets the row representing the parent for @self. |
1341 | * |
1342 | * That is the row that would need to be collapsed |
1343 | * to make this row disappear. |
1344 | * |
1345 | * If @self is a row corresponding to the root model, |
1346 | * %NULL is returned. |
1347 | * |
1348 | * The value returned by this function never changes |
1349 | * until the row is destroyed. |
1350 | * |
1351 | * Returns: (nullable) (transfer full): The parent of @self |
1352 | */ |
1353 | GtkTreeListRow * |
1354 | gtk_tree_list_row_get_parent (GtkTreeListRow *self) |
1355 | { |
1356 | TreeNode *parent; |
1357 | |
1358 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), NULL); |
1359 | |
1360 | if (self->node == NULL) |
1361 | return NULL; |
1362 | |
1363 | parent = self->node->parent; |
1364 | if (parent->is_root) |
1365 | return NULL; |
1366 | |
1367 | return tree_node_get_row (node: parent); |
1368 | } |
1369 | |
1370 | /** |
1371 | * gtk_tree_list_row_get_child_row: |
1372 | * @self: a `GtkTreeListRow` |
1373 | * @position: position of the child to get |
1374 | * |
1375 | * If @self is not expanded or @position is greater than the |
1376 | * number of children, %NULL is returned. |
1377 | * |
1378 | * Returns: (nullable) (transfer full): the child in @position |
1379 | */ |
1380 | GtkTreeListRow * |
1381 | gtk_tree_list_row_get_child_row (GtkTreeListRow *self, |
1382 | guint position) |
1383 | { |
1384 | TreeNode *child; |
1385 | |
1386 | g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), NULL); |
1387 | |
1388 | if (self->node == NULL) |
1389 | return NULL; |
1390 | |
1391 | if (self->node->children == NULL) |
1392 | return NULL; |
1393 | |
1394 | child = tree_node_get_nth_child (node: self->node, position); |
1395 | if (child == NULL) |
1396 | return NULL; |
1397 | |
1398 | return tree_node_get_row (node: child); |
1399 | } |
1400 | |