gtktreelistmodel.c 36.1 KB
Newer Older
Benjamin Otte's avatar
Benjamin Otte committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
 * Copyright © 2018 Benjamin Otte
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Benjamin Otte <otte@gnome.org>
 */

#include "config.h"

#include "gtktreelistmodel.h"

24
#include "gtkrbtreeprivate.h"
Benjamin Otte's avatar
Benjamin Otte committed
25
26
#include "gtkprivate.h"

Matthias Clasen's avatar
Matthias Clasen committed
27
/**
Matthias Clasen's avatar
Matthias Clasen committed
28
 * GtkTreeListModel:
Matthias Clasen's avatar
Matthias Clasen committed
29
 *
Matthias Clasen's avatar
Matthias Clasen committed
30
 * `GtkTreeListModel` is a list model that can create child models on demand.
Matthias Clasen's avatar
Matthias Clasen committed
31
32
 */

Benjamin Otte's avatar
Benjamin Otte committed
33
34
35
enum {
  PROP_0,
  PROP_AUTOEXPAND,
36
  PROP_ITEM_TYPE,
37
  PROP_MODEL,
38
  PROP_N_ITEMS,
39
  PROP_PASSTHROUGH,
Benjamin Otte's avatar
Benjamin Otte committed
40
41
42
43
44
45
46
47
48
  NUM_PROPERTIES
};

typedef struct _TreeNode TreeNode;
typedef struct _TreeAugment TreeAugment;

struct _TreeNode
{
  GListModel *model;
49
  GtkTreeListRow *row;
50
  GtkRbTree *children;
Benjamin Otte's avatar
Benjamin Otte committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  union {
    TreeNode *parent;
    GtkTreeListModel *list;
  };

  guint empty : 1;
  guint is_root : 1;
};

struct _TreeAugment
{
  guint n_items;
  guint n_local;
};

struct _GtkTreeListModel
{
  GObject parent_instance;

  TreeNode root_node;

  GtkTreeListModelCreateModelFunc create_func;
  gpointer user_data;
  GDestroyNotify user_destroy;

  guint autoexpand : 1;
77
  guint passthrough : 1;
Benjamin Otte's avatar
Benjamin Otte committed
78
79
80
81
82
83
84
};

struct _GtkTreeListModelClass
{
  GObjectClass parent_class;
};

85
86
87
88
89
90
91
92
93
94
95
96
struct _GtkTreeListRow
{
  GObject parent_instance;

  TreeNode *node; /* NULL when the row has been destroyed */
};

struct _GtkTreeListRowClass
{
  GObjectClass parent_class;
};

Benjamin Otte's avatar
Benjamin Otte committed
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };

static GtkTreeListModel *
tree_node_get_tree_list_model (TreeNode *node)
{
  for (; !node->is_root; node = node->parent)
    { }

  return node->list;
}

static TreeNode *
tree_node_get_nth_child (TreeNode *node,
                         guint     position)
{
112
  GtkRbTree *tree;
Benjamin Otte's avatar
Benjamin Otte committed
113
114
115
116
  TreeNode *child, *tmp;
  TreeAugment *aug;

  tree = node->children;
117
  child = gtk_rb_tree_get_root (tree);
Benjamin Otte's avatar
Benjamin Otte committed
118

119
  while (child)
Benjamin Otte's avatar
Benjamin Otte committed
120
    {
121
      tmp = gtk_rb_tree_node_get_left (child);
Benjamin Otte's avatar
Benjamin Otte committed
122
123
      if (tmp)
        {
124
          aug = gtk_rb_tree_get_augment (tree, tmp);
Benjamin Otte's avatar
Benjamin Otte committed
125
126
127
128
129
130
131
132
133
134
135
136
137
          if (position < aug->n_local)
            {
              child = tmp;
              continue;
            }
          position -= aug->n_local;
        }

      if (position == 0)
        return child;

      position--;

138
      child = gtk_rb_tree_node_get_right (child);
Benjamin Otte's avatar
Benjamin Otte committed
139
140
    }

141
  return NULL;
Benjamin Otte's avatar
Benjamin Otte committed
142
143
144
145
146
147
148
149
150
151
152
}

static guint
tree_node_get_n_children (TreeNode *node)
{
  TreeAugment *child_aug;
  TreeNode *child_node;

  if (node->children == NULL)
    return 0;

153
  child_node = gtk_rb_tree_get_root (node->children);
Benjamin Otte's avatar
Benjamin Otte committed
154
155
156
  if (child_node == NULL)
    return 0;

157
  child_aug = gtk_rb_tree_get_augment (node->children, child_node);
Benjamin Otte's avatar
Benjamin Otte committed
158
159
160
161
162

  return child_aug->n_items;
}

