1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Pan - A Newsreader for Gtk+
4 * Copyright (C) 2002-2006 Charles Kerr <charles@rebelbase.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include <config.h>
21 #include <iostream>
22 #include <algorithm>
23 #include <cstdarg>
24 #include <set>
25 #include <gobject/gvaluecollector.h>
26 #include <pan/general/debug.h>
27 #include <pan/general/macros.h>
28 #include "pan-tree.h"
29
30 #define IS_SORTED(tree) \
31 (tree->sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)
32
33 /***
34 **** Row Helper Functions
35 ***/
36
37 void
set_value_int(GValue * setme,int value)38 PanTreeStore :: Row :: set_value_int (GValue * setme, int value)
39 {
40 g_value_init (setme, G_TYPE_INT);
41 g_value_set_int (setme, value);
42 }
43
44 void
set_value_pointer(GValue * setme,gpointer value)45 PanTreeStore :: Row :: set_value_pointer (GValue * setme, gpointer value)
46 {
47 g_value_init (setme, G_TYPE_POINTER);
48 g_value_set_pointer (setme, value);
49 }
50
51 void
set_value_ulong(GValue * setme,unsigned long value)52 PanTreeStore :: Row :: set_value_ulong (GValue * setme, unsigned long value)
53 {
54 g_value_init (setme, G_TYPE_ULONG);
55 g_value_set_ulong (setme, value);
56 }
57
58 void
set_value_string(GValue * setme,const char * value)59 PanTreeStore :: Row :: set_value_string (GValue * setme, const char * value)
60 {
61 g_value_init (setme, G_TYPE_STRING);
62 g_value_set_string (setme, value);
63 }
64
65 void
set_value_static_string(GValue * setme,const char * value)66 PanTreeStore :: Row :: set_value_static_string (GValue * setme, const char * value)
67 {
68 g_value_init (setme, G_TYPE_STRING);
69 g_value_set_static_string (setme, value);
70 }
71
72
73 /***
74 **** Low-level utilities
75 ***/
76
77 GtkTreeIter
get_iter(const Row * row)78 PanTreeStore :: get_iter (const Row * row)
79 {
80 g_assert (row);
81 GtkTreeIter setme;
82 set_iter (&setme, row);
83 return setme;
84 }
85
86 void
get_iter(const Row * row,GtkTreeIter * setme)87 PanTreeStore :: get_iter (const Row * row, GtkTreeIter * setme)
88 {
89 g_assert (setme);
90 g_assert (row);
91 set_iter (setme, row);
92 }
93
94
95 PanTreeStore :: Row*
get_row(GtkTreeIter * iter)96 PanTreeStore :: get_row (GtkTreeIter * iter)
97 {
98 g_assert (iter);
99 g_assert (iter->stamp == stamp);
100 Row * row (static_cast<Row*>(iter->user_data));
101 g_assert (row);
102 return row;
103 }
104
105 const PanTreeStore :: Row*
get_row(const GtkTreeIter * iter) const106 PanTreeStore :: get_row (const GtkTreeIter * iter) const
107 {
108 g_assert (iter);
109 g_assert (iter->stamp == stamp);
110 const Row * row (static_cast<const Row*>(iter->user_data));
111 g_assert (row);
112 return row;
113 }
114
115 void
set_iter(GtkTreeIter * iter,const Row * row)116 PanTreeStore :: set_iter (GtkTreeIter * iter,
117 const Row * row)
118 {
119 iter->stamp = stamp;
120 iter->user_data = (gpointer)row;
121 iter->user_data2 = 0;
122 iter->user_data3 = 0;
123 }
124
125 void
invalidate_iter(GtkTreeIter * iter)126 PanTreeStore :: invalidate_iter (GtkTreeIter * iter)
127 {
128 iter->stamp = 0;
129 iter->user_data = (void*) 0xDEADBEEF;
130 iter->user_data2 = (void*) 0xDEADBEEF;
131 iter->user_data2 = (void*) 0xDEADBEEF;
132 }
133
134 bool
set_or_invalidate(GtkTreeIter * iter,const Row * row)135 PanTreeStore :: set_or_invalidate (GtkTreeIter * iter, const Row * row)
136 {
137 if (row)
138 set_iter (iter, row);
139 else
140 invalidate_iter (iter);
141 return row != 0;
142 }
143
144
145 /*****
146 ******
147 ****** implementing GtkTreeModel's interface
148 ******
149 *****/
150
151 GtkTreeModelFlags
model_get_flags(GtkTreeModel *)152 PanTreeStore :: model_get_flags (GtkTreeModel *)
153 {
154 return GTK_TREE_MODEL_ITERS_PERSIST;
155 }
156
157 gint
model_get_n_columns(GtkTreeModel * model)158 PanTreeStore :: model_get_n_columns (GtkTreeModel * model)
159 {
160 const PanTreeStore * store (PAN_TREE_STORE(model));
161 g_return_val_if_fail (store, 0);
162 return store->n_columns;
163 }
164
165 GType
model_get_column_type(GtkTreeModel * tree,gint n)166 PanTreeStore :: model_get_column_type (GtkTreeModel * tree,
167 gint n)
168 {
169 return (*((PanTreeStore*)(tree))->column_types)[n];
170 }
171
172 gboolean
model_get_iter(GtkTreeModel * model,GtkTreeIter * setme,GtkTreePath * path)173 PanTreeStore :: model_get_iter (GtkTreeModel * model,
174 GtkTreeIter * setme,
175 GtkTreePath * path)
176 {
177 PanTreeStore * store = PAN_TREE_STORE(model);
178 g_return_val_if_fail (store, false);
179 g_return_val_if_fail (path, false);
180
181 // make sure it's not an empty path.
182 const int depth (gtk_tree_path_get_depth (path));
183 if (depth < 1)
184 return false;
185
186 // find the row that correpsonds to this path.
187 PanTreeStore::Row * row (store->root);
188 const int * indices = gtk_tree_path_get_indices (path);
189 for (int i=0; i<depth; ++i) {
190 row = row->nth_child (*indices++);
191 if (!row)
192 return false;
193 }
194
195 // build an iter from that row.
196 store->set_iter (setme, row);
197 return true;
198 }
199
200 GtkTreePath*
model_get_path(GtkTreeModel * model,GtkTreeIter * iter)201 PanTreeStore :: model_get_path (GtkTreeModel * model,
202 GtkTreeIter * iter)
203 {
204 PanTreeStore * store (PAN_TREE_STORE(model));
205 g_return_val_if_fail (store, 0);
206 return store->get_path (iter);
207 }
208
209 void
model_get_value(GtkTreeModel * model,GtkTreeIter * iter,gint column,GValue * dest_value)210 PanTreeStore :: model_get_value (GtkTreeModel * model,
211 GtkTreeIter * iter,
212 gint column,
213 GValue * dest_value)
214 {
215 g_assert (iter);
216 g_assert (dest_value);
217 PanTreeStore * store = PAN_TREE_STORE(model);
218 g_assert (store);
219 g_assert (iter->stamp == store->stamp);
220 g_assert (0<=column && column<store->n_columns);
221
222 store->get_row(iter)->get_value (column, dest_value);
223 }
224
225 gboolean
model_iter_next(GtkTreeModel * model,GtkTreeIter * iter)226 PanTreeStore :: model_iter_next (GtkTreeModel * model,
227 GtkTreeIter * iter)
228 {
229 PanTreeStore * tree = PAN_TREE_STORE(model);
230 g_return_val_if_fail (tree, false);
231 g_return_val_if_fail (iter, false);
232 g_return_val_if_fail (iter->stamp == tree->stamp, false);
233
234 Row * row (tree->get_row (iter));
235 row = row->parent->nth_child (row->child_index + 1);
236
237 return tree->set_or_invalidate (iter, row);
238 }
239
240 gboolean
model_iter_children(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)241 PanTreeStore :: model_iter_children (GtkTreeModel * model,
242 GtkTreeIter * iter,
243 GtkTreeIter * parent)
244 {
245 return model_iter_nth_child (model, iter, parent, 0);
246 }
247
248 gint
model_iter_n_children(GtkTreeModel * model,GtkTreeIter * iter)249 PanTreeStore :: model_iter_n_children (GtkTreeModel * model,
250 GtkTreeIter * iter)
251 {
252 PanTreeStore * tree = PAN_TREE_STORE(model);
253 g_return_val_if_fail (tree, 0);
254 g_return_val_if_fail (!iter || iter->stamp == tree->stamp, 0);
255
256 const Row * row (iter ? tree->get_row(iter) : tree->root);
257 return row->n_children();
258 }
259
260 gboolean
model_iter_has_child(GtkTreeModel * model,GtkTreeIter * iter)261 PanTreeStore :: model_iter_has_child (GtkTreeModel *model,
262 GtkTreeIter *iter)
263 {
264 return model_iter_n_children (model, iter) != 0;
265 }
266
267 gboolean
model_iter_nth_child(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)268 PanTreeStore :: model_iter_nth_child (GtkTreeModel * model,
269 GtkTreeIter * iter,
270 GtkTreeIter * parent,
271 gint n)
272 {
273 PanTreeStore * tree = PAN_TREE_STORE(model);
274 g_return_val_if_fail (tree, false);
275 g_return_val_if_fail (iter, false);
276 g_return_val_if_fail (!parent || parent->stamp == tree->stamp, false);
277
278 Row * row (parent ? tree->get_row(parent) : tree->root);
279 row = row->nth_child (n);
280
281 return tree->set_or_invalidate (iter, row);
282 }
283
284 gboolean
model_iter_parent(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * child)285 PanTreeStore :: model_iter_parent (GtkTreeModel * model,
286 GtkTreeIter * iter,
287 GtkTreeIter * child)
288 {
289 return PAN_TREE_STORE(model)->get_parent (iter, child);
290 }
291
292 /*****
293 ******
294 *****/
295
296 GtkTreePath*
get_path(const Row * row) const297 PanTreeStore :: get_path (const Row * row) const
298 {
299 g_return_val_if_fail (row, NULL);
300
301 std::vector<int> indices;
302 while (row && row!=root) {
303 indices.push_back (row->child_index);
304 row = row->parent;
305 }
306
307 GtkTreePath * path = gtk_tree_path_new ();
308 for (std::vector<int>::const_reverse_iterator it(indices.rbegin()), end(indices.rend()); it!=end; ++it)
309 gtk_tree_path_append_index (path, *it);
310
311 return path;
312 }
313
314 GtkTreePath*
get_path(GtkTreeIter * iter)315 PanTreeStore :: get_path (GtkTreeIter * iter)
316 {
317 g_return_val_if_fail (iter, NULL);
318 g_return_val_if_fail (iter->stamp == stamp, NULL);
319
320 return get_path (get_row (iter));
321 }
322
323 bool
get_parent(GtkTreeIter * iter,GtkTreeIter * child)324 PanTreeStore :: get_parent (GtkTreeIter * iter,
325 GtkTreeIter * child)
326 {
327 g_return_val_if_fail (child, false);
328 g_return_val_if_fail (iter, false);
329 g_return_val_if_fail (child->stamp == stamp, false);
330
331 const Row * row (get_row (child));
332 return set_or_invalidate (iter, row->parent!=root ? row->parent : 0);
333 }
334
335 bool
is_root(const GtkTreeIter * iter) const336 PanTreeStore :: is_root (const GtkTreeIter* iter) const
337 {
338 g_return_val_if_fail (iter, false);
339 g_return_val_if_fail (iter->stamp == stamp, false);
340 return get_row(iter)->parent == root;
341 }
342
343 size_t
get_depth(const Row * row) const344 PanTreeStore :: get_depth (const Row * row) const
345 {
346 g_assert (row);
347 size_t depth (0);
348 for (;;) {
349 if (row == root) break;
350 ++depth;
351 row = row->parent;
352 g_assert (row);
353 }
354 return depth;
355 }
356
357 bool
is_in_tree(Row * row) const358 PanTreeStore :: is_in_tree (Row * row) const
359 {
360 while (row && row!=root)
361 row = row->parent;
362 return row == root;
363 }
364
365 /*****
366 ******
367 ****** implementing GtkTreeSortable
368 ******
369 *****/
370
371 gboolean
sortable_get_sort_column_id(GtkTreeSortable * sortable,gint * setme_column_id,GtkSortType * setme_order)372 PanTreeStore :: sortable_get_sort_column_id (GtkTreeSortable * sortable,
373 gint * setme_column_id,
374 GtkSortType * setme_order)
375 {
376 PanTreeStore * tree (PAN_TREE_STORE (sortable));
377 g_return_val_if_fail (tree, false);
378
379 if (setme_column_id)
380 *setme_column_id = tree->sort_column_id;
381 if (setme_order)
382 *setme_order = tree->order;
383
384 return (tree->sort_column_id != GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)
385 && (tree->sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID);
386 }
387
388 gboolean
sortable_has_sort_func(GtkTreeSortable * sortable,gint col)389 PanTreeStore :: sortable_has_sort_func (GtkTreeSortable *sortable, gint col)
390 {
391 PanTreeStore * store (PAN_TREE_STORE (sortable));
392 g_return_val_if_fail (store, false);
393 column_sort_info_t::const_iterator it (store->sort_info->find (col));
394 return (it!=store->sort_info->end()) && (it->second.sort_func!=0);
395 }
396
397 // SORTING
398
399 struct
400 PanTreeStore :: SortRowInfo
401 {
402 int pos;
403 Row * row;
404 GtkTreeIter iter;
405 };
406
407 struct PanTreeStore :: SortData
408 {
409 PanTreeStore * store;
410 GtkTreeModel * model;
411 PanTreeStore::SortInfo& sort_info;
412 GtkSortType order;
SortDataPanTreeStore::SortData413 SortData (PanTreeStore * tree_store,
414 PanTreeStore::SortInfo & s,
415 GtkSortType o):
416 store(tree_store), model(GTK_TREE_MODEL(tree_store)), sort_info(s), order(o) {}
417 };
418
419 int
row_compare_func(gconstpointer a_gpointer,gconstpointer b_gpointer,gpointer user_data)420 PanTreeStore :: row_compare_func (gconstpointer a_gpointer,
421 gconstpointer b_gpointer,
422 gpointer user_data)
423 {
424 const SortRowInfo * a = (const SortRowInfo*) a_gpointer;
425 const SortRowInfo * b = (const SortRowInfo*) b_gpointer;
426 const SortData * help = (const SortData*) user_data;
427 int val = (help->sort_info.sort_func) (help->model,
428 const_cast<GtkTreeIter*>(&a->iter),
429 const_cast<GtkTreeIter*>(&b->iter),
430 help->sort_info.user_data);
431 if (!val) // inplace sort
432 val = a->row->child_index - b->row->child_index;
433
434 if (help->order == GTK_SORT_DESCENDING)
435 val = -val;
436
437 return val;
438 }
439
440 void
sort_children(SortInfo & sort_info,Row * parent,bool recurse,int mode)441 PanTreeStore :: sort_children (SortInfo & sort_info,
442 Row * parent,
443 bool recurse,
444 int mode)
445 {
446 g_assert (parent);
447 g_assert (mode==FLIP || mode==SORT);
448
449 const int n (parent->n_children());
450 if (n < 2) // no need to sort
451 return;
452
453 // build a temporary array to sort
454 SortRowInfo * sort_array = new SortRowInfo [n];
455 for (int i=0; i<n; ++i) {
456 SortRowInfo& row_info (sort_array[mode==SORT ? i : n-1-i]);
457 row_info.pos = i;
458 row_info.row = parent->children[i];
459 set_iter (&row_info.iter, row_info.row);
460 }
461
462 if (mode==SORT) {
463 SortData help (this, sort_info, order);
464 g_qsort_with_data (sort_array,
465 n, sizeof(SortRowInfo),
466 row_compare_func, &help);
467 }
468
469 // update the child indices...
470 bool reordered (false);
471 for (int i=0; i<n; ++i) {
472 Row * child = sort_array[i].row;
473 reordered |= (child->child_index != i);
474 child->child_index = i;
475 parent->children[i] = child;
476 g_assert (child->parent == parent);
477 }
478
479 // let the world know we've changed
480 if (reordered)
481 {
482 int * new_order (new int [n]);
483 for (int i=0; i<n; ++i)
484 new_order[i] = sort_array[i].pos;
485
486 GtkTreeModel * model (GTK_TREE_MODEL(this));
487 if (parent == root)
488 {
489 GtkTreePath * path (gtk_tree_path_new ());
490 gtk_tree_model_rows_reordered (model, path, 0, new_order);
491 gtk_tree_path_free (path);
492 }
493 else
494 {
495 GtkTreeIter it (get_iter (parent));
496 GtkTreePath * path (get_path (parent));
497 gtk_tree_model_rows_reordered (model, path, &it, new_order);
498 gtk_tree_path_free (path);
499 }
500
501 delete [] new_order;
502 }
503
504 delete [] sort_array;
505
506 for (int i=0; recurse && i<n; ++i)
507 sort_children (sort_info, parent->children[i], recurse, mode);
508 }
509
510 void
sort(int mode)511 PanTreeStore :: sort (int mode)
512 {
513 if (!sort_paused && (sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID))
514 {
515 g_assert (sortable_has_sort_func (GTK_TREE_SORTABLE(this), sort_column_id));
516 sort_children ((*sort_info)[sort_column_id], root, true, mode);
517 }
518 }
519
520 void
sortable_set_sort_column_id(GtkTreeSortable * sortable,gint sort_column_id,GtkSortType order)521 PanTreeStore :: sortable_set_sort_column_id (GtkTreeSortable * sortable,
522 gint sort_column_id,
523 GtkSortType order)
524 {
525 PanTreeStore * tree (PAN_TREE_STORE (sortable));
526 g_return_if_fail (tree);
527
528 // if no change, there's nothing to do...
529 if (tree->sort_column_id==sort_column_id && tree->order==order)
530 return;
531
532 // sanity checks...
533 if (sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID) {
534 g_return_if_fail (tree->sort_info->count(sort_column_id) != 0);
535 g_return_if_fail (tree->sort_info->find(sort_column_id)->second.sort_func != 0);
536 }
537
538 const bool flip (sort_column_id == tree->sort_column_id);
539 tree->sort_paused = 0;
540 tree->sort_column_id = sort_column_id;
541 tree->order = order;
542 gtk_tree_sortable_sort_column_changed (sortable);
543 tree->sort (flip ? FLIP : SORT);
544 }
545
546 gboolean
sortable_has_default_sort_func(GtkTreeSortable * sortable)547 PanTreeStore :: sortable_has_default_sort_func (GtkTreeSortable *sortable)
548 {
549 return sortable_has_sort_func (sortable, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID);
550 }
551
552 void
sortable_set_sort_func(GtkTreeSortable * sortable,gint col,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)553 PanTreeStore :: sortable_set_sort_func (GtkTreeSortable *sortable,
554 gint col,
555 GtkTreeIterCompareFunc func,
556 gpointer data,
557 GDestroyNotify destroy)
558 {
559 PanTreeStore * tree (PAN_TREE_STORE (sortable));
560 g_return_if_fail (tree);
561
562 (*tree->sort_info)[col].assign (func, data, destroy);
563
564 if (tree->sort_column_id == col)
565 tree->sort ();
566 }
567
568 void
sortable_set_default_sort_func(GtkTreeSortable * s,GtkTreeIterCompareFunc f,gpointer p,GDestroyNotify d)569 PanTreeStore :: sortable_set_default_sort_func (GtkTreeSortable * s,
570 GtkTreeIterCompareFunc f,
571 gpointer p,
572 GDestroyNotify d)
573 {
574 sortable_set_sort_func (s, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, f, p, d);
575 }
576
577 namespace
578 {
579 GObjectClass *parent_class (0);
580
581 typedef std::vector<GValue> values_t;
582 }
583
584 struct
585 PanTreeStore :: FreeRowWalker: public PanTreeStore::WalkFunctor
586 {
587 PanTreeStore * store;
FreeRowWalkerPanTreeStore::FreeRowWalker588 FreeRowWalker (PanTreeStore *t): store(t) {}
~FreeRowWalkerPanTreeStore::FreeRowWalker589 virtual ~FreeRowWalker () {}
operator ()PanTreeStore::FreeRowWalker590 virtual bool operator()(PanTreeStore *s, Row *r, GtkTreeIter*, GtkTreePath*) {
591 if (store->row_dispose)
592 store->row_dispose->row_dispose (s, r);
593 else
594 delete r;
595 return true; // keep marching
596 }
597 };
598
599 void
pan_tree_dispose(GObject * object)600 PanTreeStore :: pan_tree_dispose (GObject *object)
601 {
602 PanTreeStore * store (PAN_TREE_STORE (object));
603
604 // erase the remaining nodes
605 FreeRowWalker walk (store);
606 store->postfix_walk (walk);
607
608 // clear the sort info
609 store->sort_info->clear ();
610 }
611
612 void
pan_tree_finalize(GObject * object)613 PanTreeStore :: pan_tree_finalize (GObject *object)
614 {
615 PanTreeStore * store (PAN_TREE_STORE (object));
616
617 delete store->root;
618 delete store->column_types;
619 delete store->sort_info;
620 store->root = 0;
621 store->column_types = 0;
622 store->sort_info = 0;
623
624 (*parent_class->finalize) (object);
625 }
626
627 void
pan_tree_class_init(PanTreeStoreClass * klass)628 PanTreeStore :: pan_tree_class_init (PanTreeStoreClass *klass)
629 {
630 GObjectClass *object_class;
631
632 parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
633 object_class = (GObjectClass*) klass;
634
635 object_class->dispose = pan_tree_dispose;
636 object_class->finalize = pan_tree_finalize;
637 }
638
639 void
pan_tree_model_init(GtkTreeModelIface * iface)640 PanTreeStore :: pan_tree_model_init (GtkTreeModelIface *iface)
641 {
642 iface->get_flags = model_get_flags;
643 iface->get_n_columns = model_get_n_columns;
644 iface->get_column_type = model_get_column_type;
645 iface->get_iter = model_get_iter;
646 iface->get_path = model_get_path;
647 iface->get_value = model_get_value;
648 iface->iter_next = model_iter_next;
649 iface->iter_children = model_iter_children;
650 iface->iter_has_child = model_iter_has_child;
651 iface->iter_n_children = model_iter_n_children;
652 iface->iter_nth_child = model_iter_nth_child;
653 iface->iter_parent = model_iter_parent;
654 }
655
656 void
pan_tree_sortable_init(GtkTreeSortableIface * iface)657 PanTreeStore :: pan_tree_sortable_init (GtkTreeSortableIface *iface)
658 {
659 iface->get_sort_column_id = sortable_get_sort_column_id;
660 iface->set_sort_column_id = sortable_set_sort_column_id;
661 iface->set_sort_func = sortable_set_sort_func;
662 iface->set_default_sort_func = sortable_set_default_sort_func;
663 iface->has_default_sort_func = sortable_has_default_sort_func;
664 }
665
666 namespace
667 {
668 class RootRow: public PanTreeStore::Row {
get_value(int,GValue *)669 virtual void get_value (int, GValue *) { /* unused */ }
670 };
671 }
672
673 void
pan_tree_init(PanTreeStore * tree)674 PanTreeStore :: pan_tree_init (PanTreeStore * tree)
675 {
676 tree->stamp = g_random_int();
677 tree->n_columns = 0;
678 tree->root = new RootRow ();
679 tree->row_dispose = 0;
680 tree->column_types = new std::vector<GType>();
681 tree->sort_paused = 0;
682 tree->sort_info = new column_sort_info_t;
683 tree->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
684 tree->order = GTK_SORT_ASCENDING;
685 }
686
687 /***
688 ****
689 ***/
690
691 GType
get_type()692 PanTreeStore :: get_type ()
693 {
694 static GType pan_tree_type (0);
695 if (pan_tree_type)
696 return pan_tree_type;
697
698 static const GTypeInfo pan_tree_info = {
699 sizeof (PanTreeStoreClass),
700 NULL, // base_init
701 NULL, // base_finalize
702 (GClassInitFunc) pan_tree_class_init,
703 NULL, // class finalize
704 NULL, // class_data
705 sizeof (PanTreeStore),
706 0, // n_preallocs
707 (GInstanceInitFunc) pan_tree_init,
708 0 // value_table
709 };
710
711 pan_tree_type = g_type_register_static (
712 G_TYPE_OBJECT, "PanTreeStore", &pan_tree_info, (GTypeFlags)0);
713 static const GInterfaceInfo tree_model_info =
714 { (GInterfaceInitFunc)pan_tree_model_init, 0, 0 };
715 g_type_add_interface_static (
716 pan_tree_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
717 static const GInterfaceInfo sortable_info =
718 { (GInterfaceInitFunc)pan_tree_sortable_init, 0, 0 };
719 g_type_add_interface_static (
720 pan_tree_type, GTK_TYPE_TREE_SORTABLE, &sortable_info);
721 return pan_tree_type;
722 }
723
724 PanTreeStore*
new_tree(int n_columns,...)725 PanTreeStore :: new_tree (int n_columns, ...)
726 {
727 g_return_val_if_fail (n_columns>0, 0);
728 PanTreeStore* tree = (PanTreeStore*) g_object_new (PAN_TREE_STORE_TYPE, NULL);
729
730 va_list args;
731 va_start (args, n_columns);
732 for (int i=0; i<n_columns; ++i) {
733 const GType type (va_arg (args, GType));
734 tree->column_types->push_back (type);
735 }
736 va_end (args);
737 tree->n_columns = tree->column_types->size();
738
739 return tree;
740 }
741
742 /***
743 ****
744 ***/
745
746 void
row_changed(Row * row)747 PanTreeStore :: row_changed (Row * row)
748 {
749 GtkTreeIter iter;
750 get_iter (row, &iter);
751 row_changed (&iter);
752 }
753
754 void
row_changed(GtkTreeIter * iter)755 PanTreeStore :: row_changed (GtkTreeIter * iter)
756 {
757 GtkTreeModel * model (GTK_TREE_MODEL(this));
758 GtkTreePath * path (gtk_tree_model_get_path (model, iter));
759 gtk_tree_model_row_changed (model, path, iter);
760 gtk_tree_path_free (path);
761 }
762
763 /***
764 ****
765 ***/
766
767 void
renumber_children(Row * parent,int child_lo,int child_hi)768 PanTreeStore :: renumber_children (Row * parent,
769 int child_lo,
770 int child_hi)
771 {
772 const int n_children (parent->n_children());
773 child_hi = std::min (child_hi, n_children);
774
775 g_assert (parent);
776 g_assert (child_lo >= 0);
777 g_assert (child_lo <= child_hi);
778 g_assert (child_hi <= n_children);
779
780 Row ** it = &parent->children[child_lo];
781 for (int i=child_lo; i!=child_hi; ++i, ++it) {
782 (*it)->child_index = i;
783 (*it)->parent = parent;
784 }
785 }
786
787 struct PanTreeStore :: RowCompareByDepth
788 {
789 PanTreeStore * store;
RowCompareByDepthPanTreeStore::RowCompareByDepth790 RowCompareByDepth (PanTreeStore *s): store(s) {}
operator ()PanTreeStore::RowCompareByDepth791 bool operator() (const Row* a, const Row* b) const {
792 return store->get_depth(a) < store->get_depth(b);
793 }
794 };
795
796 void
insert_sorted(const parent_to_children_t & new_parents)797 PanTreeStore :: insert_sorted (const parent_to_children_t& new_parents)
798 {
799 g_return_if_fail (!new_parents.empty());
800
801 RowCompareByDepth depth_compare (this);
802
803 // this is a pool of all the parents that need kids added...
804 std::set<Row*> pool;
805 foreach_const (parent_to_children_t, new_parents, it)
806 pool.insert (it->first ? it->first : root);
807
808 while (!pool.empty())
809 {
810 // get a subset of pool: parents that are are in the tree
811 rows_t keys;
812 foreach_const (std::set<Row*>, pool, it)
813 if (is_in_tree (*it))
814 keys.push_back (*it);
815 g_assert (!keys.empty());
816
817 // sort these from shallowest to deepest
818 std::sort (keys.begin(), keys.end(), depth_compare);
819
820 // process each of them
821 foreach_const (rows_t, keys, it)
822 {
823 // add the children to this parent...
824 // caller passes in NULL for the parent of top-level articles...
825 Row * parent (*it);
826 Row * key (parent==root ? 0 : parent);
827 const rows_t& children (new_parents.find(key)->second);
828 insert_sorted (parent, children);
829 pool.erase (parent);
830 }
831 }
832 }
833
834 struct PanTreeStore :: RowCompareByColumn
835 {
836 PanTreeStore * store;
837 GtkTreeModel * model;
838 GtkTreeIterCompareFunc sort_func;
839 gpointer user_data;
840 bool reverse;
841
RowCompareByColumnPanTreeStore::RowCompareByColumn842 RowCompareByColumn (PanTreeStore * s):
843 store (s),
844 model (GTK_TREE_MODEL(s))
845 {
846 const SortInfo& info ((*(store->sort_info))[store->sort_column_id]);
847 sort_func = info.sort_func;
848 user_data = info.user_data;
849 reverse = (store->order == GTK_SORT_DESCENDING);
850 }
851
operator ()PanTreeStore::RowCompareByColumn852 bool operator() (const Row* a, const Row* b) const
853 {
854 GtkTreeIter a_it (store->get_iter (a));
855 GtkTreeIter b_it (store->get_iter (b));
856 int val = sort_func ? (sort_func)(model, &a_it, &b_it, user_data) : (a<b);
857 if (reverse) val = -val;
858 return val < 0;
859 }
860 };
861
862 /***
863 ****
864 ***/
865
866 struct PanTreeStore::ReparentWalker: public PanTreeStore::WalkFunctor
867 {
868 GtkTreeModel * model;
869
ReparentWalkerPanTreeStore::ReparentWalker870 ReparentWalker (PanTreeStore *store): model(GTK_TREE_MODEL(store)) {}
871
~ReparentWalkerPanTreeStore::ReparentWalker872 virtual ~ReparentWalker () {}
873
operator ()PanTreeStore::ReparentWalker874 virtual bool operator()(PanTreeStore *store, Row* row,
875 GtkTreeIter *iter, GtkTreePath *path)
876 {
877 const int n_children (row->n_children());
878
879 if (n_children)
880 {
881 // fire a "row inserted" for each child
882 GtkTreePath * cpath (gtk_tree_path_copy (path));
883 gtk_tree_path_down (cpath);
884 for (int i=0; i<n_children; ++i) {
885 Row * child (row->nth_child (i));
886 GtkTreeIter citer;
887 store->set_iter (&citer, child);
888 gtk_tree_model_row_inserted (model, cpath, &citer);
889 if (!i)
890 gtk_tree_model_row_has_child_toggled (model, path, iter);
891 gtk_tree_path_next (cpath);
892 }
893 gtk_tree_path_free (cpath);
894 }
895
896 return true; // keep marching
897 }
898 };
899
900 void
insert_sorted(Row * parent,const rows_t & children_in)901 PanTreeStore :: insert_sorted (Row * parent,
902 const rows_t & children_in)
903 {
904 g_return_if_fail (!children_in.empty());
905
906 if (!parent)
907 parent = root;
908
909 //std::cerr << LINE_ID << " adding " << children_in.size() << " sorted children to " << parent << " which currently has " << parent->children.size() << " children" << std::endl;
910
911 // sort new children...
912 rows_t children (children_in);
913 foreach (rows_t, children, it)
914 (*it)->parent = parent;
915 RowCompareByColumn compare_column (this);
916 std::sort (children.begin(), children.end(), compare_column);
917
918 //for (int i=0, n=children.size()-1; i<n; ++i)
919 // g_assert (!compare_column (children[i+1], children[i]));
920 //for (int i=0, n=parent->children.size()-1; i<n; ++i)
921 // g_assert (!compare_column (parent->children[i+1], parent->children[i]));
922
923
924
925 // merge the two sorted containers together...
926 const size_t old_size (parent->children.size());
927 rows_t tmp;
928 tmp.reserve (old_size + children.size());
929 const size_t n_new (children.size());
930 std::vector<int> indices;
931 indices.reserve (n_new);
932 rows_t::const_iterator o_it (parent->children.begin()),
933 o_end (parent->children.end()),
934 n_it (children.begin()),
935 n_end (children.end());
936 for (size_t i=0; o_it!=o_end || n_it!=n_end; ++i) {
937 Row * addme (0);
938 if ((n_it==n_end)) {
939 //std::cerr << LINE_ID << " n is empty ... adding another o at " << tmp.size() << std::endl;
940 addme = *o_it++;
941 }
942 else if (o_it==o_end) {
943 //std::cerr << LINE_ID << " o is empty .. adding NEW at " << tmp.size() << std::endl;
944 indices.push_back (i);
945 addme = *n_it++;
946 }
947 else if (compare_column (*n_it, *o_it)) {
948 //std::cerr << LINE_ID << " adding NEW at index " << tmp.size() << std::endl;
949 indices.push_back (i);
950 addme = *n_it++;
951 }
952 else {
953 //std::cerr << LINE_ID << " adding old at index " << tmp.size() << std::endl;
954 addme = *o_it++;
955 }
956 // g_assert (tmp.empty() || !compare_column(addme,tmp.back()));
957 addme->child_index = i;
958 // g_assert (tmp.empty() || !compare_column(addme,tmp.back()));
959 tmp.push_back (addme);
960 }
961 parent->children.swap (tmp);
962
963 // check our work...
964 //g_assert (parent->children.size() == old_size + children.size());
965 //g_assert (indices.size() == children.size());
966 //for (int i=0, n=parent->children.size(); i<n; ++i)
967 // g_assert (i==0 || !compare_column(parent->children[i], parent->children[i-1]));
968
969 // emit the 'row inserted' signals
970 GtkTreePath * path (get_path (parent));
971 GtkTreeIter iter;
972 GtkTreeModel * model (GTK_TREE_MODEL (this));
973 foreach_const (std::vector<int>, indices, it)
974 {
975 Row * child (parent->nth_child (*it));
976 get_iter (child, &iter);
977 gtk_tree_path_append_index (path, *it);
978 gtk_tree_model_row_inserted (model, path, &iter);
979 gtk_tree_path_up (path);
980
981 // if row has children, this handles all of their
982 // row-inserted and has-child-toggled signals...
983 // if the row doesn't have children, this has
984 // no effect...
985 if (!child->children.empty()) {
986 ReparentWalker walk (this);
987 prefix_walk (walk, &iter, true);
988 }
989 }
990
991 // maybe emit the 'row has child toggled' signal
992 if (!old_size && parent!=root) {
993 GtkTreeIter it (get_iter (parent));
994 gtk_tree_model_row_has_child_toggled (model, path, &it);
995 }
996
997 // cleanup
998 gtk_tree_path_free (path);
999
1000 //for (int i=0, n=parent->children.size(); i<n; ++i)
1001 // g_assert (i==0 || !compare_column(parent->children[i], parent->children[i-1]));
1002 }
1003
1004 void
insert(Row * parent_row,const rows_t & new_children,int position)1005 PanTreeStore :: insert (Row * parent_row,
1006 const rows_t & new_children,
1007 int position)
1008 {
1009 g_return_if_fail (!new_children.empty());
1010 g_return_if_fail (position >= 0);
1011
1012 if (!parent_row)
1013 parent_row = root;
1014
1015 // insert the rows
1016 const size_t n_rows (new_children.size());
1017 const int old_size (parent_row->n_children());
1018 position = std::min (position, old_size);
1019 parent_row->children.insert (parent_row->children.begin()+position,
1020 new_children.begin(),
1021 new_children.end());
1022 renumber_children (parent_row, position);
1023
1024 // set the return iter
1025 GtkTreeIter iter (get_iter (parent_row->children[position]));
1026
1027 // emit the 'row inserted' signals
1028 GtkTreeModel * model (GTK_TREE_MODEL(this));
1029 Row ** it (&parent_row->children[position]);
1030 GtkTreePath * path (get_path (*it));
1031 for (size_t i=0; i<n_rows; ++i)
1032 {
1033 Row * child (*it++);
1034 set_iter (&iter, child);
1035 gtk_tree_model_row_inserted (model, path, &iter);
1036 gtk_tree_path_next (path);
1037
1038 // if row has children, this handles all of their
1039 // row-inserted and has-child-toggled signals...
1040 // if the row doesn't have children, this has
1041 // no effect...
1042 if (!child->children.empty()) {
1043 ReparentWalker walk (this);
1044 prefix_walk (walk, &iter, true);
1045 }
1046 }
1047
1048 // maybe emit the 'row has child toggled' signal
1049 if (!old_size && parent_row!=root) {
1050 GtkTreeIter parent_it (get_iter (parent_row));
1051 gtk_tree_path_up (path);
1052 gtk_tree_model_row_has_child_toggled (model, path, &parent_it);
1053 }
1054
1055 // cleanup
1056 gtk_tree_path_free (path);
1057 }
1058
1059 void
reparent(Row * new_parent,Row * row,int position)1060 PanTreeStore :: reparent (Row * new_parent,
1061 Row * row,
1062 int position)
1063 {
1064 g_return_if_fail (row != 0);
1065
1066 GtkTreeModel * model (GTK_TREE_MODEL(this));
1067
1068 if (!new_parent)
1069 new_parent = root;
1070
1071 // remove our subtree's toplevel from its old parent
1072 rows_t tmp;
1073 tmp.push_back (row);
1074 remove_siblings (tmp, false);
1075
1076 // add the subtree's toplevel to its new parent row
1077 const int new_parent_old_n_children (new_parent->n_children());
1078 position = std::min (position, new_parent_old_n_children);
1079 new_parent->children.insert (new_parent->children.begin()+position, row);
1080 renumber_children (new_parent, position);
1081
1082 // emit a row-inserted for iter
1083 GtkTreeIter iter (get_iter (row));
1084 GtkTreePath * path (get_path (row));
1085 gtk_tree_model_row_inserted (model, path, &iter);
1086 gtk_tree_path_free (path);
1087
1088 // this emits all the row-inserted and has-child-toggled signals EXCEPT
1089 // for iter's row-inserted (above) and parent-iter's has-child-toggled (below).
1090 // It's kind of kludgy but gets all the signals emitted in the right sequence.
1091 ReparentWalker walk (this);
1092 prefix_walk (walk, &iter, true);
1093
1094 // if this was the new parent's first child, fire a has-child-toggled event
1095 if (!new_parent_old_n_children) {
1096 GtkTreePath * path (get_path (new_parent));
1097 GtkTreeIter new_parent_iter;
1098 get_iter (new_parent, &new_parent_iter);
1099 gtk_tree_model_row_has_child_toggled (model, path, &new_parent_iter);
1100 gtk_tree_path_free (path);
1101 }
1102 }
1103
1104 void
reparent(const parent_to_children_t & new_parents)1105 PanTreeStore :: reparent (const parent_to_children_t& new_parents)
1106 {
1107 const RowCompareByDepth depth_compare (this);
1108
1109 // REMOVE children from their OLD parents
1110
1111 rows_t remove_me;
1112 foreach_const (parent_to_children_t, new_parents, it)
1113 remove_me.insert (remove_me.end(), it->second.begin(), it->second.end());
1114 remove (remove_me, false);
1115
1116 // ADD children to their NEW parent
1117
1118 insert_sorted (new_parents);
1119 }
1120
1121 /***
1122 ****
1123 ***/
1124
1125 void
remove(const rows_t & rows,bool delete_rows)1126 PanTreeStore :: remove (const rows_t& rows, bool delete_rows)
1127 {
1128 parent_to_children_t parent_to_kids;
1129 foreach_const (rows_t, rows, it) {
1130 g_assert (*it);
1131 g_assert ((*it)->parent);
1132 parent_to_kids[(*it)->parent].push_back (*it);
1133 }
1134
1135 // remove them in same-parent batches, starting at the
1136 // deepest parents and working up to the shallower ones
1137 rows_t keys;
1138 foreach_const (parent_to_children_t, parent_to_kids, it)
1139 keys.push_back (it->first);
1140 const RowCompareByDepth compare_depth (this);
1141 std::sort (keys.begin(), keys.end(), compare_depth);
1142 foreach_const_r (rows_t, keys, it)
1143 remove_siblings (parent_to_kids[*it], delete_rows);
1144 }
1145
1146 struct PanTreeStore::ClearWalker: public PanTreeStore::WalkFunctor
1147 {
~ClearWalkerPanTreeStore::ClearWalker1148 virtual ~ClearWalker () {}
1149
clear_childrenPanTreeStore::ClearWalker1150 void clear_children (PanTreeStore* store, Row * row)
1151 {
1152 store->remove_siblings (row->children, true);
1153 }
1154
operator ()PanTreeStore::ClearWalker1155 virtual bool operator()(PanTreeStore* store,
1156 PanTreeStore::Row*,
1157 GtkTreeIter* iter, GtkTreePath*)
1158 {
1159 clear_children (store, store->get_row(iter));
1160 return true;
1161 }
1162 };
1163
1164 void
clear()1165 PanTreeStore :: clear ()
1166 {
1167 ClearWalker walker;
1168 postfix_walk (walker);
1169 walker.clear_children (this, root);
1170 }
1171
1172
1173 struct PanTreeStore :: RowCompareByChildPos {
operator ()PanTreeStore::RowCompareByChildPos1174 bool operator() (const Row* a, const Row* b) const {
1175 return a->child_index < b->child_index;
1176 }
1177 };
1178
1179 void
remove_siblings(const rows_t & siblings,bool delete_rows)1180 PanTreeStore :: remove_siblings (const rows_t& siblings,
1181 bool delete_rows)
1182 {
1183 // entry assertions
1184 g_assert (!siblings.empty());
1185 Row * parent (siblings[0]->parent);
1186 g_assert (parent);
1187 foreach_const (rows_t, siblings, it)
1188 g_assert ((*it)->parent == parent); // all are siblings
1189
1190 // unthread the doomed rows
1191 std::set<int> removed_indices;
1192 GtkTreeModel * model (GTK_TREE_MODEL(this));
1193 GtkTreePath * path (get_path (parent));
1194 foreach_const (rows_t, siblings, nit) {
1195 Row * row (*nit);
1196 removed_indices.insert (row->child_index);
1197 row->parent = 0;
1198 row->child_index = -1;
1199 }
1200
1201 // remove the dead rows; re-index the live ones
1202 int pos (0);
1203 rows_t keepers, purged;
1204 keepers.reserve (parent->n_children());
1205 foreach_const (rows_t, parent->children, it) {
1206 Row * row (*it);
1207 if (!row->parent)
1208 purged.push_back (row);
1209 else {
1210 row->child_index = pos++;
1211 keepers.push_back (row);
1212 }
1213 }
1214 parent->children.swap (keepers);
1215
1216 // fire removal signal for child
1217 foreach_const_r (std::set<int>, removed_indices, it) {
1218 gtk_tree_path_append_index (path, *it);
1219 gtk_tree_model_row_deleted (model, path);
1220 gtk_tree_path_up (path);
1221 }
1222
1223 // maybe fire has-children signal for parent
1224 if (parent!=root && !parent->n_children()) {
1225 GtkTreeIter pit;
1226 set_iter (&pit, parent);
1227 gtk_tree_model_row_has_child_toggled (model, path, &pit);
1228 }
1229
1230 // clean up purged rows
1231 if (delete_rows) {
1232 foreach (rows_t, purged, it) {
1233 Row * row (*it);
1234 g_assert (row->child_index == -1);
1235 FreeRowWalker walk (this);
1236 GtkTreeIter iter (get_iter (row));
1237 postfix_walk (walk, &iter);
1238 }
1239 }
1240
1241 gtk_tree_path_free (path);
1242 }
1243
1244 /***
1245 ****
1246 ***/
1247
1248 bool
walk_helper(int walk_mode,Row * row,GtkTreePath * path,WalkFunctor & walker)1249 PanTreeStore :: walk_helper (int walk_mode,
1250 Row * row,
1251 GtkTreePath * path,
1252 WalkFunctor & walker)
1253 {
1254 g_assert (row);
1255 g_assert (walk_mode==WALK_PREFIX || walk_mode==WALK_POSTFIX);
1256
1257 bool more (true);
1258
1259 GtkTreeIter iter;
1260 set_iter (&iter, row);
1261
1262 if (more && row!=root && walk_mode==WALK_PREFIX)
1263 more = walker (this, row, &iter, path);
1264
1265 const size_t n_children (row->n_children());
1266 if (more && n_children) {
1267 if (path)
1268 gtk_tree_path_append_index (path, 0);
1269 for (Row ** it(&row->children.front()),
1270 ** end(it+n_children); more && it!=end; ++it) {
1271 more = walk_helper (walk_mode, *it, path, walker);
1272 if (path)
1273 gtk_tree_path_next (path);
1274 }
1275 if (path)
1276 gtk_tree_path_up (path);
1277 }
1278
1279 if (more && row!=root && walk_mode==WALK_POSTFIX)
1280 more = walker (this, row, &iter, path);
1281
1282 return more;
1283 }
1284
1285 void
walk(int walk_mode,WalkFunctor & walker,GtkTreeIter * top,bool need_path)1286 PanTreeStore :: walk (int walk_mode,
1287 WalkFunctor & walker,
1288 GtkTreeIter * top,
1289 bool need_path)
1290 {
1291 GtkTreePath * path (0);
1292 if (need_path)
1293 path = top ? get_path(top) : gtk_tree_path_new();
1294
1295 Row * row (top ? get_row(top) : root);
1296 walk_helper (walk_mode, row, path, walker);
1297 gtk_tree_path_free (path);
1298 }
1299
1300 void
prefix_walk(WalkFunctor & walker,GtkTreeIter * top,bool need_path)1301 PanTreeStore :: prefix_walk (WalkFunctor & walker,
1302 GtkTreeIter * top,
1303 bool need_path)
1304 {
1305 walk (WALK_PREFIX, walker, top, need_path);
1306 }
1307
1308 void
postfix_walk(WalkFunctor & walker,GtkTreeIter * top,bool need_path)1309 PanTreeStore :: postfix_walk (WalkFunctor & walker,
1310 GtkTreeIter * top,
1311 bool need_path)
1312 {
1313 walk (WALK_POSTFIX, walker, top, need_path);
1314 }
1315
1316 /***
1317 ****
1318 ***/
1319
1320 PanTreeStore :: Row*
get_prev(Row * row)1321 PanTreeStore :: get_prev (Row * row)
1322 {
1323 if (!row || row==root)
1324 return 0;
1325 if (row->child_index==0)
1326 return row->parent==root ? 0 : row->parent;
1327 Row * sibling = row->parent->nth_child (row->child_index-1);
1328 return sibling->get_last_descendant ();
1329 }
1330
1331 PanTreeStore :: Row*
get_next(Row * row)1332 PanTreeStore :: get_next (Row * row)
1333 {
1334 // child
1335 if (!row->children.empty())
1336 return row->nth_child (0);
1337
1338 // sibling
1339 while (row && row!=root) {
1340 Row * sibling = row->parent->nth_child (row->child_index + 1);
1341 if (sibling)
1342 return sibling;
1343 row = row->parent;
1344 }
1345
1346 // if all else fails, just return the first node
1347 return root->nth_child(0);
1348 }
1349
1350 bool
get_prev(GtkTreeIter * iter)1351 PanTreeStore :: get_prev (GtkTreeIter * iter)
1352 {
1353 return set_or_invalidate (iter, get_prev(get_row(iter)));
1354 }
1355
1356 bool
get_next(GtkTreeIter * iter)1357 PanTreeStore :: get_next (GtkTreeIter * iter)
1358 {
1359 return set_or_invalidate (iter, get_next(get_row(iter)));
1360 }
1361
1362 bool
back(GtkTreeIter * iter)1363 PanTreeStore :: back (GtkTreeIter * iter)
1364 {
1365 return set_or_invalidate (iter, root->get_last_descendant());
1366 }
1367
1368 bool
front(GtkTreeIter * iter)1369 PanTreeStore :: front (GtkTreeIter * iter)
1370 {
1371 return set_or_invalidate (iter, get_next(root));
1372 }
1373