finch/libgnt/gnttree.c

changeset 39360
e7bed293aad5
parent 39302
64aabebb476b
child 39361
a1068caa3600
equal deleted inserted replaced
39302:64aabebb476b 39360:e7bed293aad5
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 "gntstyle.h"
25 #include "gnttree.h"
26 #include "gntutils.h"
27
28 #include <string.h>
29 #include <ctype.h>
30
31 #define SEARCH_TIMEOUT_S 4 /* 4 secs */
32 #define SEARCHING(tree) (tree->priv->search && tree->priv->search->len > 0)
33
34 #define COLUMN_INVISIBLE(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_INVISIBLE)
35 #define BINARY_DATA(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_BINARY_DATA)
36 #define RIGHT_ALIGNED(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_RIGHT_ALIGNED)
37
38 enum
39 {
40 PROP_0,
41 PROP_COLUMNS,
42 PROP_EXPANDER,
43 };
44
45 enum
46 {
47 SIG_SELECTION_CHANGED,
48 SIG_SCROLLED,
49 SIG_TOGGLED,
50 SIG_COLLAPSED,
51 SIGS,
52 };
53
54 struct _GntTreePriv
55 {
56 GString *search;
57 int search_timeout;
58 int search_column;
59 gboolean (*search_func)(GntTree *tree, gpointer key, const char *search, const char *current);
60
61 GCompareFunc compare;
62 int lastvisible;
63 int expander_level;
64 };
65
66 #define TAB_SIZE 3
67
68 /* XXX: Make this one into a GObject?
69 * ... Probably not */
70 struct _GntTreeRow
71 {
72 int box_count;
73
74 void *key;
75 void *data; /* XXX: unused */
76
77 gboolean collapsed;
78 gboolean choice; /* Is this a choice-box?
79 If choice is true, then child will be NULL */
80 gboolean isselected;
81 GntTextFormatFlags flags;
82 int color;
83
84 GntTreeRow *parent;
85 GntTreeRow *child;
86 GntTreeRow *next;
87 GntTreeRow *prev;
88
89 GList *columns;
90 GntTree *tree;
91 };
92
93 struct _GntTreeCol
94 {
95 char *text;
96 gboolean isbinary;
97 int span; /* How many columns does it span? */
98 };
99
100 static void tree_selection_changed(GntTree *, GntTreeRow *, GntTreeRow *);
101 static void _gnt_tree_init_internals(GntTree *tree, int col);
102
103 static GntWidgetClass *parent_class = NULL;
104 static guint signals[SIGS] = { 0 };
105
106 static void
107 readjust_columns(GntTree *tree)
108 {
109 int i, col, total;
110 int width;
111 #define WIDTH(i) (tree->columns[i].width_ratio ? tree->columns[i].width_ratio : tree->columns[i].width)
112 gnt_widget_get_size(GNT_WIDGET(tree), &width, NULL);
113 if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER))
114 width -= 2;
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 *
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 *
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
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 *
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 *
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 *
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 *
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 *
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 *
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
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
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
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 *
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
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
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_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED))
428 return;
429
430 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
431 pos = 0;
432 else
433 pos = 1;
434
435 if (tree->top == NULL)
436 tree->top = tree->root;
437 if (tree->current == NULL && tree->root != NULL) {
438 tree->current = tree->root;
439 tree_selection_changed(tree, NULL, tree->current);
440 }
441
442 wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL));
443
444 start = 0;
445 if (tree->show_title)
446 {
447 int i;
448 int x = pos;
449
450 mvwhline(widget->window, pos + 1, pos, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL),
451 widget->priv.width - pos - 1);
452 mvwhline(widget->window, pos, pos, ' ' | gnt_color_pair(GNT_COLOR_NORMAL),
453 widget->priv.width - pos - 1);
454
455 for (i = 0; i < tree->ncol; i++)
456 {
457 if (COLUMN_INVISIBLE(tree, i)) {
458 continue;
459 }
460 mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width);
461 NEXT_X;
462 }
463 if (pos)
464 {
465 tree_mark_columns(tree, pos, 0,
466 (tree->show_separator ? ACS_TTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
467 tree_mark_columns(tree, pos, widget->priv.height - pos,
468 (tree->show_separator ? ACS_BTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
469 }
470 tree_mark_columns(tree, pos, pos + 1,
471 (tree->show_separator ? ACS_PLUS : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL));
472 tree_mark_columns(tree, pos, pos,
473 (tree->show_separator ? ACS_VLINE : ' ') | gnt_color_pair(GNT_COLOR_NORMAL));
474 start = 2;
475 }
476
477 rows = widget->priv.height - pos * 2 - start - 1;
478 tree->bottom = get_next_n_opt(tree->top, rows, &down);
479 if (down < rows)
480 {
481 tree->top = get_prev_n(tree->bottom, rows);
482 if (tree->top == NULL)
483 tree->top = tree->root;
484 }
485
486 up = get_distance(tree->top, tree->current);
487 if (up < 0)
488 tree->top = tree->current;
489 else if (up >= widget->priv.height - pos)
490 tree->top = get_prev_n(tree->current, rows);
491
492 if (tree->top && !row_matches_search(tree->top))
493 tree->top = get_next(tree->top);
494 row = tree->top;
495 scrcol = widget->priv.width - 1 - 2 * pos; /* exclude the borders and the scrollbar */
496
497 if (tree->current && !row_matches_search(tree->current)) {
498 GntTreeRow *old = tree->current;
499 tree->current = tree->top;
500 tree_selection_changed(tree, old, tree->current);
501 }
502
503 for (i = start + pos; row && i < widget->priv.height - pos;
504 i++, row = get_next(row))
505 {
506 char *str;
507 int wr;
508
509 GntTextFormatFlags flags = row->flags;
510 int attr = 0;
511
512 if (!row_matches_search(row))
513 continue;
514 str = update_row_text(tree, row);
515
516 if ((wr = gnt_util_onscreen_width(str, NULL)) > scrcol)
517 {
518 char *s = (char*)gnt_util_onscreen_width_to_pointer(str, scrcol, &wr);
519 *s = '\0';
520 }
521
522 if (flags & GNT_TEXT_FLAG_BOLD)
523 attr |= A_BOLD;
524 if (flags & GNT_TEXT_FLAG_UNDERLINE)
525 attr |= A_UNDERLINE;
526 if (flags & GNT_TEXT_FLAG_BLINK)
527 attr |= A_BLINK;
528
529 if (row == tree->current)
530 {
531 current = i;
532 attr |= A_BOLD;
533 if (gnt_widget_has_focus(widget))
534 attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT);
535 else
536 attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT_D);
537 }
538 else
539 {
540 if (flags & GNT_TEXT_FLAG_DIM)
541 if (row->color)
542 attr |= (A_DIM | gnt_color_pair(row->color));
543 else
544 attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
545 else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
546 attr |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
547 else if (row->color)
548 attr |= gnt_color_pair(row->color);
549 else
550 attr |= gnt_color_pair(GNT_COLOR_NORMAL);
551 }
552
553 wbkgdset(widget->window, '\0' | attr);
554 mvwaddstr(widget->window, i, pos, C_(str));
555 whline(widget->window, ' ', scrcol - wr);
556 tree->bottom = row;
557 g_free(str);
558 tree_mark_columns(tree, pos, i,
559 (tree->show_separator ? ACS_VLINE : ' ') | attr);
560 }
561
562 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL));
563 while (i < widget->priv.height - pos)
564 {
565 mvwhline(widget->window, i, pos, ' ',
566 widget->priv.width - pos * 2 - 1);
567 tree_mark_columns(tree, pos, i,
568 (tree->show_separator ? ACS_VLINE : ' '));
569 i++;
570 }
571
572 scrcol = widget->priv.width - pos - 1; /* position of the scrollbar */
573 rows--;
574 if (rows > 0)
575 {
576 int total = 0;
577 int showing, position;
578
579 get_next_n_opt(tree->root, g_list_length(tree->list), &total);
580 showing = rows * rows / MAX(total, 1) + 1;
581 showing = MIN(rows, showing);
582
583 total -= rows;
584 up = get_distance(tree->root, tree->top);
585 down = total - up;
586
587 position = (rows - showing) * up / MAX(1, up + down);
588 position = MAX((tree->top != tree->root), position);
589
590 if (showing + position > rows)
591 position = rows - showing;
592
593 if (showing + position == rows && row)
594 position = MAX(0, rows - 1 - showing);
595 else if (showing + position < rows && !row)
596 position = rows - showing;
597
598 position += pos + start + 1;
599
600 mvwvline(widget->window, pos + start + 1, scrcol,
601 ' ' | gnt_color_pair(GNT_COLOR_NORMAL), rows);
602 mvwvline(widget->window, position, scrcol,
603 ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing);
604 }
605
606 mvwaddch(widget->window, start + pos, scrcol,
607 ((tree->top != tree->root) ? ACS_UARROW : ' ') |
608 gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
609
610 mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol,
611 (row ? ACS_DARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
612
613 /* If there's a search-text, show it in the bottom of the tree */
614 if (tree->priv->search && tree->priv->search->len > 0) {
615 const char *str = gnt_util_onscreen_width_to_pointer(tree->priv->search->str, scrcol - 1, NULL);
616 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
617 mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos,
618 tree->priv->search->str, str - tree->priv->search->str);
619 }
620 wmove(widget->window, current, pos);
621
622 gnt_widget_queue_update(widget);
623 }
624
625 static void
626 gnt_tree_draw(GntWidget *widget)
627 {
628 GntTree *tree = GNT_TREE(widget);
629
630 redraw_tree(tree);
631
632 GNTDEBUG;
633 }
634
635 static void
636 gnt_tree_size_request(GntWidget *widget)
637 {
638 if (widget->priv.height == 0)
639 widget->priv.height = 10; /* XXX: Why?! */
640 if (widget->priv.width == 0)
641 {
642 GntTree *tree = GNT_TREE(widget);
643 int i, width = 0;
644 width = 1 + 2 * (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER));
645 for (i = 0; i < tree->ncol; i++)
646 if (!COLUMN_INVISIBLE(tree, i)) {
647 width = width + tree->columns[i].width;
648 if (tree->priv->lastvisible != i)
649 width++;
650 }
651 widget->priv.width = width;
652 }
653 }
654
655 static void
656 gnt_tree_map(GntWidget *widget)
657 {
658 GntTree *tree = GNT_TREE(widget);
659 if (widget->priv.width == 0 || widget->priv.height == 0)
660 {
661 gnt_widget_size_request(widget);
662 }
663 tree->top = tree->root;
664 tree->current = tree->root;
665 GNTDEBUG;
666 }
667
668 static void
669 tree_selection_changed(GntTree *tree, GntTreeRow *old, GntTreeRow *current)
670 {
671 g_signal_emit(tree, signals[SIG_SELECTION_CHANGED], 0, old ? old->key : NULL,
672 current ? current->key : NULL);
673 }
674
675 static gboolean
676 action_down(GntBindable *bind, GList *null)
677 {
678 int dist;
679 GntTree *tree = GNT_TREE(bind);
680 GntTreeRow *old = tree->current;
681 GntTreeRow *row = get_next(tree->current);
682 if (row == NULL)
683 return FALSE;
684 tree->current = row;
685 if ((dist = get_distance(tree->current, tree->bottom)) < 0)
686 gnt_tree_scroll(tree, -dist);
687 else
688 redraw_tree(tree);
689 if (old != tree->current)
690 tree_selection_changed(tree, old, tree->current);
691 return TRUE;
692 }
693
694 static gboolean
695 action_move_parent(GntBindable *bind, GList *null)
696 {
697 GntTree *tree = GNT_TREE(bind);
698 GntTreeRow *row = tree->current;
699 int dist;
700
701 if (!row || !row->parent || SEARCHING(tree))
702 return FALSE;
703
704 tree->current = row->parent;
705 if ((dist = get_distance(tree->current, tree->top)) > 0)
706 gnt_tree_scroll(tree, -dist);
707 else
708 redraw_tree(tree);
709 tree_selection_changed(tree, row, tree->current);
710 return TRUE;
711 }
712
713 static gboolean
714 action_up(GntBindable *bind, GList *list)
715 {
716 int dist;
717 GntTree *tree = GNT_TREE(bind);
718 GntTreeRow *old = tree->current;
719 GntTreeRow *row = get_prev(tree->current);
720 if (!row)
721 return FALSE;
722 tree->current = row;
723 if ((dist = get_distance(tree->current, tree->top)) > 0)
724 gnt_tree_scroll(tree, -dist);
725 else
726 redraw_tree(tree);
727 if (old != tree->current)
728 tree_selection_changed(tree, old, tree->current);
729
730 return TRUE;
731 }
732
733 static gboolean
734 action_page_down(GntBindable *bind, GList *null)
735 {
736 GntTree *tree = GNT_TREE(bind);
737 GntTreeRow *old = tree->current;
738 GntTreeRow *row = get_next(tree->bottom);
739 if (row)
740 {
741 int dist = get_distance(tree->top, tree->current);
742 tree->top = tree->bottom;
743 tree->current = get_next_n_opt(tree->top, dist, NULL);
744 redraw_tree(tree);
745 }
746 else if (tree->current != tree->bottom)
747 {
748 tree->current = tree->bottom;
749 redraw_tree(tree);
750 }
751
752 if (old != tree->current)
753 tree_selection_changed(tree, old, tree->current);
754 return TRUE;
755 }
756
757 static gboolean
758 action_page_up(GntBindable *bind, GList *null)
759 {
760 GntWidget *widget = GNT_WIDGET(bind);
761 GntTree *tree = GNT_TREE(bind);
762 GntTreeRow *row;
763 GntTreeRow *old = tree->current;
764
765 if (tree->top != tree->root)
766 {
767 int dist = get_distance(tree->top, tree->current);
768 row = get_prev_n(tree->top, widget->priv.height - 1 -
769 tree->show_title * 2 - 2 * (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) == 0));
770 if (row == NULL)
771 row = tree->root;
772 tree->top = row;
773 tree->current = get_next_n_opt(tree->top, dist, NULL);
774 redraw_tree(tree);
775 }
776 else if (tree->current != tree->top)
777 {
778 tree->current = tree->top;
779 redraw_tree(tree);
780 }
781 if (old != tree->current)
782 tree_selection_changed(tree, old, tree->current);
783 return TRUE;
784 }
785
786 static void
787 end_search(GntTree *tree)
788 {
789 if (tree->priv->search) {
790 g_source_remove(tree->priv->search_timeout);
791 g_string_free(tree->priv->search, TRUE);
792 tree->priv->search = NULL;
793 tree->priv->search_timeout = 0;
794 GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
795 }
796 }
797
798 static gboolean
799 search_timeout(gpointer data)
800 {
801 GntTree *tree = data;
802
803 end_search(tree);
804 redraw_tree(tree);
805
806 return FALSE;
807 }
808
809 static gboolean
810 gnt_tree_key_pressed(GntWidget *widget, const char *text)
811 {
812 GntTree *tree = GNT_TREE(widget);
813 GntTreeRow *old = tree->current;
814
815 if (text[0] == '\r' || text[0] == '\n') {
816 end_search(tree);
817 gnt_widget_activate(widget);
818 } else if (tree->priv->search) {
819 gboolean changed = TRUE;
820 if (g_unichar_isprint(*text)) {
821 tree->priv->search = g_string_append_c(tree->priv->search, *text);
822 } else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) {
823 if (tree->priv->search->len)
824 tree->priv->search->str[--tree->priv->search->len] = '\0';
825 } else
826 changed = FALSE;
827 if (changed) {
828 redraw_tree(tree);
829 } else {
830 gnt_bindable_perform_action_key(GNT_BINDABLE(tree), text);
831 }
832 g_source_remove(tree->priv->search_timeout);
833 tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree);
834 return TRUE;
835 } else if (text[0] == ' ' && text[1] == 0) {
836 /* Space pressed */
837 GntTreeRow *row = tree->current;
838 if (row && row->child)
839 {
840 row->collapsed = !row->collapsed;
841 redraw_tree(tree);
842 g_signal_emit(tree, signals[SIG_COLLAPSED], 0, row->key, row->collapsed);
843 }
844 else if (row && row->choice)
845 {
846 row->isselected = !row->isselected;
847 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
848 redraw_tree(tree);
849 }
850 } else {
851 return FALSE;
852 }
853
854 if (old != tree->current)
855 {
856 tree_selection_changed(tree, old, tree->current);
857 }
858
859 return TRUE;
860 }
861
862 static void
863 gnt_tree_free_columns(GntTree *tree)
864 {
865 int i;
866 for (i = 0; i < tree->ncol; i++) {
867 g_free(tree->columns[i].title);
868 }
869 g_free(tree->columns);
870 }
871
872 static void
873 gnt_tree_destroy(GntWidget *widget)
874 {
875 GntTree *tree = GNT_TREE(widget);
876
877 end_search(tree);
878 if (tree->hash)
879 g_hash_table_destroy(tree->hash);
880 g_list_free(tree->list);
881 gnt_tree_free_columns(tree);
882 g_free(tree->priv);
883 }
884
885 static gboolean
886 gnt_tree_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
887 {
888 GntTree *tree = GNT_TREE(widget);
889 GntTreeRow *old = tree->current;
890 if (event == GNT_MOUSE_SCROLL_UP) {
891 action_up(GNT_BINDABLE(widget), NULL);
892 } else if (event == GNT_MOUSE_SCROLL_DOWN) {
893 action_down(GNT_BINDABLE(widget), NULL);
894 } else if (event == GNT_LEFT_MOUSE_DOWN) {
895 GntTreeRow *row;
896 GntTree *tree = GNT_TREE(widget);
897 int pos = 1;
898 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
899 pos = 0;
900 if (tree->show_title)
901 pos += 2;
902 pos = y - widget->priv.y - pos;
903 row = get_next_n(tree->top, pos);
904 if (row && tree->current != row) {
905 GntTreeRow *old = tree->current;
906 tree->current = row;
907 redraw_tree(tree);
908 tree_selection_changed(tree, old, tree->current);
909 } else if (row && row == tree->current) {
910 if (row->choice) {
911 row->isselected = !row->isselected;
912 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
913 redraw_tree(tree);
914 } else {
915 gnt_widget_activate(widget);
916 }
917 }
918 } else {
919 return FALSE;
920 }
921 if (old != tree->current) {
922 tree_selection_changed(tree, old, tree->current);
923 }
924 return TRUE;
925 }
926
927 static void
928 gnt_tree_size_changed(GntWidget *widget, int w, int h)
929 {
930 GntTree *tree = GNT_TREE(widget);
931 if (widget->priv.width <= 0)
932 return;
933
934 readjust_columns(tree);
935 }
936
937 static gboolean
938 start_search(GntBindable *bindable, GList *list)
939 {
940 GntTree *tree = GNT_TREE(bindable);
941 if (tree->priv->search)
942 return FALSE;
943 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
944 tree->priv->search = g_string_new(NULL);
945 tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree);
946 return TRUE;
947 }
948
949 static gboolean
950 end_search_action(GntBindable *bindable, GList *list)
951 {
952 GntTree *tree = GNT_TREE(bindable);
953 if (tree->priv->search == NULL)
954 return FALSE;
955 GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
956 end_search(tree);
957 redraw_tree(tree);
958 return TRUE;
959 }
960
961 static gboolean
962 move_first_action(GntBindable *bind, GList *null)
963 {
964 GntTree *tree = GNT_TREE(bind);
965 GntTreeRow *row = tree->root;
966 GntTreeRow *old = tree->current;
967 if (row && !row_matches_search(row))
968 row = get_next(row);
969 if (row) {
970 tree->current = row;
971 redraw_tree(tree);
972 if (old != tree->current)
973 tree_selection_changed(tree, old, tree->current);
974 }
975
976 return TRUE;
977 }
978
979 static gboolean
980 move_last_action(GntBindable *bind, GList *null)
981 {
982 GntTree *tree = GNT_TREE(bind);
983 GntTreeRow *old = tree->current;
984 GntTreeRow *row = tree->bottom;
985 GntTreeRow *next;
986
987 while ((next = get_next(row)))
988 row = next;
989
990 if (row) {
991 tree->current = row;
992 redraw_tree(tree);
993 if (old != tree->current)
994 tree_selection_changed(tree, old, tree->current);
995 }
996
997 return TRUE;
998 }
999
1000 static void
1001 gnt_tree_set_property(GObject *obj, guint prop_id, const GValue *value,
1002 GParamSpec *spec)
1003 {
1004 GntTree *tree = GNT_TREE(obj);
1005 switch (prop_id) {
1006 case PROP_COLUMNS:
1007 _gnt_tree_init_internals(tree, g_value_get_int(value));
1008 break;
1009 case PROP_EXPANDER:
1010 if (tree->priv->expander_level == g_value_get_int(value))
1011 break;
1012 tree->priv->expander_level = g_value_get_int(value);
1013 default:
1014 break;
1015 }
1016 }
1017
1018 static void
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
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_STRINGS
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_STRINGS
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, NULL,
1075 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
1076 signals[SIG_SCROLLED] =
1077 g_signal_new("scrolled",
1078 G_TYPE_FROM_CLASS(klass),
1079 G_SIGNAL_RUN_LAST,
1080 0,
1081 NULL, NULL, NULL,
1082 G_TYPE_NONE, 1, G_TYPE_INT);
1083 signals[SIG_TOGGLED] =
1084 g_signal_new("toggled",
1085 G_TYPE_FROM_CLASS(klass),
1086 G_SIGNAL_RUN_LAST,
1087 G_STRUCT_OFFSET(GntTreeClass, toggled),
1088 NULL, NULL, NULL,
1089 G_TYPE_NONE, 1, G_TYPE_POINTER);
1090 signals[SIG_COLLAPSED] =
1091 g_signal_new("collapse-toggled",
1092 G_TYPE_FROM_CLASS(klass),
1093 G_SIGNAL_RUN_LAST,
1094 0,
1095 NULL, NULL, NULL,
1096 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
1097
1098 gnt_bindable_class_register_action(bindable, "move-up", action_up,
1099 GNT_KEY_UP, NULL);
1100 gnt_bindable_register_binding(bindable, "move-up", GNT_KEY_CTRL_P, NULL);
1101 gnt_bindable_class_register_action(bindable, "move-down", action_down,
1102 GNT_KEY_DOWN, NULL);
1103 gnt_bindable_register_binding(bindable, "move-down", GNT_KEY_CTRL_N, NULL);
1104 gnt_bindable_class_register_action(bindable, "move-parent", action_move_parent,
1105 GNT_KEY_BACKSPACE, NULL);
1106 gnt_bindable_class_register_action(bindable, "page-up", action_page_up,
1107 GNT_KEY_PGUP, NULL);
1108 gnt_bindable_class_register_action(bindable, "page-down", action_page_down,
1109 GNT_KEY_PGDOWN, NULL);
1110 gnt_bindable_class_register_action(bindable, "start-search", start_search,
1111 "/", NULL);
1112 gnt_bindable_class_register_action(bindable, "end-search", end_search_action,
1113 "\033", NULL);
1114 gnt_bindable_class_register_action(bindable, "move-first", move_first_action,
1115 GNT_KEY_HOME, NULL);
1116 gnt_bindable_class_register_action(bindable, "move-last", move_last_action,
1117 GNT_KEY_END, NULL);
1118
1119 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), bindable);
1120 GNTDEBUG;
1121 }
1122
1123 static void
1124 gnt_tree_init(GTypeInstance *instance, gpointer class)
1125 {
1126 GntWidget *widget = GNT_WIDGET(instance);
1127 GntTree *tree = GNT_TREE(widget);
1128 tree->show_separator = TRUE;
1129 tree->priv = g_new0(GntTreePriv, 1);
1130 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y |
1131 GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW);
1132 gnt_widget_set_take_focus(widget, TRUE);
1133 widget->priv.minw = 4;
1134 widget->priv.minh = 1;
1135 GNTDEBUG;
1136 }
1137
1138 /******************************************************************************
1139 * GntTree API
1140 *****************************************************************************/
1141 GType
1142 gnt_tree_get_type(void)
1143 {
1144 static GType type = 0;
1145
1146 if(type == 0)
1147 {
1148 static const GTypeInfo info = {
1149 sizeof(GntTreeClass),
1150 NULL, /* base_init */
1151 NULL, /* base_finalize */
1152 (GClassInitFunc)gnt_tree_class_init,
1153 NULL, /* class_finalize */
1154 NULL, /* class_data */
1155 sizeof(GntTree),
1156 0, /* n_preallocs */
1157 gnt_tree_init, /* instance_init */
1158 NULL /* value_table */
1159 };
1160
1161 type = g_type_register_static(GNT_TYPE_WIDGET,
1162 "GntTree",
1163 &info, 0);
1164 }
1165
1166 return type;
1167 }
1168
1169 static void
1170 free_tree_col(gpointer data)
1171 {
1172 GntTreeCol *col = data;
1173 if (!col->isbinary)
1174 g_free(col->text);
1175 g_free(col);
1176 }
1177
1178 static void
1179 free_tree_row(gpointer data)
1180 {
1181 GntTreeRow *row = data;
1182
1183 if (!row)
1184 return;
1185
1186 g_list_foreach(row->columns, (GFunc)free_tree_col, NULL);
1187 g_list_free(row->columns);
1188 g_free(row);
1189 }
1190
1191 GntWidget *gnt_tree_new()
1192 {
1193 return gnt_tree_new_with_columns(1);
1194 }
1195
1196 void gnt_tree_set_visible_rows(GntTree *tree, int rows)
1197 {
1198 GntWidget *widget = GNT_WIDGET(tree);
1199 widget->priv.height = rows;
1200 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
1201 widget->priv.height += 2;
1202 }
1203
1204 int gnt_tree_get_visible_rows(GntTree *tree)
1205 {
1206 GntWidget *widget = GNT_WIDGET(tree);
1207 int ret = widget->priv.height;
1208 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
1209 ret -= 2;
1210 return ret;
1211 }
1212
1213 GList *gnt_tree_get_rows(GntTree *tree)
1214 {
1215 return tree->list;
1216 }
1217
1218 void gnt_tree_scroll(GntTree *tree, int count)
1219 {
1220 GntTreeRow *row;
1221
1222 if (count < 0)
1223 {
1224 if (get_root_distance(tree->top) == 0)
1225 return;
1226 row = get_prev_n(tree->top, -count);
1227 if (row == NULL)
1228 row = tree->root;
1229 tree->top = row;
1230 }
1231 else
1232 {
1233 get_next_n_opt(tree->bottom, count, &count);
1234 tree->top = get_next_n(tree->top, count);
1235 }
1236
1237 redraw_tree(tree);
1238 g_signal_emit(tree, signals[SIG_SCROLLED], 0, count);
1239 }
1240
1241 static gpointer
1242 find_position(GntTree *tree, gpointer key, gpointer parent)
1243 {
1244 GntTreeRow *row;
1245
1246 if (tree->priv->compare == NULL)
1247 return NULL;
1248
1249 if (parent == NULL)
1250 row = tree->root;
1251 else
1252 row = g_hash_table_lookup(tree->hash, parent);
1253
1254 if (!row)
1255 return NULL;
1256
1257 if (parent)
1258 row = row->child;
1259
1260 while (row)
1261 {
1262 if (tree->priv->compare(key, row->key) < 0)
1263 return (row->prev ? row->prev->key : NULL);
1264 if (row->next)
1265 row = row->next;
1266 else
1267 return row->key;
1268 }
1269 return NULL;
1270 }
1271
1272 void gnt_tree_sort_row(GntTree *tree, gpointer key)
1273 {
1274 GntTreeRow *row, *q, *s;
1275 int current, newp;
1276
1277 if (!tree->priv->compare)
1278 return;
1279
1280 row = g_hash_table_lookup(tree->hash, key);
1281 g_return_if_fail(row != NULL);
1282
1283 current = g_list_index(tree->list, key);
1284
1285 if (row->parent)
1286 s = row->parent->child;
1287 else
1288 s = tree->root;
1289
1290 q = NULL;
1291 while (s) {
1292 if (tree->priv->compare(row->key, s->key) < 0)
1293 break;
1294 q = s;
1295 s = s->next;
1296 }
1297
1298 /* Move row between q and s */
1299 if (row == q || row == s)
1300 return;
1301
1302 if (q == NULL) {
1303 /* row becomes the first child of its parent */
1304 row->prev->next = row->next; /* row->prev cannot be NULL at this point */
1305 if (row->next)
1306 row->next->prev = row->prev;
1307 if (row->parent)
1308 row->parent->child = row;
1309 else
1310 tree->root = row;
1311 row->next = s;
1312 g_return_if_fail(s != NULL); /* s cannot be NULL */
1313 s->prev = row;
1314 row->prev = NULL;
1315 newp = g_list_index(tree->list, s) - 1;
1316 } else {
1317 if (row->prev) {
1318 row->prev->next = row->next;
1319 } else {
1320 /* row was the first child of its parent */
1321 if (row->parent)
1322 row->parent->child = row->next;
1323 else
1324 tree->top = row->next;
1325 }
1326
1327 if (row->next)
1328 row->next->prev = row->prev;
1329
1330 q->next = row;
1331 row->prev = q;
1332 if (s)
1333 s->prev = row;
1334 row->next = s;
1335 newp = g_list_index(tree->list, q) + 1;
1336 }
1337 tree->list = g_list_reposition_child(tree->list, current, newp);
1338
1339 redraw_tree(tree);
1340 }
1341
1342 GntTreeRow *gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1343 {
1344 GntTreeRow *pr = NULL;
1345
1346 if (g_hash_table_lookup(tree->hash, key)) {
1347 gnt_tree_remove(tree, key);
1348 }
1349
1350 row->tree = tree;
1351 row->key = key;
1352 row->data = NULL;
1353 g_hash_table_replace(tree->hash, key, row);
1354
1355 if (bigbro == NULL && tree->priv->compare)
1356 {
1357 bigbro = find_position(tree, key, parent);
1358 }
1359
1360 if (tree->root == NULL)
1361 {
1362 tree->root = row;
1363 tree->list = g_list_prepend(tree->list, key);
1364 }
1365 else
1366 {
1367 int position = 0;
1368
1369 if (bigbro)
1370 {
1371 pr = g_hash_table_lookup(tree->hash, bigbro);
1372 if (pr)
1373 {
1374 if (pr->next) pr->next->prev = row;
1375 row->next = pr->next;
1376 row->prev = pr;
1377 pr->next = row;
1378 row->parent = pr->parent;
1379
1380 position = g_list_index(tree->list, bigbro);
1381 }
1382 }
1383
1384 if (pr == NULL && parent)
1385 {
1386 pr = g_hash_table_lookup(tree->hash, parent);
1387 if (pr)
1388 {
1389 if (pr->child) pr->child->prev = row;
1390 row->next = pr->child;
1391 pr->child = row;
1392 row->parent = pr;
1393
1394 position = g_list_index(tree->list, parent);
1395 }
1396 }
1397
1398 if (pr == NULL)
1399 {
1400 GntTreeRow *r = tree->root;
1401 row->next = r;
1402 if (r) r->prev = row;
1403 if (tree->current == tree->root)
1404 tree->current = row;
1405 tree->root = row;
1406 tree->list = g_list_prepend(tree->list, key);
1407 }
1408 else
1409 {
1410 tree->list = g_list_insert(tree->list, key, position + 1);
1411 }
1412 }
1413 redraw_tree(tree);
1414
1415 return row;
1416 }
1417
1418 GntTreeRow *gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent)
1419 {
1420 GntTreeRow *pr = NULL, *br = NULL;
1421
1422 if (parent)
1423 pr = g_hash_table_lookup(tree->hash, parent);
1424
1425 if (pr)
1426 br = pr->child;
1427 else
1428 br = tree->root;
1429
1430 if (br)
1431 {
1432 while (br->next)
1433 br = br->next;
1434 }
1435
1436 return gnt_tree_add_row_after(tree, key, row, parent, br ? br->key : NULL);
1437 }
1438
1439 gpointer gnt_tree_get_selection_data(GntTree *tree)
1440 {
1441 if (tree->current)
1442 return tree->current->key; /* XXX: perhaps we should just get rid of 'data' */
1443 return NULL;
1444 }
1445
1446 char *gnt_tree_get_selection_text(GntTree *tree)
1447 {
1448 if (tree->current)
1449 return update_row_text(tree, tree->current);
1450 return NULL;
1451 }
1452
1453 GList *gnt_tree_get_row_text_list(GntTree *tree, gpointer key)
1454 {
1455 GList *list = NULL, *iter;
1456 GntTreeRow *row = key ? g_hash_table_lookup(tree->hash, key) : tree->current;
1457 int i;
1458
1459 if (!row)
1460 return NULL;
1461
1462 for (i = 0, iter = row->columns; i < tree->ncol && iter;
1463 i++, iter = iter->next)
1464 {
1465 GntTreeCol *col = iter->data;
1466 list = g_list_append(list, BINARY_DATA(tree, i) ? col->text : g_strdup(col->text));
1467 }
1468
1469 return list;
1470 }
1471
1472 GList *gnt_tree_get_selection_text_list(GntTree *tree)
1473 {
1474 return gnt_tree_get_row_text_list(tree, NULL);
1475 }
1476
1477 void gnt_tree_remove(GntTree *tree, gpointer key)
1478 {
1479 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1480 static int depth = 0; /* Only redraw after all child nodes are removed */
1481 if (row)
1482 {
1483 gboolean redraw = FALSE;
1484
1485 if (row->child) {
1486 depth++;
1487 while (row->child) {
1488 gnt_tree_remove(tree, row->child->key);
1489 }
1490 depth--;
1491 }
1492
1493 if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0)
1494 redraw = TRUE;
1495
1496 /* Update root/top/current/bottom if necessary */
1497 if (tree->root == row)
1498 tree->root = get_next(row);
1499 if (tree->top == row)
1500 {
1501 if (tree->top != tree->root)
1502 tree->top = get_prev(row);
1503 else
1504 tree->top = get_next(row);
1505 }
1506 if (tree->current == row)
1507 {
1508 if (tree->current != tree->root)
1509 tree->current = get_prev(row);
1510 else
1511 tree->current = get_next(row);
1512 tree_selection_changed(tree, row, tree->current);
1513 }
1514 if (tree->bottom == row)
1515 {
1516 tree->bottom = get_prev(row);
1517 }
1518
1519 /* Fix the links */
1520 if (row->next)
1521 row->next->prev = row->prev;
1522 if (row->parent && row->parent->child == row)
1523 row->parent->child = row->next;
1524 if (row->prev)
1525 row->prev->next = row->next;
1526
1527 g_hash_table_remove(tree->hash, key);
1528 tree->list = g_list_remove(tree->list, key);
1529
1530 if (redraw && depth == 0)
1531 {
1532 redraw_tree(tree);
1533 }
1534 }
1535 }
1536
1537 static gboolean
1538 return_true(gpointer key, gpointer data, gpointer null)
1539 {
1540 return TRUE;
1541 }
1542
1543 void gnt_tree_remove_all(GntTree *tree)
1544 {
1545 tree->root = NULL;
1546 g_hash_table_foreach_remove(tree->hash, (GHRFunc)return_true, tree);
1547 g_list_free(tree->list);
1548 tree->list = NULL;
1549 tree->current = tree->top = tree->bottom = NULL;
1550 }
1551
1552 int gnt_tree_get_selection_visible_line(GntTree *tree)
1553 {
1554 return get_distance(tree->top, tree->current) +
1555 !!(GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER));
1556 }
1557
1558 void gnt_tree_change_text(GntTree *tree, gpointer key, int colno, const char *text)
1559 {
1560 GntTreeRow *row;
1561 GntTreeCol *col;
1562
1563 g_return_if_fail(colno < tree->ncol);
1564
1565 row = g_hash_table_lookup(tree->hash, key);
1566 if (row)
1567 {
1568 col = g_list_nth_data(row->columns, colno);
1569 if (BINARY_DATA(tree, colno)) {
1570 col->text = (gpointer)text;
1571 } else {
1572 g_free(col->text);
1573 col->text = g_strdup(text ? text : "");
1574 }
1575
1576 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED) &&
1577 get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0)
1578 redraw_tree(tree);
1579 }
1580 }
1581
1582 GntTreeRow *gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1583 {
1584 GntTreeRow *r;
1585 r = g_hash_table_lookup(tree->hash, key);
1586 g_return_val_if_fail(!r || !r->choice, NULL);
1587
1588 if (bigbro == NULL) {
1589 if (tree->priv->compare)
1590 bigbro = find_position(tree, key, parent);
1591 else {
1592 r = g_hash_table_lookup(tree->hash, parent);
1593 if (!r)
1594 r = tree->root;
1595 else
1596 r = r->child;
1597 if (r) {
1598 while (r->next)
1599 r = r->next;
1600 bigbro = r->key;
1601 }
1602 }
1603 }
1604 row = gnt_tree_add_row_after(tree, key, row, parent, bigbro);
1605 row->choice = TRUE;
1606
1607 return row;
1608 }
1609
1610 void gnt_tree_set_choice(GntTree *tree, void *key, gboolean set)
1611 {
1612 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1613
1614 if (!row)
1615 return;
1616 g_return_if_fail(row->choice);
1617
1618 row->isselected = set;
1619 redraw_tree(tree);
1620 }
1621
1622 gboolean gnt_tree_get_choice(GntTree *tree, void *key)
1623 {
1624 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1625
1626 if (!row)
1627 return FALSE;
1628 g_return_val_if_fail(row->choice, FALSE);
1629
1630 return row->isselected;
1631 }
1632
1633 void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags)
1634 {
1635 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1636 if (!row || row->flags == flags)
1637 return;
1638
1639 row->flags = flags;
1640 redraw_tree(tree); /* XXX: It shouldn't be necessary to redraw the whole darned tree */
1641 }
1642
1643 void gnt_tree_set_row_color(GntTree *tree, void *key, int color)
1644 {
1645 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1646 if (!row || row->color == color)
1647 return;
1648
1649 row->color = color;
1650 redraw_tree(tree);
1651 }
1652
1653 void gnt_tree_set_selected(GntTree *tree , void *key)
1654 {
1655 int dist;
1656 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1657 if (!row || row == tree->current)
1658 return;
1659
1660 if (tree->top == NULL)
1661 tree->top = row;
1662 if (tree->bottom == NULL)
1663 tree->bottom = row;
1664
1665 tree->current = row;
1666 if ((dist = get_distance(tree->current, tree->bottom)) < 0)
1667 gnt_tree_scroll(tree, -dist);
1668 else if ((dist = get_distance(tree->current, tree->top)) > 0)
1669 gnt_tree_scroll(tree, -dist);
1670 else
1671 redraw_tree(tree);
1672 tree_selection_changed(tree, row, tree->current);
1673 }
1674
1675 static void _gnt_tree_init_internals(GntTree *tree, int col)
1676 {
1677 gnt_tree_free_columns(tree);
1678
1679 tree->ncol = col;
1680 tree->hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_tree_row);
1681 tree->columns = g_new0(struct _GntTreeColInfo, col);
1682 tree->priv->lastvisible = col - 1;
1683 while (col--)
1684 {
1685 tree->columns[col].width = 15;
1686 }
1687 tree->list = NULL;
1688 tree->show_title = FALSE;
1689 g_object_notify(G_OBJECT(tree), "columns");
1690 }
1691
1692 GntWidget *gnt_tree_new_with_columns(int col)
1693 {
1694 GntWidget *widget = g_object_new(GNT_TYPE_TREE,
1695 "columns", col,
1696 "expander-level", 1,
1697 NULL);
1698
1699 return widget;
1700 }
1701
1702 GntTreeRow *gnt_tree_create_row_from_list(GntTree *tree, GList *list)
1703 {
1704 GList *iter;
1705 int i;
1706 GntTreeRow *row = g_new0(GntTreeRow, 1);
1707
1708 for (i = 0, iter = list; i < tree->ncol && iter; iter = iter->next, i++)
1709 {
1710 GntTreeCol *col = g_new0(GntTreeCol, 1);
1711 col->span = 1;
1712 if (BINARY_DATA(tree, i)) {
1713 col->text = iter->data;
1714 col->isbinary = TRUE;
1715 } else {
1716 col->text = g_strdup(iter->data ? iter->data : "");
1717 col->isbinary = FALSE;
1718 }
1719
1720 row->columns = g_list_append(row->columns, col);
1721 }
1722
1723 return row;
1724 }
1725
1726 GntTreeRow *gnt_tree_create_row(GntTree *tree, ...)
1727 {
1728 int i;
1729 va_list args;
1730 GList *list = NULL;
1731 GntTreeRow *row;
1732
1733 va_start(args, tree);
1734 for (i = 0; i < tree->ncol; i++)
1735 {
1736 list = g_list_append(list, va_arg(args, char *));
1737 }
1738 va_end(args);
1739
1740 row = gnt_tree_create_row_from_list(tree, list);
1741 g_list_free(list);
1742
1743 return row;
1744 }
1745
1746 void gnt_tree_set_col_width(GntTree *tree, int col, int width)
1747 {
1748 g_return_if_fail(col < tree->ncol);
1749
1750 tree->columns[col].width = width;
1751 if (tree->columns[col].width_ratio == 0)
1752 tree->columns[col].width_ratio = width;
1753 }
1754
1755 void gnt_tree_set_column_title(GntTree *tree, int index, const char *title)
1756 {
1757 g_free(tree->columns[index].title);
1758 tree->columns[index].title = g_strdup(title);
1759 }
1760
1761 void gnt_tree_set_column_titles(GntTree *tree, ...)
1762 {
1763 int i;
1764 va_list args;
1765
1766 va_start(args, tree);
1767 for (i = 0; i < tree->ncol; i++)
1768 {
1769 const char *title = va_arg(args, const char *);
1770 tree->columns[i].title = g_strdup(title);
1771 }
1772 va_end(args);
1773 }
1774
1775 void gnt_tree_set_show_title(GntTree *tree, gboolean set)
1776 {
1777 tree->show_title = set;
1778 GNT_WIDGET(tree)->priv.minh = (set ? 6 : 4);
1779 }
1780
1781 void gnt_tree_set_compare_func(GntTree *tree, GCompareFunc func)
1782 {
1783 tree->priv->compare = func;
1784 }
1785
1786 void gnt_tree_set_expanded(GntTree *tree, void *key, gboolean expanded)
1787 {
1788 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1789 if (row) {
1790 row->collapsed = !expanded;
1791 if (GNT_WIDGET(tree)->window)
1792 gnt_widget_draw(GNT_WIDGET(tree));
1793 g_signal_emit(tree, signals[SIG_COLLAPSED], 0, key, row->collapsed);
1794 }
1795 }
1796
1797 void gnt_tree_set_show_separator(GntTree *tree, gboolean set)
1798 {
1799 tree->show_separator = set;
1800 }
1801
1802 void gnt_tree_adjust_columns(GntTree *tree)
1803 {
1804 GntTreeRow *row = tree->root;
1805 int *widths, i, twidth;
1806
1807 widths = g_new0(int, tree->ncol);
1808 while (row) {
1809 GList *iter;
1810 for (i = 0, iter = row->columns; iter; iter = iter->next, i++) {
1811 GntTreeCol *col = iter->data;
1812 int w = gnt_util_onscreen_width(col->text, NULL);
1813 if (i == 0 && row->choice)
1814 w += 4;
1815 if (i == 0) {
1816 w += find_depth(row) * TAB_SIZE;
1817 }
1818 if (widths[i] < w)
1819 widths[i] = w;
1820 }
1821 row = get_next(row);
1822 }
1823
1824 twidth = 1 + 2 * (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER));
1825 for (i = 0; i < tree->ncol; i++) {
1826 if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE)
1827 widths[i] = tree->columns[i].width;
1828 gnt_tree_set_col_width(tree, i, widths[i]);
1829 if (!COLUMN_INVISIBLE(tree, i)) {
1830 twidth = twidth + widths[i];
1831 if (tree->priv->lastvisible != i)
1832 twidth += 1;
1833 }
1834 }
1835 g_free(widths);
1836
1837 gnt_widget_set_size(GNT_WIDGET(tree), twidth, -1);
1838 }
1839
1840 void gnt_tree_set_hash_fns(GntTree *tree, gpointer hash, gpointer eq, gpointer kd)
1841 {
1842 g_hash_table_foreach_remove(tree->hash, return_true, NULL);
1843 g_hash_table_destroy(tree->hash);
1844 tree->hash = g_hash_table_new_full(hash, eq, kd, free_tree_row);
1845 }
1846
1847 static void
1848 set_column_flag(GntTree *tree, int col, GntTreeColumnFlag flag, gboolean set)
1849 {
1850 if (set)
1851 tree->columns[col].flags |= flag;
1852 else
1853 tree->columns[col].flags &= ~flag;
1854 }
1855
1856 void gnt_tree_set_column_visible(GntTree *tree, int col, gboolean vis)
1857 {
1858 g_return_if_fail(col < tree->ncol);
1859 set_column_flag(tree, col, GNT_TREE_COLUMN_INVISIBLE, !vis);
1860 if (vis) {
1861 /* the column is visible */
1862 if (tree->priv->lastvisible < col)
1863 tree->priv->lastvisible = col;
1864 } else {
1865 if (tree->priv->lastvisible == col)
1866 while (tree->priv->lastvisible) {
1867 tree->priv->lastvisible--;
1868 if (!COLUMN_INVISIBLE(tree, tree->priv->lastvisible))
1869 break;
1870 }
1871 }
1872 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED))
1873 readjust_columns(tree);
1874 }
1875
1876 void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res)
1877 {
1878 g_return_if_fail(col < tree->ncol);
1879 set_column_flag(tree, col, GNT_TREE_COLUMN_FIXED_SIZE, !res);
1880 }
1881
1882 void gnt_tree_set_column_is_binary(GntTree *tree, int col, gboolean bin)
1883 {
1884 g_return_if_fail(col < tree->ncol);
1885 set_column_flag(tree, col, GNT_TREE_COLUMN_BINARY_DATA, bin);
1886 }
1887
1888 void gnt_tree_set_column_is_right_aligned(GntTree *tree, int col, gboolean right)
1889 {
1890 g_return_if_fail(col < tree->ncol);
1891 set_column_flag(tree, col, GNT_TREE_COLUMN_RIGHT_ALIGNED, right);
1892 }
1893
1894 void gnt_tree_set_column_width_ratio(GntTree *tree, int cols[])
1895 {
1896 int i;
1897 for (i = 0; i < tree->ncol && cols[i]; i++) {
1898 tree->columns[i].width_ratio = cols[i];
1899 }
1900 }
1901
1902 void gnt_tree_set_search_column(GntTree *tree, int col)
1903 {
1904 g_return_if_fail(col < tree->ncol);
1905 g_return_if_fail(!BINARY_DATA(tree, col));
1906 tree->priv->search_column = col;
1907 }
1908
1909 gboolean gnt_tree_is_searching(GntTree *tree)
1910 {
1911 return (tree->priv->search != NULL);
1912 }
1913
1914 void gnt_tree_set_search_function(GntTree *tree,
1915 gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current))
1916 {
1917 tree->priv->search_func = func;
1918 }
1919
1920 gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key)
1921 {
1922 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1923 return (row && row->parent) ? row->parent->key : NULL;
1924 }
1925
1926 gpointer gnt_tree_row_get_key(GntTree *tree, GntTreeRow *row)
1927 {
1928 g_return_val_if_fail(row && row->tree == tree, NULL);
1929 return row->key;
1930 }
1931
1932 GntTreeRow * gnt_tree_row_get_next(GntTree *tree, GntTreeRow *row)
1933 {
1934 g_return_val_if_fail(row && row->tree == tree, NULL);
1935 return row->next;
1936 }
1937
1938 GntTreeRow * gnt_tree_row_get_prev(GntTree *tree, GntTreeRow *row)
1939 {
1940 g_return_val_if_fail(row && row->tree == tree, NULL);
1941 return row->prev;
1942 }
1943
1944 GntTreeRow * gnt_tree_row_get_child(GntTree *tree, GntTreeRow *row)
1945 {
1946 g_return_val_if_fail(row && row->tree == tree, NULL);
1947 return row->child;
1948 }
1949
1950 GntTreeRow * gnt_tree_row_get_parent(GntTree *tree, GntTreeRow *row)
1951 {
1952 g_return_val_if_fail(row && row->tree == tree, NULL);
1953 return row->parent;
1954 }
1955
1956 /**************************************************************************
1957 * GntTreeRow GBoxed API
1958 **************************************************************************/
1959 static GntTreeRow *
1960 gnt_tree_row_ref(GntTreeRow *row)
1961 {
1962 g_return_val_if_fail(row != NULL, NULL);
1963
1964 row->box_count++;
1965
1966 return row;
1967 }
1968
1969 static void
1970 gnt_tree_row_unref(GntTreeRow *row)
1971 {
1972 g_return_if_fail(row != NULL);
1973 g_return_if_fail(row->box_count >= 0);
1974
1975 if (!row->box_count--)
1976 free_tree_row(row);
1977 }
1978
1979 GType
1980 gnt_tree_row_get_type(void)
1981 {
1982 static GType type = 0;
1983
1984 if (type == 0) {
1985 type = g_boxed_type_register_static("GntTreeRow",
1986 (GBoxedCopyFunc)gnt_tree_row_ref,
1987 (GBoxedFreeFunc)gnt_tree_row_unref);
1988 }
1989
1990 return type;
1991 }

mercurial