15817
|
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
|