static guint
163
164
tree_node_get_local_position (GtkRbTree *tree,
                              TreeNode  *node)
Benjamin Otte's avatar
Benjamin Otte committed
165
166
167
168
169
{
  TreeNode *left, *parent;
  TreeAugment *left_aug;
  guint n;
  
170
  left = gtk_rb_tree_node_get_left (node);
Benjamin Otte's avatar
Benjamin Otte committed
171
172
  if (left)
    {
173
      left_aug = gtk_rb_tree_get_augment (tree, left);
Benjamin Otte's avatar
Benjamin Otte committed
174
175
176
177
178
179
180
      n = left_aug->n_local;
    }
  else
    {
      n = 0;
    }

181
  for (parent = gtk_rb_tree_node_get_parent (node);
Benjamin Otte's avatar
Benjamin Otte committed
182
       parent;
183
       parent = gtk_rb_tree_node_get_parent (node))
Benjamin Otte's avatar
Benjamin Otte committed
184
    {
185
      left = gtk_rb_tree_node_get_left (parent);
Benjamin Otte's avatar
Benjamin Otte committed
186
187
188
189
190
191
192
193
194
195
      if (left == node)
        {
          /* we are the left node, nothing changes */
        }
      else
        {
          /* we are the right node */
          n++;
          if (left)
            {
196
              left_aug = gtk_rb_tree_get_augment (tree, left);
Benjamin Otte's avatar
Benjamin Otte committed
197
198
199
200
201
202
203
204
205
206
207
208
              n += left_aug->n_local;
            }
        }
      node = parent;
    }

  return n;
}

static guint
tree_node_get_position (TreeNode *node)
{
209
  GtkRbTree *tree;
Benjamin Otte's avatar
Benjamin Otte committed
210
211
212
213
214
215
216
217
218
219
  TreeNode *left, *parent;
  TreeAugment *left_aug;
  guint n;
  
  for (n = 0;
       !node->is_root;
       node = node->parent, n++)
    {
      tree = node->parent->children;

220
      left = gtk_rb_tree_node_get_left (node);
Benjamin Otte's avatar
Benjamin Otte committed
221
222
      if (left)
        {
223
          left_aug = gtk_rb_tree_get_augment (tree, left);
Benjamin Otte's avatar
Benjamin Otte committed
224
225
226
          n += left_aug->n_items;
        }

227
      for (parent = gtk_rb_tree_node_get_parent (node);
Benjamin Otte's avatar
Benjamin Otte committed
228
           parent;
229
           parent = gtk_rb_tree_node_get_parent (node))
Benjamin Otte's avatar
Benjamin Otte committed
230
        {
231
          left = gtk_rb_tree_node_get_left (parent);
Benjamin Otte's avatar
Benjamin Otte committed
232
233
234
235
236
237
238
239
240
241
          if (left == node)
            {
              /* we are the left node, nothing changes */
            }
          else
            {
              /* we are the right node */
              n += 1 + tree_node_get_n_children (parent);
              if (left)
                {
242
                  left_aug = gtk_rb_tree_get_augment (tree, left);
Benjamin Otte's avatar
Benjamin Otte committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
                  n += left_aug->n_items;
                }
            }
          node = parent;
        }
    }
  /* the root isn't visible */
  n--;

  return n;
}

static void
tree_node_mark_dirty (TreeNode *node)
{
  for (;
       !node->is_root;
       node = node->parent)
    {
262
      gtk_rb_tree_node_mark_dirty (node);
Benjamin Otte's avatar
Benjamin Otte committed
263
264
265
266
267
268
269
    }
}

