comparison finch/libgnt/gnttree.c @ 15818:0e3a8505ebbe

renamed gaim-text to finch
author Sean Egan <seanegan@gmail.com>
date Sun, 18 Mar 2007 19:38:15 +0000
parents
children f00f2e283ffb
comparison
equal deleted inserted replaced
15817:317e7613e581 15818:0e3a8505ebbe
1 #include "gntmarshal.h"
2 #include "gntstyle.h"
3 #include "gnttree.h"
4 #include "gntutils.h"
5
6 #include <string.h>
7 #include <ctype.h>
8
9 #define SEARCH_TIMEOUT 4000 /* 4 secs */
10
11 enum
12 {
13 SIG_SELECTION_CHANGED,
14 SIG_SCROLLED,
15 SIG_TOGGLED,
16 SIGS,
17 };
18
19 #define TAB_SIZE 3
20
21 /* XXX: Make this one into a GObject?
22 * ... Probably not */
23 struct _GnTreeRow
24 {
25 void *key;
26 void *data; /* XXX: unused */
27
28 gboolean collapsed;
29 gboolean choice; /* Is this a choice-box?
30 If choice is true, then child will be NULL */
31 gboolean isselected;
32 GntTextFormatFlags flags;
33
34 GntTreeRow *parent;
35 GntTreeRow *child;
36 GntTreeRow *next;
37 GntTreeRow *prev;
38
39 GList *columns;
40 GntTree *tree;
41 };
42
43 struct _GnTreeCol
44 {
45 char *text;
46 int span; /* How many columns does it span? */
47 };
48
49 static GntWidgetClass *parent_class = NULL;
50 static guint signals[SIGS] = { 0 };
51
52 /* Move the item at position old to position new */
53 static GList *
54 g_list_reposition_child(GList *list, int old, int new)
55 {
56 gpointer item = g_list_nth_data(list, old);
57 list = g_list_remove(list, item);
58 if (old < new)
59 new--; /* because the positions would have shifted after removing the item */
60 list = g_list_insert(list, item, new);
61 return list;
62 }
63
64 static GntTreeRow *
65 _get_next(GntTreeRow *row, gboolean godeep)
66 {
67 if (row == NULL)
68 return NULL;
69 if (godeep && row->child)
70 return row->child;
71 if (row->next)
72 return row->next;
73 return _get_next(row->parent, FALSE);
74 }
75
76 static gboolean
77 row_matches_search(GntTreeRow *row)
78 {
79 GntTree *t = row->tree;
80 if (t->search && t->search->len > 0) {
81 char *one = g_utf8_casefold(((GntTreeCol*)row->columns->data)->text, -1);
82 char *two = g_utf8_casefold(t->search->str, -1);
83 char *z = strstr(one, two);
84 g_free(one);
85 g_free(two);
86 if (z == NULL)
87 return FALSE;
88 }
89 return TRUE;
90 }
91
92 static GntTreeRow *
93 get_next(GntTreeRow *row)
94 {
95 if (row == NULL)
96 return NULL;
97 while ((row = _get_next(row, !row->collapsed)) != NULL) {
98 if (row_matches_search(row))
99 break;
100 }
101 return row;
102 }
103
104 /* Returns the n-th next row. If it doesn't exist, returns NULL */
105 static GntTreeRow *
106 get_next_n(GntTreeRow *row, int n)
107 {
108 while (row && n--)
109 row = get_next(row);
110 return row;
111 }
112
113 /* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */
114 static GntTreeRow *
115 get_next_n_opt(GntTreeRow *row, int n, int *pos)
116 {
117 GntTreeRow *next = row;
118 int r = 0;
119
120 if (row == NULL)
121 return NULL;
122
123 while (row && n--)
124 {
125 row = get_next(row);
126 if (row)
127 {
128 next = row;
129 r++;
130 }
131 }
132
133 if (pos)
134 *pos = r;
135
136 return next;
137 }
138
139 static GntTreeRow *
140 get_last_child(GntTreeRow *row)
141 {
142 if (row == NULL)
143 return NULL;
144 if (!row->collapsed && row->child)
145 row = row->child;
146 else
147 return row;
148
149 while(row->next)
150 row = row->next;
151 if (!row->collapsed && row->child)
152 row = get_last_child(row->child);
153 return row;
154 }
155
156 static GntTreeRow *
157 get_prev(GntTreeRow *row)
158 {
159 if (row == NULL)
160 return NULL;
161 while (row) {
162 if (row->prev)
163 row = get_last_child(row->prev);
164 else
165 row = row->parent;
166 if (!row || row_matches_search(row))
167 break;
168 }
169 return row;
170 }
171
172 static GntTreeRow *
173 get_prev_n(GntTreeRow *row, int n)
174 {
175 while (row && n--)
176 row = get_prev(row);
177 return row;
178 }
179
180 /* Distance of row from the root */
181 /* XXX: This is uber-inefficient */
182 static int
183 get_root_distance(GntTreeRow *row)
184 {
185 if (row == NULL)
186 return -1;
187 return get_root_distance(get_prev(row)) + 1;
188 }
189
190 /* Returns the distance between a and b.
191 * If a is 'above' b, then the distance is positive */
192 static int
193 get_distance(GntTreeRow *a, GntTreeRow *b)
194 {
195 /* First get the distance from a to the root.
196 * Then the distance from b to the root.
197 * Subtract.
198 * It's not that good, but it works. */
199 int ha = get_root_distance(a);
200 int hb = get_root_distance(b);
201
202 return (hb - ha);
203 }
204
205 static int
206 find_depth(GntTreeRow *row)
207 {
208 int dep = -1;
209
210 while (row)
211 {
212 dep++;
213 row = row->parent;
214 }
215
216 return dep;
217 }
218
219 static char *
220 update_row_text(GntTree *tree, GntTreeRow *row)
221 {
222 GString *string = g_string_new(NULL);
223 GList *iter;
224 int i;
225
226 for (i = 0, iter = row->columns; i < tree->ncol && iter; i++, iter = iter->next)
227 {
228 GntTreeCol *col = iter->data;
229 const char *text;
230 int len = gnt_util_onscreen_width(col->text, NULL);
231 int fl = 0;
232 gboolean cut = FALSE;
233
234 if (i == 0)
235 {
236 if (row->choice)
237 {
238 g_string_append_printf(string, "[%c] ",
239 row->isselected ? 'X' : ' ');
240 fl = 4;
241 }
242 else if (row->parent == NULL && row->child)
243 {
244 if (row->collapsed)
245 {
246 string = g_string_append(string, "+ ");
247 }
248 else
249 {
250 string = g_string_append(string, "- ");
251 }
252 fl = 2;
253 }
254 else
255 {
256 fl = TAB_SIZE * find_depth(row);
257 g_string_append_printf(string, "%*s", fl, "");
258 }
259 len += fl;
260 }
261 else
262 g_string_append_c(string, '|');
263
264 if (len > tree->columns[i].width) {
265 len = tree->columns[i].width - 1;
266 cut = TRUE;
267 }
268 text = gnt_util_onscreen_width_to_pointer(col->text, len - fl, NULL);
269 string = g_string_append_len(string, col->text, text - col->text);
270 if (cut) { /* ellipsis */
271 if (gnt_ascii_only())
272 g_string_append_c(string, '~');
273 else
274 string = g_string_append(string, "\342\200\246");
275 len++;
276 }
277
278 if (len < tree->columns[i].width && iter->next)
279 g_string_append_printf(string, "%*s", tree->columns[i].width - len, "");
280 }
281 return g_string_free(string, FALSE);
282 }
283
284 static void
285 tree_mark_columns(GntTree *tree, int pos, int y, chtype type)
286 {
287 GntWidget *widget = GNT_WIDGET(tree);
288 int i;
289 int x = pos;
290
291 for (i = 0; i < tree->ncol - 1; i++)
292 {
293 x += tree->columns[i].width;
294 mvwaddch(widget->window, y, x + i, type);
295 }
296 }
297
298 static void
299 redraw_tree(GntTree *tree)
300 {
301 int start, i;
302 GntWidget *widget = GNT_WIDGET(tree);
303 GntTreeRow *row;
304 int pos, up, down;
305 int rows, scrcol;
306
307 if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED))
308 return;
309
310 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
311 pos = 0;
312 else
313 pos = 1;
314
315 if (tree->top == NULL)
316 tree->top = tree->root;
317 if (tree->current == NULL)
318 tree->current = tree->root;
319
320 wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL));
321
322 start = 0;
323 if (tree->show_title)
324 {
325 int i;
326 int x = pos;
327
328 mvwhline(widget->window, pos + 1, pos, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL),
329 widget->priv.width - pos - 1);
330
331 for (i = 0; i < tree->ncol; i++)
332 {
333 mvwaddstr(widget->window, pos, x + i, tree->columns[i].title);
334 x += tree->columns[i].width;
335 }
336 if (pos)
337 {
338 tree_mark_columns(tree, pos, 0, ACS_TTEE | COLOR_PAIR(GNT_COLOR_NORMAL));
339 tree_mark_columns(tree, pos, widget->priv.height - pos,
340 ACS_BTEE | COLOR_PAIR(GNT_COLOR_NORMAL));
341 }
342 tree_mark_columns(tree, pos, pos + 1,
343 (tree->show_separator ? ACS_PLUS : ACS_HLINE) | COLOR_PAIR(GNT_COLOR_NORMAL));
344 tree_mark_columns(tree, pos, pos,
345 (tree->show_separator ? ACS_VLINE : ' ') | COLOR_PAIR(GNT_COLOR_NORMAL));
346 start = 2;
347 }
348
349 rows = widget->priv.height - pos * 2 - start - 1;
350 tree->bottom = get_next_n_opt(tree->top, rows, &down);
351 if (down < rows)
352 {
353 tree->top = get_prev_n(tree->bottom, rows);
354 if (tree->top == NULL)
355 tree->top = tree->root;
356 }
357
358 up = get_distance(tree->top, tree->current);
359 if (up < 0)
360 tree->top = tree->current;
361 else if (up >= widget->priv.height - pos)
362 tree->top = get_prev_n(tree->current, rows);
363
364 if (tree->top && !row_matches_search(tree->top))
365 tree->top = get_next(tree->top);
366 row = tree->top;
367 scrcol = widget->priv.width - 1 - 2 * pos; /* exclude the borders and the scrollbar */
368 for (i = start + pos; row && i < widget->priv.height - pos;
369 i++, row = get_next(row))
370 {
371 char *str;
372 int wr;
373
374 GntTextFormatFlags flags = row->flags;
375 int attr = 0;
376
377 if (!row_matches_search(row))
378 continue;
379 str = update_row_text(tree, row);
380
381 if ((wr = gnt_util_onscreen_width(str, NULL)) > scrcol)
382 {
383 char *s = (char*)gnt_util_onscreen_width_to_pointer(str, scrcol, &wr);
384 *s = '\0';
385 }
386
387 if (flags & GNT_TEXT_FLAG_BOLD)
388 attr |= A_BOLD;
389 if (flags & GNT_TEXT_FLAG_UNDERLINE)
390 attr |= A_UNDERLINE;
391 if (flags & GNT_TEXT_FLAG_BLINK)
392 attr |= A_BLINK;
393
394 if (row == tree->current)
395 {
396 if (gnt_widget_has_focus(widget))
397 attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT);
398 else
399 attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D);
400 }
401 else
402 {
403 if (flags & GNT_TEXT_FLAG_DIM)
404 attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_DISABLED));
405 else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
406 attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_HIGHLIGHT));
407 else
408 attr |= COLOR_PAIR(GNT_COLOR_NORMAL);
409 }
410
411 wbkgdset(widget->window, '\0' | attr);
412 mvwaddstr(widget->window, i, pos, str);
413 whline(widget->window, ' ', scrcol - wr);
414 tree->bottom = row;
415 g_free(str);
416 tree_mark_columns(tree, pos, i,
417 (tree->show_separator ? ACS_VLINE : ' ') | attr);
418 }
419
420 wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
421 while (i < widget->priv.height - pos)
422 {
423 mvwhline(widget->window, i, pos, ' ',
424 widget->priv.width - pos * 2 - 1);
425 tree_mark_columns(tree, pos, i,
426 (tree->show_separator ? ACS_VLINE : ' '));
427 i++;
428 }
429
430 scrcol = widget->priv.width - pos - 1; /* position of the scrollbar */
431 rows--;
432 if (rows > 0)
433 {
434 int total;
435 int showing, position;
436
437 get_next_n_opt(tree->root, g_list_length(tree->list), &total);
438 showing = rows * rows / MAX(total, 1) + 1;
439 showing = MIN(rows, showing);
440
441 total -= rows;
442 up = get_distance(tree->root, tree->top);
443 down = total - up;
444
445 position = (rows - showing) * up / MAX(1, up + down);
446 position = MAX((tree->top != tree->root), position);
447
448 if (showing + position > rows)
449 position = rows - showing;
450
451 if (showing + position == rows && row)
452 position = MAX(0, rows - 1 - showing);
453 else if (showing + position < rows && !row)
454 position = rows - showing;
455
456 position += pos + start + 1;
457
458 mvwvline(widget->window, pos + start + 1, scrcol,
459 ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), rows);
460 mvwvline(widget->window, position, scrcol,
461 ACS_CKBOARD | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D), showing);
462 }
463
464 mvwaddch(widget->window, start + pos, scrcol,
465 ((tree->top != tree->root) ? ACS_UARROW : ' ') |
466 COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D));
467
468 mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol,
469 (row ? ACS_DARROW : ' ') | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D));
470
471 gnt_widget_queue_update(widget);
472 }
473
474 static void
475 gnt_tree_draw(GntWidget *widget)
476 {
477 GntTree *tree = GNT_TREE(widget);
478
479 redraw_tree(tree);
480
481 GNTDEBUG;
482 }
483
484 static void
485 gnt_tree_size_request(GntWidget *widget)
486 {
487 if (widget->priv.height == 0)
488 widget->priv.height = 10; /* XXX: Why?! */
489 if (widget->priv.width == 0)
490 {
491 GntTree *tree = GNT_TREE(widget);
492 int i, width = 0;
493 for (i = 0; i < tree->ncol; i++)
494 width += tree->columns[i].width;
495 widget->priv.width = width + i;
496 }
497 }
498
499 static void
500 gnt_tree_map(GntWidget *widget)
501 {
502 GntTree *tree = GNT_TREE(widget);
503 if (widget->priv.width == 0 || widget->priv.height == 0)
504 {
505 gnt_widget_size_request(widget);
506 }
507 tree->top = tree->root;
508 tree->current = tree->root;
509 GNTDEBUG;
510 }
511
512 static void
513 tree_selection_changed(GntTree *tree, GntTreeRow *old, GntTreeRow *current)
514 {
515 g_signal_emit(tree, signals[SIG_SELECTION_CHANGED], 0, old ? old->key : NULL,
516 current ? current->key : NULL);
517 }
518
519 static gboolean
520 action_down(GntBindable *bind, GList *null)
521 {
522 int dist;
523 GntTree *tree = GNT_TREE(bind);
524 GntTreeRow *old = tree->current;
525 GntTreeRow *row = get_next(tree->current);
526 if (row == NULL)
527 return FALSE;
528 tree->current = row;
529 if ((dist = get_distance(tree->current, tree->bottom)) < 0)
530 gnt_tree_scroll(tree, -dist);
531 else
532 redraw_tree(tree);
533 if (old != tree->current)
534 tree_selection_changed(tree, old, tree->current);
535 return TRUE;
536 }
537
538 static gboolean
539 action_up(GntBindable *bind, GList *list)
540 {
541 int dist;
542 GntTree *tree = GNT_TREE(bind);
543 GntTreeRow *old = tree->current;
544 GntTreeRow *row = get_prev(tree->current);
545 if (!row)
546 return FALSE;
547 tree->current = row;
548 if ((dist = get_distance(tree->current, tree->top)) > 0)
549 gnt_tree_scroll(tree, -dist);
550 else
551 redraw_tree(tree);
552 if (old != tree->current)
553 tree_selection_changed(tree, old, tree->current);
554
555 return TRUE;
556 }
557
558 static gboolean
559 action_page_down(GntBindable *bind, GList *null)
560 {
561 GntTree *tree = GNT_TREE(bind);
562 GntTreeRow *old = tree->current;
563 GntTreeRow *row = get_next(tree->bottom);
564 if (row)
565 {
566 int dist = get_distance(tree->top, tree->current);
567 tree->top = tree->bottom;
568 tree->current = get_next_n_opt(tree->top, dist, NULL);
569 redraw_tree(tree);
570 }
571 else if (tree->current != tree->bottom)
572 {
573 tree->current = tree->bottom;
574 redraw_tree(tree);
575 }
576
577 if (old != tree->current)
578 tree_selection_changed(tree, old, tree->current);
579 return TRUE;
580 }
581
582 static gboolean
583 action_page_up(GntBindable *bind, GList *null)
584 {
585 GntWidget *widget = GNT_WIDGET(bind);
586 GntTree *tree = GNT_TREE(bind);
587 GntTreeRow *row;
588 GntTreeRow *old = tree->current;
589
590 if (tree->top != tree->root)
591 {
592 int dist = get_distance(tree->top, tree->current);
593 row = get_prev_n(tree->top, widget->priv.height - 1 -
594 tree->show_title * 2 - 2 * (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) == 0));
595 if (row == NULL)
596 row = tree->root;
597 tree->top = row;
598 tree->current = get_next_n_opt(tree->top, dist, NULL);
599 redraw_tree(tree);
600 }
601 else if (tree->current != tree->top)
602 {
603 tree->current = tree->top;
604 redraw_tree(tree);
605 }
606 if (old != tree->current)
607 tree_selection_changed(tree, old, tree->current);
608 return TRUE;
609 }
610
611 static void
612 end_search(GntTree *tree)
613 {
614 if (tree->search) {
615 g_source_remove(tree->search_timeout);
616 g_string_free(tree->search, TRUE);
617 tree->search = NULL;
618 tree->search_timeout = 0;
619 }
620 }
621
622 static gboolean
623 search_timeout(gpointer data)
624 {
625 GntTree *tree = data;
626
627 end_search(tree);
628 redraw_tree(tree);
629
630 return FALSE;
631 }
632
633 static gboolean
634 gnt_tree_key_pressed(GntWidget *widget, const char *text)
635 {
636 GntTree *tree = GNT_TREE(widget);
637 GntTreeRow *old = tree->current;
638
639 if (text[0] == '\r') {
640 end_search(tree);
641 gnt_widget_activate(widget);
642 } else if (tree->search) {
643 if (isalnum(*text)) {
644 tree->search = g_string_append_c(tree->search, *text);
645 redraw_tree(tree);
646 g_source_remove(tree->search_timeout);
647 tree->search_timeout = g_timeout_add(SEARCH_TIMEOUT, search_timeout, tree);
648 }
649 return TRUE;
650 } else if (text[0] == ' ' && text[1] == 0) {
651 /* Space pressed */
652 GntTreeRow *row = tree->current;
653 if (row && row->child)
654 {
655 row->collapsed = !row->collapsed;
656 redraw_tree(tree);
657 }
658 else if (row && row->choice)
659 {
660 row->isselected = !row->isselected;
661 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
662 redraw_tree(tree);
663 }
664 }
665
666 if (old != tree->current)
667 {
668 tree_selection_changed(tree, old, tree->current);
669 return TRUE;
670 }
671
672 return FALSE;
673 }
674
675 static void
676 gnt_tree_destroy(GntWidget *widget)
677 {
678 GntTree *tree = GNT_TREE(widget);
679 int i;
680
681 end_search(tree);
682 if (tree->hash)
683 g_hash_table_destroy(tree->hash);
684 g_list_free(tree->list);
685
686 for (i = 0; i < tree->ncol; i++)
687 {
688 g_free(tree->columns[i].title);
689 }
690 g_free(tree->columns);
691 }
692
693 static gboolean
694 gnt_tree_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
695 {
696 GntTree *tree = GNT_TREE(widget);
697 GntTreeRow *old = tree->current;
698 if (event == GNT_MOUSE_SCROLL_UP) {
699 action_up(GNT_BINDABLE(widget), NULL);
700 } else if (event == GNT_MOUSE_SCROLL_DOWN) {
701 action_down(GNT_BINDABLE(widget), NULL);
702 } else if (event == GNT_LEFT_MOUSE_DOWN) {
703 GntTreeRow *row;
704 GntTree *tree = GNT_TREE(widget);
705 int pos = 1;
706 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
707 pos = 0;
708 if (tree->show_title)
709 pos += 2;
710 pos = y - widget->priv.y - pos;
711 row = get_next_n(tree->top, pos);
712 if (row && tree->current != row) {
713 GntTreeRow *old = tree->current;
714 tree->current = row;
715 redraw_tree(tree);
716 tree_selection_changed(tree, old, tree->current);
717 } else if (row && row == tree->current) {
718 if (row->choice) {
719 row->isselected = !row->isselected;
720 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key);
721 redraw_tree(tree);
722 } else {
723 gnt_widget_activate(widget);
724 }
725 }
726 } else {
727 return FALSE;
728 }
729 if (old != tree->current) {
730 tree_selection_changed(tree, old, tree->current);
731 }
732 return TRUE;
733 }
734
735 static void
736 gnt_tree_size_changed(GntWidget *widget, int w, int h)
737 {
738 GntTree *tree = GNT_TREE(widget);
739 int i;
740 int n = 0;
741 if (widget->priv.width <= 0)
742 return;
743 for (i = 0; i < tree->ncol; ++i)
744 n += tree->columns[i].width;
745 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
746 tree->columns[tree->ncol - 1].width += widget->priv.width - n - 1 * tree->ncol;
747 else
748 tree->columns[tree->ncol - 1].width += widget->priv.width - n - 2 - 1 * tree->ncol;
749 }
750
751 static gboolean
752 start_search(GntBindable *bindable, GList *list)
753 {
754 GntTree *tree = GNT_TREE(bindable);
755 if (tree->search)
756 return FALSE;
757 tree->search = g_string_new(NULL);
758 tree->search_timeout = g_timeout_add(SEARCH_TIMEOUT, search_timeout, tree);
759 return TRUE;
760 }
761
762 static gboolean
763 end_search_action(GntBindable *bindable, GList *list)
764 {
765 GntTree *tree = GNT_TREE(bindable);
766 if (tree->search == NULL)
767 return FALSE;
768 end_search(tree);
769 redraw_tree(tree);
770 return TRUE;
771 }
772
773 static void
774 gnt_tree_class_init(GntTreeClass *klass)
775 {
776 GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
777 parent_class = GNT_WIDGET_CLASS(klass);
778 parent_class->destroy = gnt_tree_destroy;
779 parent_class->draw = gnt_tree_draw;
780 parent_class->map = gnt_tree_map;
781 parent_class->size_request = gnt_tree_size_request;
782 parent_class->key_pressed = gnt_tree_key_pressed;
783 parent_class->clicked = gnt_tree_clicked;
784 parent_class->size_changed = gnt_tree_size_changed;
785
786 signals[SIG_SELECTION_CHANGED] =
787 g_signal_new("selection-changed",
788 G_TYPE_FROM_CLASS(klass),
789 G_SIGNAL_RUN_LAST,
790 G_STRUCT_OFFSET(GntTreeClass, selection_changed),
791 NULL, NULL,
792 gnt_closure_marshal_VOID__POINTER_POINTER,
793 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
794 signals[SIG_SCROLLED] =
795 g_signal_new("scrolled",
796 G_TYPE_FROM_CLASS(klass),
797 G_SIGNAL_RUN_LAST,
798 0,
799 NULL, NULL,
800 g_cclosure_marshal_VOID__INT,
801 G_TYPE_NONE, 1, G_TYPE_INT);
802 signals[SIG_TOGGLED] =
803 g_signal_new("toggled",
804 G_TYPE_FROM_CLASS(klass),
805 G_SIGNAL_RUN_LAST,
806 G_STRUCT_OFFSET(GntTreeClass, toggled),
807 NULL, NULL,
808 g_cclosure_marshal_VOID__POINTER,
809 G_TYPE_NONE, 1, G_TYPE_POINTER);
810
811 gnt_bindable_class_register_action(bindable, "move-up", action_up,
812 GNT_KEY_UP, NULL);
813 gnt_bindable_register_binding(bindable, "move-up", GNT_KEY_CTRL_P, NULL);
814 gnt_bindable_class_register_action(bindable, "move-down", action_down,
815 GNT_KEY_DOWN, NULL);
816 gnt_bindable_register_binding(bindable, "move-down", GNT_KEY_CTRL_N, NULL);
817 gnt_bindable_class_register_action(bindable, "page-up", action_page_up,
818 GNT_KEY_PGUP, NULL);
819 gnt_bindable_class_register_action(bindable, "page-down", action_page_down,
820 GNT_KEY_PGDOWN, NULL);
821 gnt_bindable_class_register_action(bindable, "start-search", start_search,
822 "/", NULL);
823 gnt_bindable_class_register_action(bindable, "end-search", end_search_action,
824 "\033", NULL);
825
826 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), bindable);
827 GNTDEBUG;
828 }
829
830 static void
831 gnt_tree_init(GTypeInstance *instance, gpointer class)
832 {
833 GntWidget *widget = GNT_WIDGET(instance);
834 GntTree *tree = GNT_TREE(widget);
835 tree->show_separator = TRUE;
836 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y);
837 widget->priv.minw = 4;
838 widget->priv.minh = 1;
839 GNTDEBUG;
840 }
841
842 /******************************************************************************
843 * GntTree API
844 *****************************************************************************/
845 GType
846 gnt_tree_get_gtype(void)
847 {
848 static GType type = 0;
849
850 if(type == 0)
851 {
852 static const GTypeInfo info = {
853 sizeof(GntTreeClass),
854 NULL, /* base_init */
855 NULL, /* base_finalize */
856 (GClassInitFunc)gnt_tree_class_init,
857 NULL, /* class_finalize */
858 NULL, /* class_data */
859 sizeof(GntTree),
860 0, /* n_preallocs */
861 gnt_tree_init, /* instance_init */
862 NULL /* value_table */
863 };
864
865 type = g_type_register_static(GNT_TYPE_WIDGET,
866 "GntTree",
867 &info, 0);
868 }
869
870 return type;
871 }
872
873 static void
874 free_tree_col(gpointer data)
875 {
876 GntTreeCol *col = data;
877
878 g_free(col->text);
879 g_free(col);
880 }
881
882 static void
883 free_tree_row(gpointer data)
884 {
885 GntTreeRow *row = data;
886
887 if (!row)
888 return;
889
890 g_list_foreach(row->columns, (GFunc)free_tree_col, NULL);
891 g_list_free(row->columns);
892 g_free(row);
893 }
894
895 GntWidget *gnt_tree_new()
896 {
897 return gnt_tree_new_with_columns(1);
898 }
899
900 void gnt_tree_set_visible_rows(GntTree *tree, int rows)
901 {
902 GntWidget *widget = GNT_WIDGET(tree);
903 widget->priv.height = rows;
904 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
905 widget->priv.height += 2;
906 }
907
908 int gnt_tree_get_visible_rows(GntTree *tree)
909 {
910 GntWidget *widget = GNT_WIDGET(tree);
911 int ret = widget->priv.height;
912 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER))
913 ret -= 2;
914 return ret;
915 }
916
917 const GList *gnt_tree_get_rows(GntTree *tree)
918 {
919 return tree->list;
920 }
921
922 void gnt_tree_scroll(GntTree *tree, int count)
923 {
924 GntTreeRow *row;
925
926 if (count < 0)
927 {
928 if (get_root_distance(tree->top) == 0)
929 return;
930 row = get_prev_n(tree->top, -count);
931 if (row == NULL)
932 row = tree->root;
933 tree->top = row;
934 }
935 else
936 {
937 get_next_n_opt(tree->bottom, count, &count);
938 tree->top = get_next_n(tree->top, count);
939 }
940
941 redraw_tree(tree);
942 g_signal_emit(tree, signals[SIG_SCROLLED], 0, count);
943 }
944
945 static gpointer
946 find_position(GntTree *tree, gpointer key, gpointer parent)
947 {
948 GntTreeRow *row;
949
950 if (tree->compare == NULL)
951 return NULL;
952
953 if (parent == NULL)
954 row = tree->root;
955 else
956 row = g_hash_table_lookup(tree->hash, parent);
957
958 if (!row)
959 return NULL;
960
961 if (parent)
962 row = row->child;
963
964 while (row)
965 {
966 if (tree->compare(key, row->key) < 0)
967 return (row->prev ? row->prev->key : NULL);
968 if (row->next)
969 row = row->next;
970 else
971 return row->key;
972 }
973 return NULL;
974 }
975
976 void gnt_tree_sort_row(GntTree *tree, gpointer key)
977 {
978 GntTreeRow *row, *q, *s;
979 int current, newp;
980
981 if (!tree->compare)
982 return;
983
984 row = g_hash_table_lookup(tree->hash, key);
985 g_return_if_fail(row != NULL);
986
987 current = g_list_index(tree->list, key);
988
989 if (row->parent)
990 s = row->parent->child;
991 else
992 s = tree->root;
993
994 q = NULL;
995 while (s) {
996 if (tree->compare(row->key, s->key) < 0)
997 break;
998 q = s;
999 s = s->next;
1000 }
1001
1002 /* Move row between q and s */
1003 if (row == q || row == s)
1004 return;
1005
1006 if (q == NULL) {
1007 /* row becomes the first child of its parent */
1008 row->prev->next = row->next; /* row->prev cannot be NULL at this point */
1009 if (row->next)
1010 row->next->prev = row->prev;
1011 if (row->parent)
1012 row->parent->child = row;
1013 else
1014 tree->root = row;
1015 row->next = s;
1016 s->prev = row; /* s cannot be NULL */
1017 row->prev = NULL;
1018 newp = g_list_index(tree->list, s) - 1;
1019 } else {
1020 if (row->prev) {
1021 row->prev->next = row->next;
1022 } else {
1023 /* row was the first child of its parent */
1024 if (row->parent)
1025 row->parent->child = row->next;
1026 else
1027 tree->top = row->next;
1028 }
1029
1030 if (row->next)
1031 row->next->prev = row->prev;
1032
1033 q->next = row;
1034 row->prev = q;
1035 if (s)
1036 s->prev = row;
1037 row->next = s;
1038 newp = g_list_index(tree->list, q) + 1;
1039 }
1040 tree->list = g_list_reposition_child(tree->list, current, newp);
1041
1042 redraw_tree(tree);
1043 }
1044
1045 GntTreeRow *gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1046 {
1047 GntTreeRow *pr = NULL;
1048
1049 g_hash_table_replace(tree->hash, key, row);
1050 row->tree = tree;
1051
1052 if (bigbro == NULL && tree->compare)
1053 {
1054 bigbro = find_position(tree, key, parent);
1055 }
1056
1057 if (tree->root == NULL)
1058 {
1059 tree->root = row;
1060 tree->list = g_list_prepend(tree->list, key);
1061 }
1062 else
1063 {
1064 int position = 0;
1065
1066 if (bigbro)
1067 {
1068 pr = g_hash_table_lookup(tree->hash, bigbro);
1069 if (pr)
1070 {
1071 if (pr->next) pr->next->prev = row;
1072 row->next = pr->next;
1073 row->prev = pr;
1074 pr->next = row;
1075 row->parent = pr->parent;
1076
1077 position = g_list_index(tree->list, bigbro);
1078 }
1079 }
1080
1081 if (pr == NULL && parent)
1082 {
1083 pr = g_hash_table_lookup(tree->hash, parent);
1084 if (pr)
1085 {
1086 if (pr->child) pr->child->prev = row;
1087 row->next = pr->child;
1088 pr->child = row;
1089 row->parent = pr;
1090
1091 position = g_list_index(tree->list, parent);
1092 }
1093 }
1094
1095 if (pr == NULL)
1096 {
1097 GntTreeRow *r = tree->root;
1098 row->next = r;
1099 if (r) r->prev = row;
1100 if (tree->current == tree->root)
1101 tree->current = row;
1102 tree->root = row;
1103 tree->list = g_list_prepend(tree->list, key);
1104 }
1105 else
1106 {
1107 tree->list = g_list_insert(tree->list, key, position + 1);
1108 }
1109 }
1110
1111 row->key = key;
1112 row->data = NULL;
1113
1114 redraw_tree(tree);
1115
1116 return row;
1117 }
1118
1119 GntTreeRow *gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent)
1120 {
1121 GntTreeRow *pr = NULL, *br = NULL;
1122
1123 if (parent)
1124 pr = g_hash_table_lookup(tree->hash, parent);
1125
1126 if (pr)
1127 br = pr->child;
1128 else
1129 br = tree->root;
1130
1131 if (br)
1132 {
1133 while (br->next)
1134 br = br->next;
1135 }
1136
1137 return gnt_tree_add_row_after(tree, key, row, parent, br ? br->key : NULL);
1138 }
1139
1140 gpointer gnt_tree_get_selection_data(GntTree *tree)
1141 {
1142 if (tree->current)
1143 return tree->current->key; /* XXX: perhaps we should just get rid of 'data' */
1144 return NULL;
1145 }
1146
1147 char *gnt_tree_get_selection_text(GntTree *tree)
1148 {
1149 if (tree->current)
1150 return update_row_text(tree, tree->current);
1151 return NULL;
1152 }
1153
1154 GList *gnt_tree_get_selection_text_list(GntTree *tree)
1155 {
1156 GList *list = NULL, *iter;
1157 int i;
1158
1159 if (!tree->current)
1160 return NULL;
1161
1162 for (i = 0, iter = tree->current->columns; i < tree->ncol && iter;
1163 i++, iter = iter->next)
1164 {
1165 GntTreeCol *col = iter->data;
1166 list = g_list_append(list, g_strdup(col->text));
1167 }
1168
1169 return list;
1170 }
1171
1172 void gnt_tree_remove(GntTree *tree, gpointer key)
1173 {
1174 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1175 static int depth = 0; /* Only redraw after all child nodes are removed */
1176 if (row)
1177 {
1178 gboolean redraw = FALSE;
1179
1180 if (row->child) {
1181 depth++;
1182 while (row->child) {
1183 gnt_tree_remove(tree, row->child->key);
1184 }
1185 depth--;
1186 }
1187
1188 if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0)
1189 redraw = TRUE;
1190
1191 /* Update root/top/current/bottom if necessary */
1192 if (tree->root == row)
1193 tree->root = get_next(row);
1194 if (tree->top == row)
1195 {
1196 if (tree->top != tree->root)
1197 tree->top = get_prev(row);
1198 else
1199 tree->top = get_next(row);
1200 }
1201 if (tree->current == row)
1202 {
1203 if (tree->current != tree->root)
1204 tree->current = get_prev(row);
1205 else
1206 tree->current = get_next(row);
1207 tree_selection_changed(tree, row, tree->current);
1208 }
1209 if (tree->bottom == row)
1210 {
1211 tree->bottom = get_prev(row);
1212 }
1213
1214 /* Fix the links */
1215 if (row->next)
1216 row->next->prev = row->prev;
1217 if (row->parent && row->parent->child == row)
1218 row->parent->child = row->next;
1219 if (row->prev)
1220 row->prev->next = row->next;
1221
1222 g_hash_table_remove(tree->hash, key);
1223 tree->list = g_list_remove(tree->list, key);
1224
1225 if (redraw && depth == 0)
1226 {
1227 redraw_tree(tree);
1228 }
1229 }
1230 }
1231
1232 static gboolean
1233 return_true(gpointer key, gpointer data, gpointer null)
1234 {
1235 return TRUE;
1236 }
1237
1238 void gnt_tree_remove_all(GntTree *tree)
1239 {
1240 tree->root = NULL;
1241 g_hash_table_foreach_remove(tree->hash, (GHRFunc)return_true, tree);
1242 g_list_free(tree->list);
1243 tree->list = NULL;
1244 tree->current = tree->top = tree->bottom = NULL;
1245 }
1246
1247 int gnt_tree_get_selection_visible_line(GntTree *tree)
1248 {
1249 return get_distance(tree->top, tree->current) +
1250 !!(GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER));
1251 }
1252
1253 void gnt_tree_change_text(GntTree *tree, gpointer key, int colno, const char *text)
1254 {
1255 GntTreeRow *row;
1256 GntTreeCol *col;
1257
1258 g_return_if_fail(colno < tree->ncol);
1259
1260 row = g_hash_table_lookup(tree->hash, key);
1261 if (row)
1262 {
1263 col = g_list_nth_data(row->columns, colno);
1264 g_free(col->text);
1265 col->text = g_strdup(text);
1266
1267 if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0)
1268 redraw_tree(tree);
1269 }
1270 }
1271
1272 GntTreeRow *gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro)
1273 {
1274 GntTreeRow *r;
1275 r = g_hash_table_lookup(tree->hash, key);
1276 g_return_val_if_fail(!r || !r->choice, NULL);
1277
1278 if (bigbro == NULL) {
1279 if (tree->compare)
1280 bigbro = find_position(tree, key, parent);
1281 else {
1282 r = g_hash_table_lookup(tree->hash, parent);
1283 if (!r)
1284 r = tree->root;
1285 else
1286 r = r->child;
1287 if (r) {
1288 while (r->next)
1289 r = r->next;
1290 bigbro = r->key;
1291 }
1292 }
1293 }
1294 row = gnt_tree_add_row_after(tree, key, row, parent, bigbro);
1295 row->choice = TRUE;
1296
1297 return row;
1298 }
1299
1300 void gnt_tree_set_choice(GntTree *tree, void *key, gboolean set)
1301 {
1302 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1303
1304 if (!row)
1305 return;
1306 g_return_if_fail(row->choice);
1307
1308 row->isselected = set;
1309 redraw_tree(tree);
1310 }
1311
1312 gboolean gnt_tree_get_choice(GntTree *tree, void *key)
1313 {
1314 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1315
1316 if (!row)
1317 return FALSE;
1318 g_return_val_if_fail(row->choice, FALSE);
1319
1320 return row->isselected;
1321 }
1322
1323 void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags)
1324 {
1325 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1326 if (!row || row->flags == flags)
1327 return;
1328
1329 row->flags = flags;
1330 redraw_tree(tree); /* XXX: It shouldn't be necessary to redraw the whole darned tree */
1331 }
1332
1333 void gnt_tree_set_selected(GntTree *tree , void *key)
1334 {
1335 int dist;
1336 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1337 if (!row)
1338 return;
1339
1340 if (tree->top == NULL)
1341 tree->top = row;
1342 if (tree->bottom == NULL)
1343 tree->bottom = row;
1344
1345 tree->current = row;
1346 if ((dist = get_distance(tree->current, tree->bottom)) < 0)
1347 gnt_tree_scroll(tree, -dist);
1348 else if ((dist = get_distance(tree->current, tree->top)) > 0)
1349 gnt_tree_scroll(tree, -dist);
1350 else
1351 redraw_tree(tree);
1352 }
1353
1354 void _gnt_tree_init_internals(GntTree *tree, int col)
1355 {
1356 tree->ncol = col;
1357 tree->hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_tree_row);
1358 tree->columns = g_new0(struct _GntTreeColInfo, col);
1359 while (col--)
1360 {
1361 tree->columns[col].width = 15;
1362 }
1363 tree->list = NULL;
1364 tree->show_title = FALSE;
1365 }
1366
1367 GntWidget *gnt_tree_new_with_columns(int col)
1368 {
1369 GntWidget *widget = g_object_new(GNT_TYPE_TREE, NULL);
1370 GntTree *tree = GNT_TREE(widget);
1371
1372 _gnt_tree_init_internals(tree, col);
1373
1374 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW);
1375 gnt_widget_set_take_focus(widget, TRUE);
1376
1377 return widget;
1378 }
1379
1380 GntTreeRow *gnt_tree_create_row_from_list(GntTree *tree, GList *list)
1381 {
1382 GList *iter;
1383 int i;
1384 GntTreeRow *row = g_new0(GntTreeRow, 1);
1385
1386 for (i = 0, iter = list; i < tree->ncol && iter; iter = iter->next, i++)
1387 {
1388 GntTreeCol *col = g_new0(GntTreeCol, 1);
1389 col->span = 1;
1390 col->text = g_strdup(iter->data ? iter->data : "");
1391
1392 row->columns = g_list_append(row->columns, col);
1393 }
1394
1395 return row;
1396 }
1397
1398 GntTreeRow *gnt_tree_create_row(GntTree *tree, ...)
1399 {
1400 int i;
1401 va_list args;
1402 GList *list = NULL;
1403 GntTreeRow *row;
1404
1405 va_start(args, tree);
1406 for (i = 0; i < tree->ncol; i++)
1407 {
1408 list = g_list_append(list, va_arg(args, char *));
1409 }
1410 va_end(args);
1411
1412 row = gnt_tree_create_row_from_list(tree, list);
1413 g_list_free(list);
1414
1415 return row;
1416 }
1417
1418 void gnt_tree_set_col_width(GntTree *tree, int col, int width)
1419 {
1420 g_return_if_fail(col < tree->ncol);
1421
1422 tree->columns[col].width = width;
1423 }
1424
1425 void gnt_tree_set_column_titles(GntTree *tree, ...)
1426 {
1427 int i;
1428 va_list args;
1429
1430 va_start(args, tree);
1431 for (i = 0; i < tree->ncol; i++)
1432 {
1433 const char *title = va_arg(args, const char *);
1434 tree->columns[i].title = g_strdup(title);
1435 }
1436 va_end(args);
1437 }
1438
1439 void gnt_tree_set_show_title(GntTree *tree, gboolean set)
1440 {
1441 tree->show_title = set;
1442 GNT_WIDGET(tree)->priv.minh = (set ? 6 : 4);
1443 }
1444
1445 void gnt_tree_set_compare_func(GntTree *tree, GCompareFunc func)
1446 {
1447 tree->compare = func;
1448 }
1449
1450 void gnt_tree_set_expanded(GntTree *tree, void *key, gboolean expanded)
1451 {
1452 GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
1453 if (row) {
1454 row->collapsed = !expanded;
1455 if (GNT_WIDGET(tree)->window)
1456 gnt_widget_draw(GNT_WIDGET(tree));
1457 }
1458 }
1459
1460 void gnt_tree_set_show_separator(GntTree *tree, gboolean set)
1461 {
1462 tree->show_separator = set;
1463 }
1464
1465 void gnt_tree_adjust_columns(GntTree *tree)
1466 {
1467 GntTreeRow *row = tree->root;
1468 int *widths, i, twidth, height;
1469
1470 widths = g_new0(int, tree->ncol);
1471 while (row) {
1472 GList *iter;
1473 for (i = 0, iter = row->columns; iter; iter = iter->next, i++) {
1474 GntTreeCol *col = iter->data;
1475 int w = gnt_util_onscreen_width(col->text, NULL);
1476 if (widths[i] < w)
1477 widths[i] = w;
1478 }
1479 row = row->next;
1480 }
1481
1482 twidth = 1 + 2 * (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER));
1483 for (i = 0; i < tree->ncol; i++) {
1484 gnt_tree_set_col_width(tree, i, widths[i]);
1485 twidth += widths[i] + (tree->show_separator ? 1 : 0) + 1;
1486 }
1487 g_free(widths);
1488
1489 gnt_widget_get_size(GNT_WIDGET(tree), NULL, &height);
1490 gnt_widget_set_size(GNT_WIDGET(tree), twidth, height);
1491 }
1492
1493 void gnt_tree_set_hash_fns(GntTree *tree, gpointer hash, gpointer eq, gpointer kd)
1494 {
1495 g_hash_table_foreach_remove(tree->hash, return_true, NULL);
1496 g_hash_table_destroy(tree->hash);
1497 tree->hash = g_hash_table_new_full(hash, eq, kd, free_tree_row);
1498 }
1499