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
34enum {
35 PROP_0,
36 PROP_AUTOEXPAND,
37 PROP_MODEL,
38 PROP_PASSTHROUGH,
39 NUM_PROPERTIES
40};
41
42typedef struct _TreeNode TreeNode;
43typedef struct _TreeAugment TreeAugment;
44
45struct _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
59struct _TreeAugment
60{
61 guint n_items;
62 guint n_local;
63};
64
65struct _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
79struct _GtkTreeListModelClass
80{
81 GObjectClass parent_class;
82};
83
84struct _GtkTreeListRow
85{
86 GObject parent_instance;
87
88 TreeNode *node; /* NULL when the row has been destroyed */
89};
90
91struct _GtkTreeListRowClass
92{
93 GObjectClass parent_class;
94};
95
96static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
97
98static GtkTreeListModel *
99tree_node_get_tree_list_model (TreeNode *node)
100{
101 for (; !node->is_root; node = node->parent)
102 { }
103
104 return node->list;
105}
106
107static TreeNode *
108tree_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
143static guint
144tree_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
161static guint
162tree_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
205static guint
206tree_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
254static void
255tree_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
265static TreeNode *
266gtk_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
314static GListModel *
315tree_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
332static gpointer
333tree_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
342static GtkTreeListRow *
343tree_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
358static guint
359gtk_tree_list_model_expand_node (GtkTreeListModel *self,
360 TreeNode *node);
361
362static void
363gtk_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
437static void gtk_tree_list_row_destroy (GtkTreeListRow *row);
438
439static void
440gtk_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
453static void
454gtk_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
464static void
465gtk_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
491static void
492gtk_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
521static guint
522gtk_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
545static guint
546gtk_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
564static GType
565gtk_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
575static guint
576gtk_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
583static gpointer
584gtk_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
604static void
605gtk_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
612G_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
615static void
616gtk_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
639static void
640gtk_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
667static void
668gtk_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
679static void
680gtk_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
731static void
732gtk_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 */
753GtkTreeListModel *
754gtk_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 */
788GListModel *
789gtk_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 **/
813gboolean
814gtk_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 */
832void
833gtk_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 */
858gboolean
859gtk_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 */
891GtkTreeListRow *
892gtk_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 **/
921GtkTreeListRow *
922gtk_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
952enum {
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
962static GParamSpec *row_properties[NUM_ROW_PROPERTIES] = { NULL, };
963
964G_DEFINE_TYPE (GtkTreeListRow, gtk_tree_list_row, G_TYPE_OBJECT)
965
966static void
967gtk_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
981static void
982gtk_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
1001static void
1002gtk_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
1037static void
1038gtk_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
1048static void
1049gtk_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
1120static void
1121gtk_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 */
1134guint
1135gtk_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 */
1159guint
1160gtk_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 */
1193void
1194gtk_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 */
1237gboolean
1238gtk_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 */
1261gboolean
1262gtk_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 */
1301gpointer
1302gtk_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 */
1325GListModel *
1326gtk_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 */
1353GtkTreeListRow *
1354gtk_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 */
1380GtkTreeListRow *
1381gtk_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

source code of gtk/gtk/gtktreelistmodel.c