static TreeNode *
gtk_tree_list_model_get_nth (GtkTreeListModel *self,
                             guint             position)
{
270
  GtkRbTree *tree;
Benjamin Otte's avatar
Benjamin Otte committed
271
272
273
274
275
276
277
278
  TreeNode *node, *tmp;
  guint n_children;

  n_children = tree_node_get_n_children (&self->root_node);
  if (n_children <= position)
    return NULL;

  tree = self->root_node.children;
279
  node = gtk_rb_tree_get_root (tree);
Benjamin Otte's avatar
Benjamin Otte committed
280
281
282

  while (TRUE)
    {
283
      tmp = gtk_rb_tree_node_get_left (node);
Benjamin Otte's avatar
Benjamin Otte committed
284
285
      if (tmp)
        {
286
          TreeAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
Benjamin Otte's avatar
Benjamin Otte committed
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
          if (position < aug->n_items)
            {
              node = tmp;
              continue;
            }
          position -= aug->n_items;
        }

      if (position == 0)
        return node;

      position--;

      n_children = tree_node_get_n_children (node);
      if (position < n_children)
        {
          tree = node->children;
304
          node = gtk_rb_tree_get_root (tree);
Benjamin Otte's avatar
Benjamin Otte committed
305
306
307
308
          continue;
        }
      position -= n_children;

309
      node = gtk_rb_tree_node_get_right (node);
Benjamin Otte's avatar
Benjamin Otte committed
310
311
312
313
314
    }

  g_return_val_if_reached (NULL);
}

315
316
317
static GListModel *
tree_node_create_model (GtkTreeListModel *self,
                        TreeNode         *node)
Benjamin Otte's avatar
Benjamin Otte committed
318
{
319
320
321
  TreeNode *parent = node->parent;
  GListModel *model;
  GObject *item;
Benjamin Otte's avatar
Benjamin Otte committed
322

323
324
325
326
327
328
  item = g_list_model_get_item (parent->model,
                                tree_node_get_local_position (parent->children, node));
  model = self->create_func (item, self->user_data);
  g_object_unref (item);
  if (model == NULL)
    node->empty = TRUE;
Benjamin Otte's avatar
Benjamin Otte committed
329

330
  return model;
Benjamin Otte's avatar
Benjamin Otte committed
331
332
333
}

static gpointer
334
tree_node_get_item (TreeNode *node)
Benjamin Otte's avatar
Benjamin Otte committed
335
{
336
  TreeNode *parent;
Benjamin Otte's avatar
Benjamin Otte committed
337
338
339
340
341
342

  parent = node->parent;
  return g_list_model_get_item (parent->model,
                                tree_node_get_local_position (parent->children, node));
}

343
344
static GtkTreeListRow *
tree_node_get_row (TreeNode *node)
Benjamin Otte's avatar
Benjamin Otte committed
345
{
346
  if (node->row)
Benjamin Otte's avatar
Benjamin Otte committed
347
    {
348
      return g_object_ref (node->row);
Benjamin Otte's avatar
Benjamin Otte committed
349
    }
350
  else
Benjamin Otte's avatar
Benjamin Otte committed
351
    {
352
353
354
355
      node->row = g_object_new (GTK_TYPE_TREE_LIST_ROW, NULL);
      node->row->node = node;

      return node->row;
Benjamin Otte's avatar
Benjamin Otte committed
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    }
}

static guint
gtk_tree_list_model_expand_node (GtkTreeListModel *self,
                                 TreeNode         *node);

static void
gtk_tree_list_model_items_changed_cb (GListModel *model,
                                      guint       position,
                                      guint       removed,
                                      guint       added,
                                      TreeNode   *node)
{
  GtkTreeListModel *self;
  TreeNode *child;
  guint i, tree_position, tree_removed, tree_added, n_local;

  self = tree_node_get_tree_list_model (node);
  n_local = g_list_model_get_n_items (model) - added + removed;

  if (position < n_local)
    {
      child = tree_node_get_nth_child (node, position);
      tree_position = tree_node_get_position (child);
    }
  else
    {
      child = NULL;
      tree_position = tree_node_get_position (node) + tree_node_get_n_children (node) + 1;
    }

  if (removed)
    {
      TreeNode *tmp;

      g_assert (child != NULL);
      if (position + removed < n_local)
        {
          TreeNode *end = tree_node_get_nth_child (node, position + removed);
          tree_removed = tree_node_get_position (end) - tree_position;
        }
      else
        {
          tree_removed = tree_node_get_position (node) + tree_node_get_n_children (node) + 1 - tree_position;
        }

      for (i = 0; i < removed; i++)
        {
          tmp = child;
406
          child = gtk_rb_tree_node_get_next (child);
407
          gtk_rb_tree_remove (node->children, tmp);
Benjamin Otte's avatar
Benjamin Otte committed
408
409
410
411
412
413
414
415
416
417
        }
    }
  else
    {
      tree_removed = 0;
    }

  tree_added = added;
  for (i = 0; i < added; i++)
    {
418
      child = gtk_rb_tree_insert_before (node->children, child);
Benjamin Otte's avatar
Benjamin Otte committed
419
420
421
422
423
424
425
      child->parent = node;
    }
  if (self->autoexpand)
    {
      for (i = 0; i < added; i++)
        {
          tree_added += gtk_tree_list_model_expand_node (self, child);
426
          child = gtk_rb_tree_node_get_next (child);
Benjamin Otte's avatar
Benjamin Otte committed
427
428
429
430
431
432
433
434
435
        }
    }

  tree_node_mark_dirty (node);

  g_list_model_items_changed (G_LIST_MODEL (self),
                              tree_position,
                              tree_removed,
                              tree_added);
436
437
  if (tree_removed != tree_added)
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
Benjamin Otte's avatar
Benjamin Otte committed
438
439
}

