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