1 /*
2  * GNT - The GLib Ncurses Toolkit
3  *
4  * GNT is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This library is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  */
22 
23 #include "gntinternal.h"
24 #include "gntmarshal.h"
25 #include "gntstyle.h"
26 #include "gnttree.h"
27 #include "gntutils.h"
28 
29 #include <string.h>
30 #include <ctype.h>
31 
32 #define SEARCH_TIMEOUT_S 4   /* 4 secs */
33 #define SEARCHING(tree)  (tree->priv->search && tree->priv->search->len > 0)
34 
35 #define COLUMN_INVISIBLE(tree, index)  (tree->columns[index].flags & GNT_TREE_COLUMN_INVISIBLE)
36 #define BINARY_DATA(tree, index)       (tree->columns[index].flags & GNT_TREE_COLUMN_BINARY_DATA)
37 #define RIGHT_ALIGNED(tree, index)       (tree->columns[index].flags & GNT_TREE_COLUMN_RIGHT_ALIGNED)
38 
39 enum
40 {
41 	PROP_0,
42 	PROP_COLUMNS,
43 	PROP_EXPANDER,
44 };
45 
46 enum
47 {
48 	SIG_SELECTION_CHANGED,
49 	SIG_SCROLLED,
50 	SIG_TOGGLED,
51 	SIG_COLLAPSED,
52 	SIGS,
53 };
54 
55 struct _GntTreePriv
56 {
57 	GString *search;
58 	int search_timeout;
59 	int search_column;
60 	gboolean (*search_func)(GntTree *tree, gpointer key, const char *search, const char *current);
61 
62 	GCompareFunc compare;
63 	int lastvisible;
64 	int expander_level;
65 };
66 
67 #define	TAB_SIZE 3
68 
69 /* XXX: Make this one into a GObject?
70  * 		 ... Probably not */
71 struct _GntTreeRow
72 {
73 	void *key;
74 	void *data;		/* XXX: unused */
75 
76 	gboolean collapsed;
77 	gboolean choice;            /* Is this a choice-box?
78 	                               If choice is true, then child will be NULL */
79 	gboolean isselected;
80 	GntTextFormatFlags flags;
81 	int color;
82 
83 	GntTreeRow *parent;
84 	GntTreeRow *child;
85 	GntTreeRow *next;
86 	GntTreeRow *prev;
87 
88 	GList *columns;
89 	GntTree *tree;
90 };
91 
92 struct _GntTreeCol
93 {
94 	char *text;
95 	gboolean isbinary;
96 	int span;       /* How many columns does it span? */
97 };
98 
99 static void tree_selection_changed(GntTree *, GntTreeRow *, GntTreeRow *);
100 static void _gnt_tree_init_internals(GntTree *tree, int col);
101 
102 static GntWidgetClass *parent_class = NULL;
103 static guint signals[SIGS] = { 0 };
104 
105 static void
readjust_columns(GntTree * tree)106 readjust_columns(GntTree *tree)
107 {
108 	int i, col, total;
109 	int width;
110 #define WIDTH(i) (tree->columns[i].width_ratio ? tree->columns[i].width_ratio : tree->columns[i].width)
111 	gnt_widget_get_size(GNT_WIDGET(tree), &width, NULL);
112 	if (gnt_widget_get_has_border(GNT_WIDGET(tree))) {
113 		width -= 2;
114 	}
115 	width -= 1;  /* Exclude the scrollbar from the calculation */
116 	for (i = 0, total = 0; i < tree->ncol ; i++) {
117 		if (tree->columns[i].flags & GNT_TREE_COLUMN_INVISIBLE)
118 			continue;
119 		if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE)
120 			width -= WIDTH(i) + (tree->priv->lastvisible != i);
121 		else
122 			total += WIDTH(i) + (tree->priv->lastvisible != i);
123 	}
124 
125 	if (total == 0)
126 		return;
127 
128 	for (i = 0; i < tree->ncol; i++) {
129 		if (tree->columns[i].flags & GNT_TREE_COLUMN_INVISIBLE)
130 			continue;
131 		if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE)
132 			col = WIDTH(i);
133 		else
134 			col = (WIDTH(i) * width) / total;
135 		gnt_tree_set_col_width(GNT_TREE(tree), i, col);
136 	}
137 }
138 
139 /* Move the item at position old to position new */
140 static GList *
g_list_reposition_child(GList * list,int old,int new)141 g_list_reposition_child(GList *list, int old, int new)
142 {
143 	gpointer item = g_list_nth_data(list, old);
144 	list = g_list_remove(list, item);
145 	if (old < new)
146 		new--;   /* because the positions would have shifted after removing the item */
147 	list = g_list_insert(list, item, new);
148 	return list;
149 }
150 
151 static GntTreeRow *
_get_next(GntTreeRow * row,gboolean godeep)152 _get_next(GntTreeRow *row, gboolean godeep)
153 {
154 	if (row == NULL)
155 		return NULL;
156 	if (godeep && row->child)
157 		return row->child;
158 	if (row->next)
159 		return row->next;
160 	return _get_next(row->parent, FALSE);
161 }
162 
163 static gboolean
row_matches_search(GntTreeRow * row)164 row_matches_search(GntTreeRow *row)
165 {
166 	GntTree *t = row->tree;
167 	if (t->priv->search && t->priv->search->len > 0) {
168 		GntTreeCol *col = (col = g_list_nth_data(row->columns, t->priv->search_column)) ? col : row->columns->data;
169 		char *one, *two, *z;
170 		if (t->priv->search_func)
171 			return t->priv->search_func(t, row->key, t->priv->search->str, col->text);
172 		one = g_utf8_casefold(col->text, -1);
173 		two = g_utf8_casefold(t->priv->search->str, -1);
174 		z = strstr(one, two);
175 		g_free(one);
176 		g_free(two);
177 		if (z == NULL)
178 			return FALSE;
179 	}
180 	return TRUE;
181 }
182 
183 static GntTreeRow *
get_next(GntTreeRow * row)184 get_next(GntTreeRow *row)
185 {
186 	if (row == NULL)
187 		return NULL;
188 	while ((row = _get_next(row, !row->collapsed)) != NULL) {
189 		if (row_matches_search(row))
190 			break;
191 	}
192 	return row;
193 }
194 
195 /* Returns the n-th next row. If it doesn't exist, returns NULL */
196 static GntTreeRow *
get_next_n(GntTreeRow * row,int n)197 get_next_n(GntTreeRow *row, int n)
198 {
199 	while (row && n--)
200 		row = get_next(row);
201 	return row;
202 }
203 
204 /* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */
205 static GntTreeRow *
get_next_n_opt(GntTreeRow * row,int n,int * pos)206 get_next_n_opt(GntTreeRow *row, int n, int *pos)
207 {
208 	GntTreeRow *next = row;
209 	int r = 0;
210 
211 	if (row == NULL)
212 		return NULL;
213 
214 	while (row && n--)
215 	{
216 		row = get_next(row);
217 		if (row)
218 		{
219 			next = row;
220 			r++;
221 		}
222 	}
223 
224 	if (pos)
225 		*pos = r;
226 
227 	return next;
228 }
229 
230 static GntTreeRow *
get_last_child(GntTreeRow * row)231 get_last_child(GntTreeRow *row)
232 {
233 	if (row == NULL)
234 		return NULL;
235 	if (!row->collapsed && row->child)
236 		row = row->child;
237 	else
238 		return row;
239 
240 	while(row->next)
241 		row = row->next;
242 	return get_last_child(row);
243 }
244 
245 static GntTreeRow *
get_prev(GntTreeRow * row)246 get_prev(GntTreeRow *row)
247 {
248 	if (row == NULL)
249 		return NULL;
250 	while (row) {
251 		if (row->prev)
252 			row = get_last_child(row->prev);
253 		else
254 			row = row->parent;
255 		if (!row || row_matches_search(row))
256 			break;
257 	}
258 	return row;
259 }
260 
261 static GntTreeRow *
get_prev_n(GntTreeRow * row,int n)262 get_prev_n(GntTreeRow *row, int n)
263 {
264 	while (row && n--)
265 		row = get_prev(row);
266 	return row;
267 }
268 
269 /* Distance of row from the root */
270 /* XXX: This is uber-inefficient */
271 static int
get_root_distance(GntTreeRow * row)272 get_root_distance(GntTreeRow *row)
273 {
274 	if (row == NULL)
275 		return -1;
276 	return get_root_distance(get_prev(row)) + 1;
277 }
278 
279 /* Returns the distance between a and b.
280  * If a is 'above' b, then the distance is positive */
281 static int
get_distance(GntTreeRow * a,GntTreeRow * b)282 get_distance(GntTreeRow *a, GntTreeRow *b)
283 {
284 	/* First get the distance from a to the root.
285 	 * Then the distance from b to the root.
286 	 * Subtract.
287 	 * It's not that good, but it works. */
288 	int ha = get_root_distance(a);
289 	int hb = get_root_distance(b);
290 
291 	return (hb - ha);
292 }
293 
294 static int
find_depth(GntTreeRow * row)295 find_depth(GntTreeRow *row)
296 {
297 	int dep = -1;
298 
299 	while (row)
300 	{
301 		dep++;
302 		row = row->parent;
303 	}
304 
305 	return dep;
306 }
307 
308 static char *
update_row_text(GntTree * tree,GntTreeRow * row)309 update_row_text(GntTree *tree, GntTreeRow *row)
310 {
311 	GString *string = g_string_new(NULL);
312 	GList *iter;
313 	int i;
314 	gboolean notfirst = FALSE;
315 
316 	for (i = 0, iter = row->columns; i < tree->ncol && iter; i++, iter = iter->next)
317 	{
318 		GntTreeCol *col = iter->data;
319 		const char *text;
320 		int len;
321 		int fl = 0;
322 		gboolean cut = FALSE;
323 		int width;
324 		const char *display;
325 
326 		if (COLUMN_INVISIBLE(tree, i))
327 			continue;
328 
329 		if (BINARY_DATA(tree, i))
330 			display = "";
331 		else
332 			display = col->text;
333 
334 		len = gnt_util_onscreen_width(display, NULL);
335 
336 		width = tree->columns[i].width;
337 
338 		if (i == 0)
339 		{
340 			if (row->choice)
341 			{
342 				g_string_append_printf(string, "[%c] ",
343 						row->isselected ? 'X' : ' ');
344 				fl = 4;
345 			}
346 			else if (find_depth(row) < tree->priv->expander_level && row->child)
347 			{
348 				if (row->collapsed)
349 				{
350 					string = g_string_append(string, "+ ");
351 				}
352 				else
353 				{
354 					string = g_string_append(string, "- ");
355 				}
356 				fl = 2;
357 			}
358 			else
359 			{
360 				fl = TAB_SIZE * find_depth(row);
361 				g_string_append_printf(string, "%*s", fl, "");
362 			}
363 			len += fl;
364 		} else if (notfirst && tree->show_separator)
365 			g_string_append_c(string, '|');
366 		else
367 			g_string_append_c(string, ' ');
368 
369 		notfirst = TRUE;
370 
371 		if (len > width) {
372 			len = MAX(1, width - 1);
373 			cut = TRUE;
374 		}
375 
376 		if (RIGHT_ALIGNED(tree, i) && len < tree->columns[i].width) {
377 			g_string_append_printf(string, "%*s", width - len - cut, "");
378 		}
379 
380 		text = gnt_util_onscreen_width_to_pointer(display, len - fl, NULL);
381 		string = g_string_append_len(string, display, text - display);
382 		if (cut && width > 1) { /* ellipsis */
383 			if (gnt_ascii_only())
384 				g_string_append_c(string, '~');
385 			else
386 				string = g_string_append(string, "\342\200\246");
387 			len++;
388 		}
389 
390 		if (!RIGHT_ALIGNED(tree, i) && len < tree->columns[i].width && iter->next)
391 			g_string_append_printf(string, "%*s", width - len, "");
392 	}
393 	return g_string_free(string, FALSE);
394 }
395 
396 #define NEXT_X x += tree->columns[i].width + (i > 0 ? 1 : 0)
397 
398 static void
tree_mark_columns(GntTree * tree,int pos,int y,chtype type)399 tree_mark_columns(GntTree *tree, int pos, int y, chtype type)
400 {
401 	GntWidget *widget = GNT_WIDGET(tree);
402 	int i;
403 	int x = pos;
404 	gboolean notfirst = FALSE;
405 
406 	for (i = 0; i < tree->ncol - 1; i++)
407 	{
408 		if (!COLUMN_INVISIBLE(tree, i)) {
409 			notfirst = TRUE;
410 			NEXT_X;
411 		}
412 		if (!COLUMN_INVISIBLE(tree, i+1) && notfirst)
413 			mvwaddch(widget->window, y, x, type);
414 	}
415 }
416 
417 static void
redraw_tree(GntTree * tree)418 redraw_tree(GntTree *tree)
419 {
420 	int start, i;
421 	GntWidget *widget = GNT_WIDGET(tree);
422 	GntTreeRow *row;
423 	int pos, up, down = 0;
424 	int rows, scrcol;
425 	int current = 0;
426 
427 	if (!gnt_widget_get_mapped(GNT_WIDGET(tree)))
428 		return;
429 
430 	pos = gnt_widget_get_has_border(widget) ? 1 : 0;
431 
432 	if (tree->top == NULL)
433 		tree->top = tree->root;
434 	if (tree->current == NULL && tree->root != NULL) {
435 		tree->current = tree->root;
436 		tree_selection_changed(tree, NULL, tree->current);
437 	}
438 
439 	wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL));
440 
441 	start = 0;
442 	if (tree->show_title)
443 	{
444 		int i;
445 		int x = pos;
446 
447 		mvwhline(widget->window, pos + 1, pos, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL),
448 				widget->priv.width - pos - 1);
449 		mvwhline(widget->window, pos, pos, ' ' | gnt_color_pair(GNT_COLOR_NORMAL),
450 				widget->priv.width - pos - 1);
451 
452 		for (i = 0; i < tree->ncol; i++)
453 		{
454 			if (COLUMN_INVISIBLE(tree, i)) {
455 				continue;
456 			}
457 			mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width);
458 			NEXT_X;
459 		}
460 		if (pos)
461 		{
462 			tree_mark_columns(tree, pos, 0,
463 					(tree->show_separator ? ACS_TTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
464 			tree_mark_columns(tree, pos, widget->priv.height - pos,
465 					(tree->show_separator ? ACS_BTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
466 		}
467 		tree_mark_columns(tree, pos, pos + 1,
468 			(tree->show_separator ? ACS_PLUS : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
469 		tree_mark_columns(tree, pos, pos,
470 			(tree->show_separator ? ACS_VLINE : ' ') | gnt_color_pair(GNT_COLOR_NORMAL));
471 		start = 2;
472 	}
473 
474 	rows = widget->priv.height - pos * 2 - start - 1;
475 	tree->bottom = get_next_n_opt(tree->top, rows, &down);
476 	if (down < rows)
477 	{
478 		tree->top = get_prev_n(tree->bottom, rows);
479 		if (tree->top == NULL)
480 			tree->top = tree->root;
481 	}
482 
483 	up = get_distance(tree->top, tree->current);
484 	if (up < 0)
485 		tree->top = tree->current;
486 	else if (up >= widget->priv.height - pos)
487 		tree->top = get_prev_n(tree->current, rows);
488 
489 	if (tree->top && !row_matches_search(tree->top))
490 		tree->top = get_next(tree->top);
491 	row = tree->top;
492 	scrcol = widget->priv.width - 1 - 2 * pos;  /* exclude the borders and the scrollbar */
493 
494 	if (tree->current && !row_matches_search(tree->current)) {
495 		GntTreeRow *old = tree->current;
496 		tree->current = tree->top;
497 		tree_selection_changed(tree, old, tree->current);
498 	}
499 
500 	for (i = start + pos; row && i < widget->priv.height - pos;
501 				i++, row = get_next(row))
502 	{
503 		char *str;
504 		int wr;
505 
506 		GntTextFormatFlags flags = row->flags;
507 		int attr = 0;
508 
509 		if (!row_matches_search(row))
510 			continue;
511 		str = update_row_text(tree, row);
512 
513 		if ((wr = gnt_util_onscreen_width(str, NULL)) > scrcol)
514 		{
515 			char *s = (char*)gnt_util_onscreen_width_to_pointer(str, scrcol, &wr);
516 			*s = '\0';
517 		}
518 
519 		if (flags & GNT_TEXT_FLAG_BOLD)
520 			attr |= A_BOLD;
521 		if (flags & GNT_TEXT_FLAG_UNDERLINE)
522 			attr |= A_UNDERLINE;
523 		if (flags & GNT_TEXT_FLAG_BLINK)
524 			attr |= A_BLINK;
525 
526 		if (row == tree->current)
527 		{
528 			current = i;
529 			attr |= A_BOLD;
530 			if (gnt_widget_has_focus(widget))
531 				attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT);
532 			else
533 				attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT_D);
534 		}
535 		else
536 		{
537 			if (flags & GNT_TEXT_FLAG_DIM)
538 				if (row->color)
539 					attr |= (A_DIM | gnt_color_pair(row->color));
540 				else
541 					attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
542 			else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
543 				attr |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
544 			else if (row->color)
545 				attr |= gnt_color_pair(row->color);
546 			else
547 				attr |= gnt_color_pair(GNT_COLOR_NORMAL);
548 		}
549 
550 		wbkgdset(widget->window, '\0' | attr);
551 		mvwaddstr(widget->window, i, pos, C_(str));
552 		whline(widget->window, ' ', scrcol - wr);
553 		tree->bottom = row;
554 		g_free(str);
555 		tree_mark_columns(tree, pos, i,
556 			(tree->show_separator ? ACS_VLINE : ' ') | attr);
557 	}
558 
559 	wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL));
560 	while (i < widget->priv.height - pos)
561 	{
562 		mvwhline(widget->window, i, pos, ' ',
563 				widget->priv.width - pos * 2 - 1);
564 		tree_mark_columns(tree, pos, i,
565 			(tree->show_separator ? ACS_VLINE : ' '));
566 		i++;
567 	}
568 
569 	scrcol = widget->priv.width - pos - 1;  /* position of the scrollbar */
570 	rows--;
571 	if (rows > 0)
572 	{
573 		int total = 0;
574 		int showing, position;
575 
576 		get_next_n_opt(tree->root, g_list_length(tree->list), &total);
577 		showing = rows * rows / MAX(total, 1) + 1;
578 		showing = MIN(rows, showing);
579 
580 		total -= rows;
581 		up = get_distance(tree->root, tree->top);
582 		down = total - up;
583 
584 		position = (rows - showing) * up / MAX(1, up + down);
585 		position = MAX((tree->top != tree->root), position);
586 
587 		if (showing + position > rows)
588 			position = rows - showing;
589 
590 		if (showing + position == rows  && row)
591 			position = MAX(0, rows - 1 - showing);
592 		else if (showing + position < rows && !row)
593 			position = rows - showing;
594 
595 		position += pos + start + 1;
596 
597 		mvwvline(widget->window, pos + start + 1, scrcol,
598 				' ' | gnt_color_pair(GNT_COLOR_NORMAL), rows);
599 		mvwvline(widget->window, position, scrcol,
600 				ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing);
601 	}
602 
603 	mvwaddch(widget->window, start + pos, scrcol,
604 			((tree->top != tree->root) ?  ACS_UARROW : ' ') |
605 				gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
606 
607 	mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol,
608 			(row ?  ACS_DARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
609 
610 	/* If there's a search-text, show it in the bottom of the tree */
611 	if (tree->priv->search && tree->priv->search->len > 0) {
612 		const char *str = gnt_util_onscreen_width_to_pointer(tree->priv->search->str, scrcol - 1, NULL);
613 		wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
614 		mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos,
615 				tree->priv->search->str, str - tree->priv->search->str);
616 	}
617 	wmove(widget->window, current, pos);
618 
619 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
620 	gnt_widget_queue_update(widget);
621 G_GNUC_END_IGNORE_DEPRECATIONS
622 }
623 
624 static void
gnt_tree_draw(GntWidget * widget)625 gnt_tree_draw(GntWidget *widget)
626 {
627 	GntTree *tree = GNT_TREE(widget);
628 
629 	redraw_tree(tree);
630 
631 	GNTDEBUG;
632 }
633 
634 static void
gnt_tree_size_request(GntWidget * widget)635 gnt_tree_size_request(GntWidget *widget)
636 {
637 	if (widget->priv.height == 0)
638 		widget->priv.height = 10;	/* XXX: Why?! */
639 	if (widget->priv.width == 0)
640 	{
641 		GntTree *tree = GNT_TREE(widget);
642 		int i, width = 0;
643 		width = gnt_widget_get_has_border(GNT_WIDGET(tree)) ? 3 : 1;
644 		for (i = 0; i < tree->ncol; i++)
645 			if (!COLUMN_INVISIBLE(tree, i)) {
646 				width = width + tree->columns[i].width;
647 				if (tree->priv->lastvisible != i)
648 					width++;
649 			}
650 		widget->priv.width = width;
651 	}
652 }
653 
654 static void
gnt_tree_map(GntWidget * widget)655 gnt_tree_map(GntWidget *widget)
656 {
657 	GntTree *tree = GNT_TREE(widget);
658 	if (widget->priv.width == 0 || widget->priv.height == 0)
659 	{
660 		gnt_widget_size_request(widget);
661 	}
662 	tree->top = tree->root;
663 	tree->current = tree->root;
664 	GNTDEBUG;
665 }
666 
667 static void
tree_selection_changed(GntTree * tree,GntTreeRow * old,GntTreeRow * current)668 tree_selection_changed(GntTree *tree, GntTreeRow *old, GntTreeRow *current)
669 {
670 	g_signal_emit(tree, signals[SIG_SELECTION_CHANGED], 0, old ? old->key : NULL,
671 				current ? current->key : NULL);
672 }
673 
674 static gboolean
action_down(GntBindable * bind,GList * null)675 action_down(GntBindable *bind, GList *null)
676 {
677 	int dist;
678 	GntTree *tree = GNT_TREE(bind);
679 	GntTreeRow *old = tree->current;
680 	GntTreeRow *row = get_next(tree->current);
681 	if (row == NULL)
682 		return FALSE;
683 	tree->current = row;
684 	if ((dist = get_distance(tree->current, tree->bottom)) < 0)
685 		gnt_tree_scroll(tree, -dist);
686 	else
687 		redraw_tree(tree);
688 	if (old != tree->current)
689 		tree_selection_changed(tree, old, tree->current);
690 	return TRUE;
691 }
692 
693 static gboolean
action_move_parent(GntBindable * bind,GList * null)694 action_move_parent(GntBindable *bind, GList *null)
695 {
696 	GntTree *tree = GNT_TREE(bind);
697 	GntTreeRow *row = tree->current;
698 	int dist;
699 
700 	if (!row || !row->parent || SEARCHING(tree))
701 		return FALSE;
702 
703 	tree->current = row->parent;
704 	if ((dist = get_distance(tree->current, tree->top)) > 0)
705 		gnt_tree_scroll(tree, -dist);
706 	else
707 		redraw_tree(tree);
708 	tree_selection_changed(tree, row, tree->current);
709 	return TRUE;
710 }
711 
712 static gboolean
action_up(GntBindable * bind,GList * list)713 action_up(GntBindable *bind, GList *list)
714 {
715 	int dist;
716 	GntTree *tree = GNT_TREE(bind);
717 	GntTreeRow *old = tree->current;
718 	GntTreeRow *row = get_prev(tree->current);
719 	if (!row)
720 		return FALSE;
721 	tree->current = row;
722 	if ((dist = get_distance(tree->current, tree->top)) > 0)
723 		gnt_tree_scroll(tree, -dist);
724 	else
725 		redraw_tree(tree);
726 	if (old != tree->current)
727 		tree_selection_changed(tree, old, tree->current);
728 
729 	return TRUE;
730 }
731 
732 static gboolean
action_page_down(GntBindable * bind,GList * null)733 action_page_down(GntBindable *bind, GList *null)
734 {
735 	GntTree *tree = GNT_TREE(bind);
736 	GntTreeRow *old = tree->current;
737 	GntTreeRow *row = get_next(tree->bottom);
738 	if (row)
739 	{
740 		int dist = get_distance(tree->top, tree->current);
741 		tree->top = tree->bottom;
742 		tree->current = get_next_n_opt(tree->top, dist, NULL);
743 		redraw_tree(tree);
744 	}
745 	else if (tree->current != tree->bottom)
746 	{
747 		tree->current = tree->bottom;
748 		redraw_tree(tree);
749 	}
750 
751 	if (old != tree->current)
752 		tree_selection_changed(tree, old, tree->current);
753 	return TRUE;
754 }
755 
756 static gboolean
action_page_up(GntBindable * bind,GList * null)757 action_page_up(GntBindable *bind, GList *null)
758 {
759 	GntWidget *widget = GNT_WIDGET(bind);
760 	GntTree *tree = GNT_TREE(bind);
761 	GntTreeRow *row;
762 	GntTreeRow *old = tree->current;
763 
764 	if (tree->top != tree->root)
765 	{
766 		int dist = get_distance(tree->top, tree->current);
767 		row = get_prev_n(
768 		        tree->top,
769 		        widget->priv.height - 1 - tree->show_title * 2 -
770 		                (gnt_widget_get_has_border(widget) ? 2 : 0));
771 		if (row == NULL)
772 			row = tree->root;
773 		tree->top = row;
774 		tree->current = get_next_n_opt(tree->top, dist, NULL);
775 		redraw_tree(tree);
776 	}
777 	else if (tree->current != tree->top)
778 	{
779 		tree->current = tree->top;
780 		redraw_tree(tree);
781 	}
782 	if (old != tree->current)
783 		tree_selection_changed(tree, old, tree->current);
784 	return TRUE;
785 }
786 
787 static void
end_search(GntTree * tree)788 end_search(GntTree *tree)
789 {
790 	if (tree->priv->search) {
791 		g_source_remove(tree->priv->search_timeout);
792 		g_string_free(tree->priv->search, TRUE);
793 		tree->priv->search = NULL;
794 		tree->priv->search_timeout = 0;
795 		gnt_widget_set_disable_actions(GNT_WIDGET(tree), FALSE);
796 	}
797 }
798 
799 static gboolean
search_timeout(gpointer data)800 search_timeout(gpointer data)
801 {
802 	GntTree *tree = data;
803 
804 	end_search(tree);
805 	redraw_tree(tree);
806 
807 	return FALSE;
808 }
809 
810 static gboolean
gnt_tree_key_pressed(GntWidget * widget,const char * text)811 gnt_tree_key_pressed(GntWidget *widget, const char *text)
812 {
813 	GntTree *tree = GNT_TREE(widget);
814 	GntTreeRow *old = tree->current;
815 
816 	if (text[0] == '\r' || text[0] == '\n') {
817 		end_search(tree);
818 		gnt_widget_activate(widget);
819 	} else if (tree->priv->search) {
820 		gboolean changed = TRUE;
821 		if (g_unichar_isprint(*text)) {
822 			tree->priv->search = g_string_append_c(tree->priv->search, *text);
823 		} else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) {
824 			if (tree->priv->search->len)
825 				tree->priv->search->str[--tree->priv->search->len] = '\0';
826 		} else
827 			changed = FALSE;
828 		if (changed) {
829 			redraw_tree(tree);
830 		} else {
831 			gnt_bindable_perform_action_key(GNT_BINDABLE(tree), text);
832 		}
833 		g_source_remove(tree->priv->search_timeout);
834 		tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree);
835 		return TRUE;
836 	} else if (text[0] == ' ' && text[1] == 0) {
837 		/* Space pressed */
838 		GntTreeRow *row = tree->current;
839 		if (row && row->child)
840 		{
841 			row->collapsed = !row->collapsed;
842 			redraw_tree(tree);
843 			g_signal_emit(tree, signals[SIG_COLLAPSED], 0, row->key, row->collapsed);
844 		}
845 		else if (row && row->choice)
846 		{
847 			row->isselected = !row->isselected;
848 			g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
849 			redraw_tree(tree);
850 		}
851 	} else {
852 		return FALSE;
853 	}
854 
855 	if (old != tree->current)
856 	{
857 		tree_selection_changed(tree, old, tree->current);
858 	}
859 
860 	return TRUE;
861 }
862 
863 static void
gnt_tree_free_columns(GntTree * tree)864 gnt_tree_free_columns(GntTree *tree)
865 {
866 	int i;
867 	for (i = 0; i < tree->ncol; i++) {
868 		g_free(tree->columns[i].title);
869 	}
870 	g_free(tree->columns);
871 }
872 
873 static void
gnt_tree_destroy(GntWidget * widget)874 gnt_tree_destroy(GntWidget *widget)
875 {
876 	GntTree *tree = GNT_TREE(widget);
877 
878 	end_search(tree);
879 	if (tree->hash)
880 		g_hash_table_destroy(tree->hash);
881 	g_list_free(tree->list);
882 	gnt_tree_free_columns(tree);
883 	g_free(tree->priv);
884 }
885 
886 static gboolean
gnt_tree_clicked(GntWidget * widget,GntMouseEvent event,int x,int y)887 gnt_tree_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
888 {
889 	GntTree *tree = GNT_TREE(widget);
890 	GntTreeRow *old = tree->current;
891 	if (event == GNT_MOUSE_SCROLL_UP) {
892 		action_up(GNT_BINDABLE(widget), NULL);
893 	} else if (event == GNT_MOUSE_SCROLL_DOWN) {
894 		action_down(GNT_BINDABLE(widget), NULL);
895 	} else if (event == GNT_LEFT_MOUSE_DOWN) {
896 		GntTreeRow *row;
897 		GntTree *tree = GNT_TREE(widget);
898 		int pos = gnt_widget_get_has_border(widget) ? 1 : 0;
899 		if (tree->show_title)
900 			pos += 2;
901 		pos = y - widget->priv.y - pos;
902 		row = get_next_n(tree->top, pos);
903 		if (row && tree->current != row) {
904 			GntTreeRow *old = tree->current;
905 			tree->current = row;
906 			redraw_tree(tree);
907 			tree_selection_changed(tree, old, tree->current);
908 		} else if (row && row == tree->current) {
909 			if (row->choice) {
910 				row->isselected = !row->isselected;
911 				g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
912 				redraw_tree(tree);
913 			} else {
914 				gnt_widget_activate(widget);
915 			}
916 		}
917 	} else {
918 		return FALSE;
919 	}
920 	if (old != tree->current) {
921 		tree_selection_changed(tree, old, tree->current);
922 	}
923 	return TRUE;
924 }
925 
926 static void
gnt_tree_size_changed(GntWidget * widget,int w,int h)927 gnt_tree_size_changed(GntWidget *widget, int w, int h)
928 {
929 	GntTree *tree = GNT_TREE(widget);
930 	if (widget->priv.width <= 0)
931 		return;
932 
933 	readjust_columns(tree);
934 }
935 
936 static gboolean
start_search(GntBindable * bindable,GList * list)937 start_search(GntBindable *bindable, GList *list)
938 {
939 	GntTree *tree = GNT_TREE(bindable);
940 	if (tree->priv->search)
941 		return FALSE;
942 	gnt_widget_set_disable_actions(GNT_WIDGET(tree), TRUE);
943 	tree->priv->search = g_string_new(NULL);
944 	tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree);
945 	return TRUE;
946 }
947 
948 static gboolean
end_search_action(GntBindable * bindable,GList * list)949 end_search_action(GntBindable *bindable, GList *list)
950 {
951 	GntTree *tree = GNT_TREE(bindable);
952 	if (tree->priv->search == NULL)
953 		return FALSE;
954 	gnt_widget_set_disable_actions(GNT_WIDGET(tree), FALSE);
955 	end_search(tree);
956 	redraw_tree(tree);
957 	return TRUE;
958 }
959 
960 static gboolean
move_first_action(GntBindable * bind,GList * null)961 move_first_action(GntBindable *bind, GList *null)
962 {
963 	GntTree *tree = GNT_TREE(bind);
964 	GntTreeRow *row = tree->root;
965 	GntTreeRow *old = tree->current;
966 	if (row && !row_matches_search(row))
967 		row = get_next(row);
968 	if (row) {
969 		tree->current = row;
970 		redraw_tree(tree);
971 		if (old != tree->current)
972 			tree_selection_changed(tree, old, tree->current);
973 	}
974 
975 	return TRUE;
976 }
977 
978 static gboolean
move_last_action(GntBindable * bind,GList * null)979 move_last_action(GntBindable *bind, GList *null)
980 {
981 	GntTree *tree = GNT_TREE(bind);
982 	GntTreeRow *old = tree->current;
983 	GntTreeRow *row = tree->bottom;
984 	GntTreeRow *next;
985 
986 	while ((next = get_next(row)))
987 		row = next;
988 
989 	if (row) {
990 		tree->current = row;
991 		redraw_tree(tree);
992 		if (old != tree->current)
993 			tree_selection_changed(tree, old, tree->current);
994 	}
995 
996 	return TRUE;
997 }
998 
999 static void
gnt_tree_set_property(GObject * obj,guint prop_id,const GValue * value,GParamSpec * spec)1000 gnt_tree_set_property(GObject *obj, guint prop_id, const GValue *value,
1001 		GParamSpec *spec)
1002 {
1003 	GntTree *tree = GNT_TREE(obj);
1004 	switch (prop_id) {
1005 		case PROP_COLUMNS:
1006 			_gnt_tree_init_internals(tree, g_value_get_int(value));
1007 			break;
1008 		case PROP_EXPANDER:
1009 			if (tree->priv->expander_level == g_value_get_int(value))
1010 				break;
1011 			tree->priv->expander_level = g_value_get_int(value);
1012 			g_object_notify(obj, "expander-level");
1013 		default:
1014 			break;
1015 	}
1016 }
1017 
1018 static void
gnt_tree_get_property(GObject * obj,guint prop_id,GValue * value,GParamSpec * spec)1019 gnt_tree_get_property(GObject *obj, guint prop_id, GValue *value,
1020 		GParamSpec *spec)
1021 {
1022 	GntTree *tree = GNT_TREE(obj);
1023 	switch (prop_id) {
1024 		case PROP_COLUMNS:
1025 			g_value_set_int(value, tree->ncol);
1026 			break;
1027 		case PROP_EXPANDER:
1028 			g_value_set_int(value, tree->priv->expander_level);
1029 			break;
1030 		default:
1031 			break;
1032 	}
1033 }
1034 
1035 static void
gnt_tree_class_init(GntTreeClass * klass)1036 gnt_tree_class_init(GntTreeClass *klass)
1037 {
1038 	GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
1039 	GObjectClass *gclass = G_OBJECT_CLASS(klass);
1040 
1041 	parent_class = GNT_WIDGET_CLASS(klass);
1042 	parent_class->destroy = gnt_tree_destroy;
1043 	parent_class->draw = gnt_tree_draw;
1044 	parent_class->map = gnt_tree_map;
1045 	parent_class->size_request = gnt_tree_size_request;
1046 	parent_class->key_pressed = gnt_tree_key_pressed;
1047 	parent_class->clicked = gnt_tree_clicked;
1048 	parent_class->size_changed = gnt_tree_size_changed;
1049 
1050 	gclass->set_property = gnt_tree_set_property;
1051 	gclass->get_property = gnt_tree_get_property;
1052 	g_object_class_install_property(gclass,
1053 			PROP_COLUMNS,
1054 			g_param_spec_int("columns", "Columns",
1055 				"Number of columns in the tree.",
1056 				1, G_MAXINT, 1,
1057 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
1058 			)
1059 		);
1060 	g_object_class_install_property(gclass,
1061 			PROP_EXPANDER,
1062 			g_param_spec_int("expander-level", "Expander level",
1063 				"Number of levels to show expander in the tree.",
1064 				0, G_MAXINT, 1,
1065 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
1066 			)
1067 		);
1068 
1069 	signals[SIG_SELECTION_CHANGED] =
1070 		g_signal_new("selection-changed",
1071 					 G_TYPE_FROM_CLASS(klass),
1072 					 G_SIGNAL_RUN_LAST,
1073 					 G_STRUCT_OFFSET(GntTreeClass, selection_changed),
1074 					 NULL, NULL,
1075 					 gnt_closure_marshal_VOID__POINTER_POINTER,
1076 					 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
1077 	signals[SIG_SCROLLED] =
1078 		g_signal_new("scrolled",
1079 					 G_TYPE_FROM_CLASS(klass),
1080 					 G_SIGNAL_RUN_LAST,
1081 					 0,
1082 					 NULL, NULL,
1083 					 g_cclosure_marshal_VOID__INT,
1084 					 G_TYPE_NONE, 1, G_TYPE_INT);
1085 	signals[SIG_TOGGLED] =
1086 		g_signal_new("toggled",
1087 					 G_TYPE_FROM_CLASS(klass),
1088 					 G_SIGNAL_RUN_LAST,
1089 					 G_STRUCT_OFFSET(GntTreeClass, toggled),
1090 					 NULL, NULL,
1091 					 g_cclosure_marshal_VOID__POINTER,
1092 					 G_TYPE_NONE, 1, G_TYPE_POINTER);
1093 	signals[SIG_COLLAPSED] =
1094 		g_signal_new("collapse-toggled",
1095 					 G_TYPE_FROM_CLASS(klass),
1096 					 G_SIGNAL_RUN_LAST,
1097 					 0,
1098 					 NULL, NULL,
1099 					 gnt_closure_marshal_VOID__POINTER_BOOLEAN,
1100 					 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
1101 
1102 	gnt_bindable_class_register_action(bindable, "move-up", action_up,
1103 				GNT_KEY_UP, NULL);
1104 	gnt_bindable_register_binding(bindable, "move-up", GNT_KEY_CTRL_P, NULL);
1105 	gnt_bindable_class_register_action(bindable, "move-down", action_down,
1106 				GNT_KEY_DOWN, NULL);
1107 	gnt_bindable_register_binding(bindable, "move-down", GNT_KEY_CTRL_N, NULL);
1108 	gnt_bindable_class_register_action(bindable, "move-parent", action_move_parent,
1109 				GNT_KEY_BACKSPACE, NULL);
1110 	gnt_bindable_class_register_action(bindable, "page-up", action_page_up,
1111 				GNT_KEY_PGUP, NULL);
1112 	gnt_bindable_class_register_action(bindable, "page-down", action_page_down,
1113 				GNT_KEY_PGDOWN, NULL);
1114 	gnt_bindable_class_register_action(bindable, "start-search", start_search,
1115 				"/", NULL);
1116 	gnt_bindable_class_register_action(bindable, "end-search", end_search_action,
1117 				"\033", NULL);
1118 	gnt_bindable_class_register_action(bindable, "move-first", move_first_action,
1119 			GNT_KEY_HOME, NULL);
1120 	gnt_bindable_class_register_action(bindable, "move-last", move_last_action,
1121 			GNT_KEY_END, NULL);
1122 
1123 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), bindable);
1124 	GNTDEBUG;
1125 }
1126 
1127 static void
gnt_tree_init(GTypeInstance * instance,gpointer class)1128 gnt_tree_init(GTypeInstance *instance, gpointer class)
1129 {
1130 	GntWidget *widget = GNT_WIDGET(instance);
1131 	GntTree *tree = GNT_TREE(widget);
1132 	tree->show_separator = TRUE;
1133 	tree->priv = g_new0(GntTreePriv, 1);
1134 
1135 	gnt_widget_set_grow_x(widget, TRUE);
1136 	gnt_widget_set_grow_y(widget, TRUE);
1137 	gnt_widget_set_has_shadow(widget, FALSE);
1138 	gnt_widget_set_take_focus(widget, TRUE);
1139 	widget->priv.minw = 4;
1140 	widget->priv.minh = 1;
1141 	GNTDEBUG;
1142 }
1143 
1144 /******************************************************************************
1145  * GntTree API
1146  *****************************************************************************/
1147 GType
gnt_tree_get_gtype(void)1148 gnt_tree_get_gtype(void)
1149 {
1150 	static GType type = 0;
1151 
1152 	if(type == 0)
1153 	{
1154 		static const GTypeInfo info = {
1155 			sizeof(GntTreeClass),
1156 			NULL,					/* base_init		*/
1157 			NULL,					/* base_finalize	*/
1158 			(GClassInitFunc)gnt_tree_class_init,
1159 			NULL,					/* class_finalize	*/
1160 			NULL,					/* class_data		*/
1161 			sizeof(GntTree),
1162 			0,						/* n_preallocs		*/
1163 			gnt_tree_init,			/* instance_init	*/
1164 			NULL					/* value_table		*/
1165 		};
1166 
1167 		type = g_type_register_static(GNT_TYPE_WIDGET,
1168 									  "GntTree",
1169 									  &info, 0);
1170 	}
1171 
1172 	return type;
1173 }
1174 
1175 static void
free_tree_col(gpointer data)1176 free_tree_col(gpointer data)
1177 {
1178 	GntTreeCol *col = data;
1179 	if (!col->isbinary)
1180 		g_free(col->text);
1181 	g_free(col);
1182 }
1183 
1184 static void
free_tree_row(gpointer data)1185 free_tree_row(gpointer data)
1186 {
1187 	GntTreeRow *row = data;
1188 
1189 	if (!row)
1190 		return;
1191 
1192 	g_list_foreach(row->columns, (GFunc)free_tree_col, NULL);
1193 	g_list_free(row->columns);
1194 	g_free(row);
1195 }
1196 
gnt_tree_new()1197 GntWidget *gnt_tree_new()
1198 {
1199 	return gnt_tree_new_with_columns(1);
1200 }
1201 
gnt_tree_set_visible_rows(GntTree * tree,int rows)1202 void gnt_tree_set_visible_rows(GntTree *tree, int rows)
1203 {
1204 	GntWidget *widget = GNT_WIDGET(tree);
1205 	widget->priv.height = rows;
1206 	if (gnt_widget_get_has_border(widget)) {
1207 		widget->priv.height += 2;
1208 	}
1209 }
1210 
gnt_tree_get_visible_rows(GntTree * tree)1211 int gnt_tree_get_visible_rows(GntTree *tree)
1212 {
1213 	GntWidget *widget = GNT_WIDGET(tree);
1214 	int ret = widget->priv.height;
1215 	if (gnt_widget_get_has_border(widget)) {
1216 		ret -= 2;
1217 	}
1218 	return ret;
1219 }
1220 
gnt_tree_get_rows(GntTree * tree)1221 GList *gnt_tree_get_rows(GntTree *tree)
1222 {
1223 	return tree->list;
1224 }
1225 
gnt_tree_scroll(GntTree * tree,int count)1226 void gnt_tree_scroll(GntTree *tree, int count)
1227 {
1228 	GntTreeRow *row;
1229 
1230 	if (count < 0)
1231 	{
1232 		if (get_root_distance(tree->top) == 0)
1233 			return;
1234 		row = get_prev_n(tree->top, -count);
1235 		if (row == NULL)
1236 			row = tree->root;
1237 		tree->top = row;
1238 	}
1239 	else
1240 	{
1241 		get_next_n_opt(tree->bottom, count, &count);
1242 		tree->top = get_next_n(tree->top, count);
1243 	}
1244 
1245 	redraw_tree(tree);
1246 	g_signal_emit(tree, signals[SIG_SCROLLED], 0, count);
1247 }
1248 
1249 static gpointer
find_position(GntTree * tree,gpointer key,gpointer parent)1250 find_position(GntTree *tree, gpointer key, gpointer parent)
1251 {
1252 	GntTreeRow *row;
1253 
1254 	if (tree->priv->compare == NULL)
1255 		return NULL;
1256 
1257 	if (parent == NULL)
1258 		row = tree->root;
1259 	else
1260 		row = g_hash_table_lookup(tree->hash, parent);
1261 
1262 	if (!row)
1263 		return NULL;
1264 
1265 	if (parent)
1266 		row = row->child;
1267 
1268 	while (row)
1269 	{
1270 		if (tree->priv->compare(key, row->key) < 0)
1271 			return (row->prev ? row->prev->key : NULL);
1272 		if (row->next)
1273 			row = row->next;
1274 		else
1275 			return row->key;
1276 	}
1277 	return NULL;
1278 }
1279 
gnt_tree_sort_row(GntTree * tree,gpointer key)1280 void gnt_tree_sort_row(GntTree *tree, gpointer key)
1281 {
1282 	GntTreeRow *row, *q, *s;
1283 	int current, newp;
1284 
1285 	if (!tree->priv->compare)
1286 		return;
1287 
1288 	row = g_hash_table_lookup(tree->hash, key);
1289 	g_return_if_fail(row != NULL);
1290 
1291 	current = g_list_index(tree->list, key);
1292 
1293 	if (row->parent)
1294 		s = row->parent->child;
1295 	else
1296 		s = tree->root;
1297 
1298 	q = NULL;
1299 	while (s) {
1300 		if (tree->priv->compare(row->key, s->key) < 0)
1301 			break;
1302 		q = s;
1303 		s = s->next;
1304 	}
1305 
1306 	/* Move row between q and s */
1307 	if (row == q || row == s)
1308 		return;
1309 
1310 	if (q == NULL) {
1311 		/* row becomes the first child of its parent */
1312 		row->prev->next = row->next;  /* row->prev cannot be NULL at this point */
1313 		if (row->next)
1314 			row->next->prev = row->prev;
1315 		if (row->parent)
1316 			row->parent->child = row;
1317 		else
1318 			tree->root = row;
1319 		row->next = s;
1320 		s->prev = row;  /* s cannot be NULL */
1321 		row->prev = NULL;
1322 		newp = g_list_index(tree->list, s) - 1;
1323 	} else {
1324 		if (row->prev) {
1325 			row->prev->next = row->next;
1326 		} else {
1327 			/* row was the first child of its parent */
1328 			if (row->parent)
1329 				row->parent->child = row->next;
1330 			else
1331 				tree->top = row->next;
1332 		}
1333 
1334 		if (row->next)
1335 			row->next->prev = row->prev;
1336 
1337 		q->next = row;
1338 		row->prev = q;
1339 		if (s)
1340 			s->prev = row;
1341 		row->next = s;
1342 		newp = g_list_index(tree->list, q) + 1;
1343 	}
1344 	tree->list = g_list_reposition_child(tree->list, current, newp);
1345 
1346 	redraw_tree(tree);
1347 }
1348 
gnt_tree_add_row_after(GntTree * tree,void * key,GntTreeRow * row,void * parent,void * bigbro)1349 GntTreeRow *gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1350 {
1351 	GntTreeRow *pr = NULL;
1352 
1353 	if (g_hash_table_lookup(tree->hash, key)) {
1354 		gnt_tree_remove(tree, key);
1355 	}
1356 
1357 	row->tree = tree;
1358 	row->key = key;
1359 	row->data = NULL;
1360 	g_hash_table_replace(tree->hash, key, row);
1361 
1362 	if (bigbro == NULL && tree->priv->compare)
1363 	{
1364 		bigbro = find_position(tree, key, parent);
1365 	}
1366 
1367 	if (tree->root == NULL)
1368 	{
1369 		tree->root = row;
1370 		tree->list = g_list_prepend(tree->list, key);
1371 	}
1372 	else
1373 	{
1374 		int position = 0;
1375 
1376 		if (bigbro)
1377 		{
1378 			pr = g_hash_table_lookup(tree->hash, bigbro);
1379 			if (pr)
1380 			{
1381 				if (pr->next)	pr->next->prev = row;
1382 				row->next = pr->next;
1383 				row->prev = pr;
1384 				pr->next = row;
1385 				row->parent = pr->parent;
1386 
1387 				position = g_list_index(tree->list, bigbro);
1388 			}
1389 		}
1390 
1391 		if (pr == NULL && parent)
1392 		{
1393 			pr = g_hash_table_lookup(tree->hash, parent);
1394 			if (pr)
1395 			{
1396 				if (pr->child)	pr->child->prev = row;
1397 				row->next = pr->child;
1398 				pr->child = row;
1399 				row->parent = pr;
1400 
1401 				position = g_list_index(tree->list, parent);
1402 			}
1403 		}
1404 
1405 		if (pr == NULL)
1406 		{
1407 			GntTreeRow *r = tree->root;
1408 			row->next = r;
1409 			if (r) r->prev = row;
1410 			if (tree->current == tree->root)
1411 				tree->current = row;
1412 			tree->root = row;
1413 			tree->list = g_list_prepend(tree->list, key);
1414 		}
1415 		else
1416 		{
1417 			tree->list = g_list_insert(tree->list, key, position + 1);
1418 		}
1419 	}
1420 	redraw_tree(tree);
1421 
1422 	return row;
1423 }
1424 
gnt_tree_add_row_last(GntTree * tree,void * key,GntTreeRow * row,void * parent)1425 GntTreeRow *gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent)
1426 {
1427 	GntTreeRow *pr = NULL, *br = NULL;
1428 
1429 	if (parent)
1430 		pr = g_hash_table_lookup(tree->hash, parent);
1431 
1432 	if (pr)
1433 		br = pr->child;
1434 	else
1435 		br = tree->root;
1436 
1437 	if (br)
1438 	{
1439 		while (br->next)
1440 			br = br->next;
1441 	}
1442 
1443 	return gnt_tree_add_row_after(tree, key, row, parent, br ? br->key : NULL);
1444 }
1445 
gnt_tree_get_selection_data(GntTree * tree)1446 gpointer gnt_tree_get_selection_data(GntTree *tree)
1447 {
1448 	if (tree->current)
1449 		return tree->current->key;	/* XXX: perhaps we should just get rid of 'data' */
1450 	return NULL;
1451 }
1452 
gnt_tree_get_selection_text(GntTree * tree)1453 char *gnt_tree_get_selection_text(GntTree *tree)
1454 {
1455 	if (tree->current)
1456 		return update_row_text(tree, tree->current);
1457 	return NULL;
1458 }
1459 
gnt_tree_get_row_text_list(GntTree * tree,gpointer key)1460 GList *gnt_tree_get_row_text_list(GntTree *tree, gpointer key)
1461 {
1462 	GList *list = NULL, *iter;
1463 	GntTreeRow *row = key ? g_hash_table_lookup(tree->hash, key) : tree->current;
1464 	int i;
1465 
1466 	if (!row)
1467 		return NULL;
1468 
1469 	for (i = 0, iter = row->columns; i < tree->ncol && iter;
1470 			i++, iter = iter->next)
1471 	{
1472 		GntTreeCol *col = iter->data;
1473 		list = g_list_append(list, BINARY_DATA(tree, i) ? col->text : g_strdup(col->text));
1474 	}
1475 
1476 	return list;
1477 }
1478 
gnt_tree_get_selection_text_list(GntTree * tree)1479 GList *gnt_tree_get_selection_text_list(GntTree *tree)
1480 {
1481 	return gnt_tree_get_row_text_list(tree, NULL);
1482 }
1483 
gnt_tree_remove(GntTree * tree,gpointer key)1484 void gnt_tree_remove(GntTree *tree, gpointer key)
1485 {
1486 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1487 	static int depth = 0; /* Only redraw after all child nodes are removed */
1488 	if (row)
1489 	{
1490 		gboolean redraw = FALSE;
1491 
1492 		if (row->child) {
1493 			depth++;
1494 			while (row->child) {
1495 				gnt_tree_remove(tree, row->child->key);
1496 			}
1497 			depth--;
1498 		}
1499 
1500 		if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0)
1501 			redraw = TRUE;
1502 
1503 		/* Update root/top/current/bottom if necessary */
1504 		if (tree->root == row)
1505 			tree->root = get_next(row);
1506 		if (tree->top == row)
1507 		{
1508 			if (tree->top != tree->root)
1509 				tree->top = get_prev(row);
1510 			else
1511 				tree->top = get_next(row);
1512 		}
1513 		if (tree->current == row)
1514 		{
1515 			if (tree->current != tree->root)
1516 				tree->current = get_prev(row);
1517 			else
1518 				tree->current = get_next(row);
1519 			tree_selection_changed(tree, row, tree->current);
1520 		}
1521 		if (tree->bottom == row)
1522 		{
1523 			tree->bottom = get_prev(row);
1524 		}
1525 
1526 		/* Fix the links */
1527 		if (row->next)
1528 			row->next->prev = row->prev;
1529 		if (row->parent && row->parent->child == row)
1530 			row->parent->child = row->next;
1531 		if (row->prev)
1532 			row->prev->next = row->next;
1533 
1534 		g_hash_table_remove(tree->hash, key);
1535 		tree->list = g_list_remove(tree->list, key);
1536 
1537 		if (redraw && depth == 0)
1538 		{
1539 			redraw_tree(tree);
1540 		}
1541 	}
1542 }
1543 
1544 static gboolean
return_true(gpointer key,gpointer data,gpointer null)1545 return_true(gpointer key, gpointer data, gpointer null)
1546 {
1547 	return TRUE;
1548 }
1549 
gnt_tree_remove_all(GntTree * tree)1550 void gnt_tree_remove_all(GntTree *tree)
1551 {
1552 	tree->root = NULL;
1553 	g_hash_table_foreach_remove(tree->hash, (GHRFunc)return_true, tree);
1554 	g_list_free(tree->list);
1555 	tree->list = NULL;
1556 	tree->current = tree->top = tree->bottom = NULL;
1557 }
1558 
gnt_tree_get_selection_visible_line(GntTree * tree)1559 int gnt_tree_get_selection_visible_line(GntTree *tree)
1560 {
1561 	return get_distance(tree->top, tree->current) +
1562 	       !gnt_widget_get_has_border(GNT_WIDGET(tree));
1563 }
1564 
gnt_tree_change_text(GntTree * tree,gpointer key,int colno,const char * text)1565 void gnt_tree_change_text(GntTree *tree, gpointer key, int colno, const char *text)
1566 {
1567 	GntTreeRow *row;
1568 	GntTreeCol *col;
1569 
1570 	g_return_if_fail(colno < tree->ncol);
1571 
1572 	row = g_hash_table_lookup(tree->hash, key);
1573 	if (row)
1574 	{
1575 		col = g_list_nth_data(row->columns, colno);
1576 		if (BINARY_DATA(tree, colno)) {
1577 			col->text = (gpointer)text;
1578 		} else {
1579 			g_free(col->text);
1580 			col->text = g_strdup(text ? text : "");
1581 		}
1582 
1583 		if (gnt_widget_get_mapped(GNT_WIDGET(tree)) &&
1584 		    get_distance(tree->top, row) >= 0 &&
1585 		    get_distance(row, tree->bottom) >= 0) {
1586 			redraw_tree(tree);
1587 		}
1588 	}
1589 }
1590 
gnt_tree_add_choice(GntTree * tree,void * key,GntTreeRow * row,void * parent,void * bigbro)1591 GntTreeRow *gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1592 {
1593 	GntTreeRow *r;
1594 	r = g_hash_table_lookup(tree->hash, key);
1595 	g_return_val_if_fail(!r || !r->choice, NULL);
1596 
1597 	if (bigbro == NULL) {
1598 		if (tree->priv->compare)
1599 			bigbro = find_position(tree, key, parent);
1600 		else {
1601 			r = g_hash_table_lookup(tree->hash, parent);
1602 			if (!r)
1603 				r = tree->root;
1604 			else
1605 				r = r->child;
1606 			if (r) {
1607 				while (r->next)
1608 					r = r->next;
1609 				bigbro = r->key;
1610 			}
1611 		}
1612 	}
1613 	row = gnt_tree_add_row_after(tree, key, row, parent, bigbro);
1614 	row->choice = TRUE;
1615 
1616 	return row;
1617 }
1618 
gnt_tree_set_choice(GntTree * tree,void * key,gboolean set)1619 void gnt_tree_set_choice(GntTree *tree, void *key, gboolean set)
1620 {
1621 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1622 
1623 	if (!row)
1624 		return;
1625 	g_return_if_fail(row->choice);
1626 
1627 	row->isselected = set;
1628 	redraw_tree(tree);
1629 }
1630 
gnt_tree_get_choice(GntTree * tree,void * key)1631 gboolean gnt_tree_get_choice(GntTree *tree, void *key)
1632 {
1633 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1634 
1635 	if (!row)
1636 		return FALSE;
1637 	g_return_val_if_fail(row->choice, FALSE);
1638 
1639 	return row->isselected;
1640 }
1641 
gnt_tree_set_row_flags(GntTree * tree,void * key,GntTextFormatFlags flags)1642 void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags)
1643 {
1644 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1645 	if (!row || row->flags == flags)
1646 		return;
1647 
1648 	row->flags = flags;
1649 	redraw_tree(tree);	/* XXX: It shouldn't be necessary to redraw the whole darned tree */
1650 }
1651 
gnt_tree_set_row_color(GntTree * tree,void * key,int color)1652 void gnt_tree_set_row_color(GntTree *tree, void *key, int color)
1653 {
1654 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1655 	if (!row || row->color == color)
1656 		return;
1657 
1658 	row->color = color;
1659 	redraw_tree(tree);
1660 }
1661 
gnt_tree_set_selected(GntTree * tree,void * key)1662 void gnt_tree_set_selected(GntTree *tree , void *key)
1663 {
1664 	int dist;
1665 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1666 	if (!row || row == tree->current)
1667 		return;
1668 
1669 	if (tree->top == NULL)
1670 		tree->top = row;
1671 	if (tree->bottom == NULL)
1672 		tree->bottom = row;
1673 
1674 	tree->current = row;
1675 	if ((dist = get_distance(tree->current, tree->bottom)) < 0)
1676 		gnt_tree_scroll(tree, -dist);
1677 	else if ((dist = get_distance(tree->current, tree->top)) > 0)
1678 		gnt_tree_scroll(tree, -dist);
1679 	else
1680 		redraw_tree(tree);
1681 	tree_selection_changed(tree, row, tree->current);
1682 }
1683 
_gnt_tree_init_internals(GntTree * tree,int col)1684 static void _gnt_tree_init_internals(GntTree *tree, int col)
1685 {
1686 	gnt_tree_free_columns(tree);
1687 
1688 	tree->ncol = col;
1689 	tree->hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_tree_row);
1690 	tree->columns = g_new0(struct _GntTreeColInfo, col);
1691 	tree->priv->lastvisible = col - 1;
1692 	while (col--)
1693 	{
1694 		tree->columns[col].width = 15;
1695 	}
1696 	tree->list = NULL;
1697 	tree->show_title = FALSE;
1698 	g_object_notify(G_OBJECT(tree), "columns");
1699 }
1700 
gnt_tree_new_with_columns(int col)1701 GntWidget *gnt_tree_new_with_columns(int col)
1702 {
1703 	GntWidget *widget = g_object_new(GNT_TYPE_TREE,
1704 			"columns", col,
1705 			"expander-level", 1,
1706 			NULL);
1707 
1708 	return widget;
1709 }
1710 
gnt_tree_create_row_from_list(GntTree * tree,GList * list)1711 GntTreeRow *gnt_tree_create_row_from_list(GntTree *tree, GList *list)
1712 {
1713 	GList *iter;
1714 	int i;
1715 	GntTreeRow *row = g_new0(GntTreeRow, 1);
1716 
1717 	for (i = 0, iter = list; i < tree->ncol && iter; iter = iter->next, i++)
1718 	{
1719 		GntTreeCol *col = g_new0(GntTreeCol, 1);
1720 		col->span = 1;
1721 		if (BINARY_DATA(tree, i)) {
1722 			col->text = iter->data;
1723 			col->isbinary = TRUE;
1724 		} else {
1725 			col->text = g_strdup(iter->data ? iter->data : "");
1726 			col->isbinary = FALSE;
1727 		}
1728 
1729 		row->columns = g_list_append(row->columns, col);
1730 	}
1731 
1732 	return row;
1733 }
1734 
gnt_tree_create_row(GntTree * tree,...)1735 GntTreeRow *gnt_tree_create_row(GntTree *tree, ...)
1736 {
1737 	int i;
1738 	va_list args;
1739 	GList *list = NULL;
1740 	GntTreeRow *row;
1741 
1742 	va_start(args, tree);
1743 	for (i = 0; i < tree->ncol; i++)
1744 	{
1745 		list = g_list_append(list, va_arg(args, char *));
1746 	}
1747 	va_end(args);
1748 
1749 	row = gnt_tree_create_row_from_list(tree, list);
1750 	g_list_free(list);
1751 
1752 	return row;
1753 }
1754 
gnt_tree_set_col_width(GntTree * tree,int col,int width)1755 void gnt_tree_set_col_width(GntTree *tree, int col, int width)
1756 {
1757 	g_return_if_fail(col < tree->ncol);
1758 
1759 	tree->columns[col].width = width;
1760 	if (tree->columns[col].width_ratio == 0)
1761 		tree->columns[col].width_ratio = width;
1762 }
1763 
gnt_tree_set_column_title(GntTree * tree,int index,const char * title)1764 void gnt_tree_set_column_title(GntTree *tree, int index, const char *title)
1765 {
1766 	g_free(tree->columns[index].title);
1767 	tree->columns[index].title = g_strdup(title);
1768 }
1769 
gnt_tree_set_column_titles(GntTree * tree,...)1770 void gnt_tree_set_column_titles(GntTree *tree, ...)
1771 {
1772 	int i;
1773 	va_list args;
1774 
1775 	va_start(args, tree);
1776 	for (i = 0; i < tree->ncol; i++)
1777 	{
1778 		const char *title = va_arg(args, const char *);
1779 		tree->columns[i].title = g_strdup(title);
1780 	}
1781 	va_end(args);
1782 }
1783 
gnt_tree_set_show_title(GntTree * tree,gboolean set)1784 void gnt_tree_set_show_title(GntTree *tree, gboolean set)
1785 {
1786 	tree->show_title = set;
1787 	GNT_WIDGET(tree)->priv.minh = (set ? 6 : 4);
1788 }
1789 
gnt_tree_set_compare_func(GntTree * tree,GCompareFunc func)1790 void gnt_tree_set_compare_func(GntTree *tree, GCompareFunc func)
1791 {
1792 	tree->priv->compare = func;
1793 }
1794 
gnt_tree_set_expanded(GntTree * tree,void * key,gboolean expanded)1795 void gnt_tree_set_expanded(GntTree *tree, void *key, gboolean expanded)
1796 {
1797 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1798 	if (row) {
1799 		row->collapsed = !expanded;
1800 		if (GNT_WIDGET(tree)->window)
1801 			gnt_widget_draw(GNT_WIDGET(tree));
1802 		g_signal_emit(tree, signals[SIG_COLLAPSED], 0, key, row->collapsed);
1803 	}
1804 }
1805 
gnt_tree_set_show_separator(GntTree * tree,gboolean set)1806 void gnt_tree_set_show_separator(GntTree *tree, gboolean set)
1807 {
1808 	tree->show_separator = set;
1809 }
1810 
gnt_tree_adjust_columns(GntTree * tree)1811 void gnt_tree_adjust_columns(GntTree *tree)
1812 {
1813 	GntTreeRow *row = tree->root;
1814 	int *widths, i, twidth;
1815 
1816 	widths = g_new0(int, tree->ncol);
1817 	while (row) {
1818 		GList *iter;
1819 		for (i = 0, iter = row->columns; iter; iter = iter->next, i++) {
1820 			GntTreeCol *col = iter->data;
1821 			int w = gnt_util_onscreen_width(col->text, NULL);
1822 			if (i == 0 && row->choice)
1823 				w += 4;
1824 			if (i == 0) {
1825 				w += find_depth(row) * TAB_SIZE;
1826 			}
1827 			if (widths[i] < w)
1828 				widths[i] = w;
1829 		}
1830 		row = get_next(row);
1831 	}
1832 
1833 	twidth = gnt_widget_get_has_border(GNT_WIDGET(tree)) ? 3 : 1;
1834 	for (i = 0; i < tree->ncol; i++) {
1835 		if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE)
1836 			widths[i] = tree->columns[i].width;
1837 		gnt_tree_set_col_width(tree, i, widths[i]);
1838 		if (!COLUMN_INVISIBLE(tree, i)) {
1839 			twidth = twidth + widths[i];
1840 			if (tree->priv->lastvisible != i)
1841 				twidth += 1;
1842 		}
1843 	}
1844 	g_free(widths);
1845 
1846 	gnt_widget_set_size(GNT_WIDGET(tree), twidth, -1);
1847 }
1848 
gnt_tree_set_hash_fns(GntTree * tree,gpointer hash,gpointer eq,gpointer kd)1849 void gnt_tree_set_hash_fns(GntTree *tree, gpointer hash, gpointer eq, gpointer kd)
1850 {
1851 	g_hash_table_foreach_remove(tree->hash, return_true, NULL);
1852 	g_hash_table_destroy(tree->hash);
1853 	tree->hash = g_hash_table_new_full(hash, eq, kd, free_tree_row);
1854 }
1855 
1856 static void
set_column_flag(GntTree * tree,int col,GntTreeColumnFlag flag,gboolean set)1857 set_column_flag(GntTree *tree, int col, GntTreeColumnFlag flag, gboolean set)
1858 {
1859 	if (set)
1860 		tree->columns[col].flags |= flag;
1861 	else
1862 		tree->columns[col].flags &= ~flag;
1863 }
1864 
gnt_tree_set_column_visible(GntTree * tree,int col,gboolean vis)1865 void gnt_tree_set_column_visible(GntTree *tree, int col, gboolean vis)
1866 {
1867 	g_return_if_fail(col < tree->ncol);
1868 	set_column_flag(tree, col, GNT_TREE_COLUMN_INVISIBLE, !vis);
1869 	if (vis) {
1870 		/* the column is visible */
1871 		if (tree->priv->lastvisible < col)
1872 			tree->priv->lastvisible = col;
1873 	} else {
1874 		if (tree->priv->lastvisible == col)
1875 			while (tree->priv->lastvisible) {
1876 				tree->priv->lastvisible--;
1877 				if (!COLUMN_INVISIBLE(tree, tree->priv->lastvisible))
1878 					break;
1879 			}
1880 	}
1881 	if (gnt_widget_get_mapped(GNT_WIDGET(tree)))
1882 		readjust_columns(tree);
1883 }
1884 
gnt_tree_set_column_resizable(GntTree * tree,int col,gboolean res)1885 void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res)
1886 {
1887 	g_return_if_fail(col < tree->ncol);
1888 	set_column_flag(tree, col, GNT_TREE_COLUMN_FIXED_SIZE, !res);
1889 }
1890 
gnt_tree_set_column_is_binary(GntTree * tree,int col,gboolean bin)1891 void gnt_tree_set_column_is_binary(GntTree *tree, int col, gboolean bin)
1892 {
1893 	g_return_if_fail(col < tree->ncol);
1894 	set_column_flag(tree, col, GNT_TREE_COLUMN_BINARY_DATA, bin);
1895 }
1896 
gnt_tree_set_column_is_right_aligned(GntTree * tree,int col,gboolean right)1897 void gnt_tree_set_column_is_right_aligned(GntTree *tree, int col, gboolean right)
1898 {
1899 	g_return_if_fail(col < tree->ncol);
1900 	set_column_flag(tree, col, GNT_TREE_COLUMN_RIGHT_ALIGNED, right);
1901 }
1902 
gnt_tree_set_column_width_ratio(GntTree * tree,int cols[])1903 void gnt_tree_set_column_width_ratio(GntTree *tree, int cols[])
1904 {
1905 	int i;
1906 	for (i = 0; i < tree->ncol && cols[i]; i++) {
1907 		tree->columns[i].width_ratio = cols[i];
1908 	}
1909 }
1910 
gnt_tree_set_search_column(GntTree * tree,int col)1911 void gnt_tree_set_search_column(GntTree *tree, int col)
1912 {
1913 	g_return_if_fail(col < tree->ncol);
1914 	g_return_if_fail(!BINARY_DATA(tree, col));
1915 	tree->priv->search_column = col;
1916 }
1917 
gnt_tree_is_searching(GntTree * tree)1918 gboolean gnt_tree_is_searching(GntTree *tree)
1919 {
1920 	return (tree->priv->search != NULL);
1921 }
1922 
gnt_tree_set_search_function(GntTree * tree,gboolean (* func)(GntTree * tree,gpointer key,const char * search,const char * current))1923 void gnt_tree_set_search_function(GntTree *tree,
1924 		gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current))
1925 {
1926 	tree->priv->search_func = func;
1927 }
1928 
gnt_tree_get_parent_key(GntTree * tree,gpointer key)1929 gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key)
1930 {
1931 	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1932 	return (row && row->parent) ? row->parent->key : NULL;
1933 }
1934 
gnt_tree_row_get_key(GntTree * tree,GntTreeRow * row)1935 gpointer gnt_tree_row_get_key(GntTree *tree, GntTreeRow *row)
1936 {
1937 	g_return_val_if_fail(row && row->tree == tree, NULL);
1938 	return row->key;
1939 }
1940 
gnt_tree_row_get_next(GntTree * tree,GntTreeRow * row)1941 GntTreeRow * gnt_tree_row_get_next(GntTree *tree, GntTreeRow *row)
1942 {
1943 	g_return_val_if_fail(row && row->tree == tree, NULL);
1944 	return row->next;
1945 }
1946 
gnt_tree_row_get_prev(GntTree * tree,GntTreeRow * row)1947 GntTreeRow * gnt_tree_row_get_prev(GntTree *tree, GntTreeRow *row)
1948 {
1949 	g_return_val_if_fail(row && row->tree == tree, NULL);
1950 	return row->prev;
1951 }
1952 
gnt_tree_row_get_child(GntTree * tree,GntTreeRow * row)1953 GntTreeRow * gnt_tree_row_get_child(GntTree *tree, GntTreeRow *row)
1954 {
1955 	g_return_val_if_fail(row && row->tree == tree, NULL);
1956 	return row->child;
1957 }
1958 
gnt_tree_row_get_parent(GntTree * tree,GntTreeRow * row)1959 GntTreeRow * gnt_tree_row_get_parent(GntTree *tree, GntTreeRow *row)
1960 {
1961 	g_return_val_if_fail(row && row->tree == tree, NULL);
1962 	return row->parent;
1963 }
1964 
1965