440
441
static void gtk_tree_list_row_destroy (GtkTreeListRow *row);

Benjamin Otte's avatar
Benjamin Otte committed
442
static void
443
gtk_tree_list_model_clear_node_children (TreeNode *node)
Benjamin Otte's avatar
Benjamin Otte committed
444
445
446
447
448
449
{
  if (node->model)
    {
      g_signal_handlers_disconnect_by_func (node->model,
                                            gtk_tree_list_model_items_changed_cb,
                                            node);
450
      g_clear_object (&node->model);
Benjamin Otte's avatar
Benjamin Otte committed
451
    }
452
453
454
455
456
457
458
459
460
461
462
463
464

  g_clear_pointer (&node->children, gtk_rb_tree_unref);
}

static void
gtk_tree_list_model_clear_node (gpointer data)
{
  TreeNode *node = data;

  if (node->row)
    gtk_tree_list_row_destroy (node->row);

  gtk_tree_list_model_clear_node_children (node);
Benjamin Otte's avatar
Benjamin Otte committed
465
466
}

467
static void
468
469
470
471
472
gtk_tree_list_model_augment (GtkRbTree *tree,
                             gpointer   _aug,
                             gpointer   _node,
                             gpointer   left,
                             gpointer   right)
473
474
475
476
477
478
479
480
481
{
  TreeAugment *aug = _aug;

  aug->n_items = 1;
  aug->n_items += tree_node_get_n_children (_node);
  aug->n_local = 1;

  if (left)
    {
482
      TreeAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
483
484
485
486
487
      aug->n_items += left_aug->n_items;
      aug->n_local += left_aug->n_local;
    }
  if (right)
    {
488
      TreeAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
489
490
491
492
493
      aug->n_items += right_aug->n_items;
      aug->n_local += right_aug->n_local;
    }
}

Benjamin Otte's avatar
Benjamin Otte committed
494
495
496
497
498
499
500
501
static void
gtk_tree_list_model_init_node (GtkTreeListModel *list,
                               TreeNode         *self,
                               GListModel       *model)
{
  gsize i, n;
  TreeNode *node;

502
  self->model = model;
Benjamin Otte's avatar
Benjamin Otte committed
503
504
505
506
  g_signal_connect (model,
                    "items-changed",
                    G_CALLBACK (gtk_tree_list_model_items_changed_cb),
                    self);
507
508
509
510
511
  self->children = gtk_rb_tree_new (TreeNode,
                                    TreeAugment,
                                    gtk_tree_list_model_augment,
                                    gtk_tree_list_model_clear_node,
                                    NULL);
Benjamin Otte's avatar
Benjamin Otte committed
512
513
514
515
516

  n = g_list_model_get_n_items (model);
  node = NULL;
  for (i = 0; i < n; i++)
    {
517
      node = gtk_rb_tree_insert_after (self->children, node);
Benjamin Otte's avatar
Benjamin Otte committed
518
519
520
521
522
523
      node->parent = self;
      if (list->autoexpand)
        gtk_tree_list_model_expand_node (list, node);
    }
}

524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
static guint
gtk_tree_list_model_expand_node (GtkTreeListModel *self,
                                 TreeNode         *node)
{
  GListModel *model;

  if (node->empty)
    return 0;
  
  if (node->model != NULL)
    return 0;

  model = tree_node_create_model (self, node);

  if (model == NULL)
    return 0;
  
  gtk_tree_list_model_init_node (self, node, model);

  tree_node_mark_dirty (node);
  
  return tree_node_get_n_children (node);
}

static guint
gtk_tree_list_model_collapse_node (GtkTreeListModel *self,
                                   TreeNode         *node)
{      
  guint n_items;

  if (node->model == NULL)
    return 0;

  n_items = tree_node_get_n_children (node);

559
  gtk_tree_list_model_clear_node_children (node);
560
561
562
563
564
565
566
567
568
569
570
571
572

  tree_node_mark_dirty (node);

  return n_items;
}


static GType
gtk_tree_list_model_get_item_type (GListModel *list)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);

  if (self->passthrough)
573
    return G_TYPE_OBJECT;
574
575
  else
    return GTK_TYPE_TREE_LIST_ROW;
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
}

static guint
gtk_tree_list_model_get_n_items (GListModel *list)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);

  return tree_node_get_n_children (&self->root_node);
}

static gpointer
gtk_tree_list_model_get_item (GListModel *list,
                              guint       position)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
  TreeNode *node;

  node = gtk_tree_list_model_get_nth (self, position);
  if (node == NULL)
    return NULL;

  if (self->passthrough)
    {
      return tree_node_get_item (node);
    }
  else
    {
      return tree_node_get_row (node);
    }
}

static void
gtk_tree_list_model_model_init (GListModelInterface *iface)
{
  iface->get_item_type = gtk_tree_list_model_get_item_type;
  iface->get_n_items = gtk_tree_list_model_get_n_items;
  iface->get_item = gtk_tree_list_model_get_item;
}

G_DEFINE_TYPE_WITH_CODE (GtkTreeListModel, gtk_tree_list_model, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tree_list_model_model_init))

Benjamin Otte's avatar
Benjamin Otte committed
618
619
620
621
622
623
624
625
626
627
628
629
630
631
static void
gtk_tree_list_model_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (object);

  switch (prop_id)
    {
    case PROP_AUTOEXPAND:
      gtk_tree_list_model_set_autoexpand (self, g_value_get_boolean (value));
      break;

632
633
634
635
    case PROP_PASSTHROUGH:
      self->passthrough = g_value_get_boolean (value);
      break;

Benjamin Otte's avatar
Benjamin Otte committed
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void 
gtk_tree_list_model_get_property (GObject     *object,
                                  guint        prop_id,
                                  GValue      *value,
                                  GParamSpec  *pspec)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (object);

  switch (prop_id)
    {
    case PROP_AUTOEXPAND:
      g_value_set_boolean (value, self->autoexpand);
      break;

656
657
658
659
    case PROP_ITEM_TYPE:
      g_value_set_gtype (value, gtk_tree_list_model_get_item_type (G_LIST_MODEL (self)));
      break;

660
661
    case PROP_MODEL:
      g_value_set_object (value, self->root_node.model);
662
663
      break;

664
665
666
667
    case PROP_N_ITEMS:
      g_value_set_uint (value, tree_node_get_n_children (&self->root_node));
      break;

668
669
    case PROP_PASSTHROUGH:
      g_value_set_boolean (value, self->passthrough);
Benjamin Otte's avatar
Benjamin Otte committed
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_tree_list_model_finalize (GObject *object)
{
  GtkTreeListModel *self = GTK_TREE_LIST_MODEL (object);

  gtk_tree_list_model_clear_node (&self->root_node);
  if (self->user_destroy)
    self->user_destroy (self->user_data);

  G_OBJECT_CLASS (gtk_tree_list_model_parent_class)->finalize (object);
688
}
Benjamin Otte's avatar
Benjamin Otte committed
689
690
691
692
693
694
695
696
697
698
699

static void
gtk_tree_list_model_class_init (GtkTreeListModelClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  gobject_class->set_property = gtk_tree_list_model_set_property;
  gobject_class->get_property = gtk_tree_list_model_get_property;
  gobject_class->finalize = gtk_tree_list_model_finalize;

  /**
Matthias Clasen's avatar
Matthias Clasen committed
700
   * GtkTreeListModel:autoexpand: (attributes org.gtk.Property.get=gtk_tree_list_model_get_autoexpand org.gtk.Property.set=gtk_tree_list_model_set_autoexpand)
Benjamin Otte's avatar
Benjamin Otte committed
701
   *
Matthias Clasen's avatar
Matthias Clasen committed
702
   * If all rows should be expanded by default.
Benjamin Otte's avatar
Benjamin Otte committed
703
704
   */
  properties[PROP_AUTOEXPAND] =
705
      g_param_spec_boolean ("autoexpand", NULL, NULL,
Benjamin Otte's avatar
Benjamin Otte committed
706
707
708
                            FALSE,
                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

709
710
711
712
713
714
715
716
717
718
719
720
  /**
   * GtkTreeListModel:item-type:
   *
   * The type of items. See [method@Gio.ListModel.get_item_type].
   *
   * Since: 4.8
   **/
  properties[PROP_ITEM_TYPE] =
    g_param_spec_gtype ("item-type", NULL, NULL,
                        G_TYPE_OBJECT,
                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

721
  /**
Matthias Clasen's avatar
Matthias Clasen committed
722
   * GtkTreeListModel:model: (attributes org.gtk.Property.get=gtk_tree_list_model_get_model)
723
   *
Matthias Clasen's avatar
Matthias Clasen committed
724
   * The root model displayed.
725
726
   */
  properties[PROP_MODEL] =
727
      g_param_spec_object ("model", NULL, NULL,
728
729
730
                           G_TYPE_LIST_MODEL,
                           GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);

731
732
733
734
735
736
737
738
739
740
741
742
  /**
   * GtkTreeListModel:n-items:
   *
   * The number of items. See [method@Gio.ListModel.get_n_items].
   *
   * Since: 4.8
   **/
  properties[PROP_N_ITEMS] =
    g_param_spec_uint ("n-items", NULL, NULL,
                       0, G_MAXUINT, 0,
                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

743
  /**
Matthias Clasen's avatar
Matthias Clasen committed
744
   * GtkTreeListModel:passthrough: (attributes org.gtk.Property.get=gtk_tree_list_model_get_passthrough)
745
   *
Matthias Clasen's avatar
Matthias Clasen committed
746
747
748
749
750
   * Gets whether the model is in passthrough mode.
   *
   * If %FALSE, the `GListModel` functions for this object return custom
   * [class@Gtk.TreeListRow] objects. If %TRUE, the values of the child
   * models are pass through unmodified.
751
752
   */
  properties[PROP_PASSTHROUGH] =
753
      g_param_spec_boolean ("passthrough", NULL, NULL,
754
755
756
                            FALSE,
                            GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);

Benjamin Otte's avatar
Benjamin Otte committed
757
758
759
760
761
762
763
764
765
766
767
768
  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}

static void
gtk_tree_list_model_init (GtkTreeListModel *self)
{
  self->root_node.list = self;
  self->root_node.is_root = TRUE;
}

/**
 * gtk_tree_list_model_new:
Matthias Clasen's avatar
Matthias Clasen committed
769
 * @root: (transfer full): The `GListModel` to use as root
770
 * @passthrough: %TRUE to pass through items from the models
Benjamin Otte's avatar
Benjamin Otte committed
771
 * @autoexpand: %TRUE to set the autoexpand property and expand the @root model
Matthias Clasen's avatar
Matthias Clasen committed
772
 * @create_func: Function to call to create the `GListModel` for the children
Matthias Clasen's avatar
Matthias Clasen committed
773
 *   of an item
774
 * @user_data: (closure): Data to pass to @create_func
Benjamin Otte's avatar
Benjamin Otte committed
775
776
 * @user_destroy: Function to call to free @user_data
 *
Matthias Clasen's avatar
Matthias Clasen committed
777
778
 * Creates a new empty `GtkTreeListModel` displaying @root
 * with all rows collapsed.
779
 *
Matthias Clasen's avatar
Matthias Clasen committed
780
781
 * Returns: a newly created `GtkTreeListModel`.
 */
Benjamin Otte's avatar
Benjamin Otte committed
782
GtkTreeListModel *
783
784
gtk_tree_list_model_new (GListModel                      *root,
                         gboolean                         passthrough,
Benjamin Otte's avatar
Benjamin Otte committed
785
786
787
788
789
790
791
792
793
794
795
796
                         gboolean                         autoexpand,
                         GtkTreeListModelCreateModelFunc  create_func,
                         gpointer                         user_data,
                         GDestroyNotify                   user_destroy)
{
  GtkTreeListModel *self;

  g_return_val_if_fail (G_IS_LIST_MODEL (root), NULL);
  g_return_val_if_fail (create_func != NULL, NULL);

  self = g_object_new (GTK_TYPE_TREE_LIST_MODEL,
                       "autoexpand", autoexpand,
797
                       "passthrough", passthrough,
Benjamin Otte's avatar
Benjamin Otte committed
798
799
800
801
802
803
                       NULL);

  self->create_func = create_func;
  self->user_data = user_data;
  self->user_destroy = user_destroy;

804
  gtk_tree_list_model_init_node (self, &self->root_node, root);
Benjamin Otte's avatar
Benjamin Otte committed
805
806
807
808

  return self;
}

809
/**
Matthias Clasen's avatar
Matthias Clasen committed
810
811
 * gtk_tree_list_model_get_model: (attributes org.gtk.Method.get_property=model)
 * @self: a `GtkTreeListModel`
812
813
814
815
 *
 * Gets the root model that @self was created with.
 *
 * Returns: (transfer none): the root model
Matthias Clasen's avatar
Matthias Clasen committed
816
 */
817
818
819
820
821
822
823
824
GListModel *
gtk_tree_list_model_get_model (GtkTreeListModel *self)
{
  g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL);

  return self->root_node.model;
}

825
/**
Matthias Clasen's avatar
Matthias Clasen committed
826
827
828
829
 * gtk_tree_list_model_get_passthrough: (attributes org.gtk.Method.get_property=passthrough)
 * @self: a `GtkTreeListModel`
 *
 * Gets whether the model is passing through original row items.
830
 *
Matthias Clasen's avatar
Matthias Clasen committed
831
832
833
 * If this function returns %FALSE, the `GListModel` functions for @self
 * return custom `GtkTreeListRow` objects. You need to call
 * [method@Gtk.TreeListRow.get_item] on these objects to get the original
834
835
836
 * item.
 *
 * If %TRUE, the values of the child models are passed through in their
Matthias Clasen's avatar
Matthias Clasen committed
837
838
 * original state. You then need to call [method@Gtk.TreeListModel.get_row]
 * to get the custom `GtkTreeListRow`s.
839
840
841
842
843
844
845
846
847
848
849
 *
 * Returns: %TRUE if the model is passing through original row items
 **/
gboolean
gtk_tree_list_model_get_passthrough (GtkTreeListModel *self)
{
  g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);

  return self->passthrough;
}

Benjamin Otte's avatar
Benjamin Otte committed
850
/**
Matthias Clasen's avatar
Matthias Clasen committed
851
852
 * gtk_tree_list_model_set_autoexpand: (attributes org.gtk.Method.set_property=autoexpand)
 * @self: a `GtkTreeListModel`
Benjamin Otte's avatar
Benjamin Otte committed
853
854
 * @autoexpand: %TRUE to make the model autoexpand its rows
 *
Matthias Clasen's avatar
Matthias Clasen committed
855
856
 * Sets whether the model should autoexpand.
 *
Benjamin Otte's avatar
Benjamin Otte committed
857
858
 * If set to %TRUE, the model will recursively expand all rows that
 * get added to the model. This can be either rows added by changes
Matthias Clasen's avatar
Matthias Clasen committed
859
860
 * to the underlying models or via [method@Gtk.TreeListRow.set_expanded].
 */
Benjamin Otte's avatar
Benjamin Otte committed
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
void
gtk_tree_list_model_set_autoexpand (GtkTreeListModel *self,
                                    gboolean          autoexpand)
{
  g_return_if_fail (GTK_IS_TREE_LIST_MODEL (self));

  if (self->autoexpand == autoexpand)
    return;

  self->autoexpand = autoexpand;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AUTOEXPAND]);
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
876
877
 * gtk_tree_list_model_get_autoexpand: (attributes org.gtk.Method.get_property=autoexpand)
 * @self: a `GtkTreeListModel`
Benjamin Otte's avatar
Benjamin Otte committed
878
879
 *
 * Gets whether the model is set to automatically expand new rows
Matthias Clasen's avatar
Matthias Clasen committed
880
881
882
883
 * that get added.
 *
 * This can be either rows added by changes to the underlying
 * models or via [method@Gtk.TreeListRow.set_expanded].
Benjamin Otte's avatar
Benjamin Otte committed
884
885
 *
 * Returns: %TRUE if the model is set to autoexpand
Matthias Clasen's avatar
Matthias Clasen committed
886
 */
Benjamin Otte's avatar
Benjamin Otte committed
887
888
889
890
891
892
893
894
gboolean
gtk_tree_list_model_get_autoexpand (GtkTreeListModel *self)
{
  g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);

  return self->autoexpand;
}

895
896
/**
 * gtk_tree_list_model_get_row:
Matthias Clasen's avatar
Matthias Clasen committed
897
 * @self: a `GtkTreeListModel`
898
899
 * @position: the position of the row to fetch
 *
Matthias Clasen's avatar
Matthias Clasen committed
900
 * Gets the row object for the given row.
901
 *
Matthias Clasen's avatar
Matthias Clasen committed
902
903
 * If @position is greater than the number of items in @self,
 * %NULL is returned.
904
 *
Matthias Clasen's avatar
Matthias Clasen committed
905
906
907
 * The row object can be used to expand and collapse rows as
 * well as to inspect its position in the tree. See its
 * documentation for details.
908
 *
Matthias Clasen's avatar
Matthias Clasen committed
909
910
911
 * This row object is persistent and will refer to the current
 * item as long as the row is present in @self, independent of
 * other rows being added or removed.
912
 *
Matthias Clasen's avatar
Matthias Clasen committed
913
914
 * If @self is set to not be passthrough, this function is
 * equivalent to calling g_list_model_get_item().
915
 *
Matthias Clasen's avatar
Matthias Clasen committed
916
917
918
919
 * Do not confuse this function with [method@Gtk.TreeListModel.get_child_row].
 *
 * Returns: (nullable) (transfer full): The row item
 */
920
921
922
GtkTreeListRow *
gtk_tree_list_model_get_row (GtkTreeListModel *self,
                             guint             position)
Benjamin Otte's avatar
Benjamin Otte committed
923
924
925
{
  TreeNode *node;

926
  g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL);
Benjamin Otte's avatar
Benjamin Otte committed
927
928
929

  node = gtk_tree_list_model_get_nth (self, position);
  if (node == NULL)
930
    return NULL;
Benjamin Otte's avatar
Benjamin Otte committed
931

932
933
  return tree_node_get_row (node);
}
Benjamin Otte's avatar
Benjamin Otte committed
934

935
/**
936
 * gtk_tree_list_model_get_child_row:
Matthias Clasen's avatar
Matthias Clasen committed
937
 * @self: a `GtkTreeListModel`
938
939
940
941
942
943
944
945
 * @position: position of the child to get
 *
 * Gets the row item corresponding to the child at index @position for
 * @self's root model.
 *
 * If @position is greater than the number of children in the root model,
 * %NULL is returned.
 *
Matthias Clasen's avatar
Matthias Clasen committed
946
 * Do not confuse this function with [method@Gtk.TreeListModel.get_row].
947
948
949
950
 *
 * Returns: (nullable) (transfer full): the child in @position
 **/
GtkTreeListRow *
951
952
gtk_tree_list_model_get_child_row (GtkTreeListModel *self,
                                   guint             position)
953
954
955
956
957
958
959
960
961
962
963
964
{
  TreeNode *child;

  g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL);

  child = tree_node_get_nth_child (&self->root_node, position);
  if (child == NULL)
    return NULL;

  return tree_node_get_row (child);
}

Benjamin Otte's avatar
Benjamin Otte committed
965
/**
Matthias Clasen's avatar
Matthias Clasen committed
966
967
968
 * GtkTreeListRow:
 *
 * `GtkTreeListRow` is used by `GtkTreeListModel` to represent items.
Benjamin Otte's avatar
Benjamin Otte committed
969
 *
Matthias Clasen's avatar
Matthias Clasen committed
970
 * It allows navigating the model as a tree and modify the state of rows.
Benjamin Otte's avatar
Benjamin Otte committed
971
 *
Matthias Clasen's avatar
Matthias Clasen committed
972
973
 * `GtkTreeListRow` instances are created by a `GtkTreeListModel` only
 * when the [property@Gtk.TreeListModel:passthrough] property is not set.
Benjamin Otte's avatar
Benjamin Otte committed
974
 *
Matthias Clasen's avatar
Matthias Clasen committed
975
976
977
978
 * There are various support objects that can make use of `GtkTreeListRow`
 * objects, such as the [class@Gtk.TreeExpander] widget that allows displaying
 * an icon to expand or collapse a row or [class@Gtk.TreeListRowSorter] that
 * makes it possible to sort trees properly.
Benjamin Otte's avatar
Benjamin Otte committed
979
 */
980
981
982

enum {
  ROW_PROP_0,
983
  ROW_PROP_CHILDREN,
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
  ROW_PROP_DEPTH,
  ROW_PROP_EXPANDABLE,
  ROW_PROP_EXPANDED,
  ROW_PROP_ITEM,
  NUM_ROW_PROPERTIES
};

static GParamSpec *row_properties[NUM_ROW_PROPERTIES] = { NULL, };

G_DEFINE_TYPE (GtkTreeListRow, gtk_tree_list_row, G_TYPE_OBJECT)

static void
gtk_tree_list_row_destroy (GtkTreeListRow *self)
{
  g_object_freeze_notify (G_OBJECT (self));

  /* FIXME: We could check some properties to avoid excess notifies */
For faster browsing, not all history is shown. View entire blame