hack to fix stderr screwing currses in peer view
[tore.git] / pytx / txlist.c
blob6c76c7987be83343d1cdb8fe116af09c0112efb9
1 /*
2 * listbox.c - scrollable listbox
4 * Written by Joshua Davis
5 * Copyright (c) Josh Davis 2008
6 */
8 #include <ncurses.h>
9 #include <math.h>
10 #include <panel.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <assert.h>
14 #include "sort.h"
15 #include "txlist.h"
16 #include "txops.h"
17 #include "txbox.h"
19 /* default cb for list of strings */
20 static char *text_cb(TXObj *obj, char *o, void *d) {
21 return d;
24 static void *data_cb(TXObj *obj, void *o, void *d) {
25 if (o) free(o);
26 return tx_text_blank(d, obj->w);
29 static int cmpr_cb(TXObj *obj,
30 const struct list_head *a,
31 const struct list_head *b) {
32 TX_EXT(TX_List, list);
33 struct Row *ra = list_entry(a, struct Row, node);
34 struct Row *rb = list_entry(b, struct Row, node);
36 return list->asc ? strcmp(ra->data, rb->data) : strcmp(rb->data, ra->data);
39 /* functions to set cbs, ie for more complicated 'data' and routines to process
40 * the 'data' into 'text' */
41 void tx_list_text_cb_set(TXObj *obj, text_fn text_cb) {
42 TX_EXT(TX_List, list);
43 list->text_cb = text_cb;
46 void tx_list_data_cb_set(TXObj *obj, data_fn data_cb) {
47 TX_EXT(TX_List, list);
48 list->data_cb = data_cb;
51 /* function to set cmpr_cb to work with 'data' */
52 void tx_list_cmpr_cb_set(TXObj *obj, cmpr_fn cmpr_cb) {
53 TX_EXT(TX_List, list);
54 list->cmpr_cb = cmpr_cb;
57 /* function to set bind_cb to hand keyevents */
58 void tx_list_bind_cb_set(TXObj *obj, bind_fn bind_cb) {
59 TX_EXT(TX_List, list);
60 list->bind_cb = bind_cb;
65 void tx_list_map_resize(TXObj *obj, int n) {
66 int i;
67 TX_EXT(TX_List, list);
69 list->map = realloc(list->map, sizeof(struct Row*) * (n + 1));
70 assert(list->map);
72 /* if we are adding an item with a key greater then the current size we have
73 * to set all the 'in between' items to NULL */
74 for (i = list->size; i < n + 1; ++i)
75 list->map[i] = NULL;
76 list->size = n + 1;
79 void tx_list_row_init(TXObj *obj, int n) {
80 TX_EXT(TX_List, list);
82 list->map[n] = calloc(1, sizeof(struct Row));
83 assert(list->map[n]);
84 list->count++;
85 list_add_tail(&list->map[n]->node, &list->rows);
86 if (!list->sel) list->sel = list->top = list->rows.next;
89 void tx_list_row_sync(TXObj *obj, int k, void *d) {
90 TX_EXT(TX_List, list);
91 struct Row *row;
93 if (k > list->size - 1) tx_list_map_resize(obj, k);
94 if (!list->map[k]) tx_list_row_init(obj, k);
95 row = list->map[k];
96 row->data = list->data_cb(obj, row->data, d);
97 row->text = list->text_cb(obj, row->text, row->data);
98 row->key = k;
101 void tx_list_sync(TXObj *obj) {
102 TX_EXT(TX_List, list);
103 int i;
105 for (i = 0; i < list->count; ++i)
106 if (list->map[i])
107 list->map[i]->text = list->text_cb(obj,
108 list->map[i]->text,
109 list->map[i]->data);
112 int tx_list_rows_draw(TXObj *obj,
113 struct list_head *top,
114 struct list_head *bot,
115 int i,
116 int b) {
117 TX_EXT(TX_List, list);
118 struct Row *row;
119 struct list_head *item;
121 wbkgdset(obj->win, '\0' | A_NORMAL);
122 for (item = top; i < b && item != bot; item = item->next, ++i) {
123 row = list_entry(item, struct Row, node);
124 mvwaddnstr(obj->win, i, 0, row->text, obj->w);
125 if ((obj->flags & TX_LIST_SCROLL) && item == list->sel) {
126 wbkgdset(obj->win, '\0' | A_STANDOUT);
127 mvwaddnstr(obj->win, i, 0, row->text, obj->w);
128 wbkgdset(obj->win, '\0' | A_NORMAL);
131 /* so we know if we fully drew the list */
132 return i;
135 int tx_list_draw(TXObj *obj) {
136 TX_EXT(TX_List, list);
137 int ret;
139 if (!list->top) return -1;
140 ret = tx_list_rows_draw(obj, list->top, &list->rows, 0, MIN(obj->h, list->count));
141 if (list->draw_cb) list->draw_cb(obj);
143 return ret;
146 void tx_list_clear(TXObj *obj) {
147 TX_EXT(TX_List, list);
148 int i;
150 /* FIXME: proper free */
151 for (i = 0; i < list->count; ++i)
152 list->map[i] = NULL;
154 /* reset map */
155 list->count = 0;
156 list->size = 0;
158 /* reset list */
159 list_init(&list->rows);
160 list->sel = list->top = NULL;
162 werase(obj->win);
165 void tx_draw_blank_line(TXObj *obj, int i) {
166 char *text;
167 text = tx_text_blank(NULL, obj->w);
168 mvwaddnstr(obj->win, i, 0, text, obj->w);
169 free(text);
172 void tx_list_row_remove(TXObj *obj, int k) {
173 TX_EXT(TX_List, list);
174 struct Row *row = list->map[k];
175 int i;
177 if (!row) return;
178 list->sel = row->node.next;
179 if (row == list->top)
180 list->top = list->sel;
182 list_del(&row->node);
184 /* FIXME: proper free */
185 list->map[k] = NULL;
186 list->count--;
188 i = tx_list_draw(obj);
190 if (i < obj->h)
191 tx_draw_blank_line(obj, i);
194 /* we 'walk' over the items in the list either forwards or backwards and set
195 * selected */
196 void tx_list_row_walk(TXObj *obj, int s) {
197 TX_EXT(TX_List, list);
198 struct list_head *up, *down;
199 int i, u;
201 up = down = list->sel;
202 u = s < 0 ? 1 : 0;
203 s = abs(s);
205 /* iterate over list until a) we hit the top b) we exceep scroll */
206 for (i = 0; i < s; i++) {
207 /* ensure scrolls greater then 1 do not loop around */
208 if (!u && down->next == &list->rows) {
209 list->sel = i ? list->rows.prev : list->rows.next;
210 return;
212 if (u && up->prev == &list->rows) {
213 list->sel = i ? list->rows.next : list->rows.prev;
214 return;
216 up = up->prev;
217 down = down->next;
219 /* we did not hit the top or bottom */
220 list->sel = u ? up : down;
224 /* this is tricky, perhaps dirty. 1) try to make the selected item occur in the
225 * middle of the list. this is possible iff there are enough items above and
226 * below the selected item. 2) place selected item closer to the top or bottom
227 * if 1) is not possible */
228 void tx_list_row_set_top(TXObj *obj) {
229 TX_EXT(TX_List, list);
230 int i,u,d,s,o;
231 struct list_head *up, *down;
233 if (list->count < obj->h) {
234 list->top = list->rows.next;
235 return;
238 s = obj->h/2;
239 /* if s is not in the middle, we must set an offset of 1. this fixes the
240 * selected item be the very bottom item in the list */
241 o = (obj->h + 1) % 2;
242 up = list->sel;
243 down = list->sel->next;
245 /* determin if we can hit the bottom */
246 for (d = 0; d < s; d++) {
247 if (down == &list->rows) break;
248 down = down->next;
251 /* can we hit the top */
252 for (u = 0; u < s; u++) {
253 if (up == list->rows.next) break;
254 up = up->prev;
257 /* displace top if at the bottom of the list */
258 if (d < s) {
259 for (i = u - d - o; i > 0; i--) {
260 if (up == &list->rows) break;
261 up = up->prev;
265 list->top = up;
268 /* using wscrl is about twice as fast, but more complicated so scrapped as 1%
269 * to 2x is nothing*/
270 void tx_list_scroll(TXObj *obj, int scroll) {
271 tx_list_row_walk(obj, scroll);
272 tx_list_row_set_top(obj);
273 tx_obj_draw(obj);
274 //wscrl(obj->win, scroll);
275 //update_panels();
276 //doupdate();
279 int tx_list_selected_get(TXObj *obj) {
280 TX_EXT(TX_List, list);
281 struct Row *row;
282 if (list->sel) {
283 row = list_entry(list->sel, struct Row, node);
284 return row->key;
286 /* no item is selected, ie list is empty */
287 return -1;
290 void tx_list_selected_set(TXObj *obj, int key) {
291 TX_EXT(TX_List, list);
292 struct Row *row;
293 struct list_head *item;
295 list_for_each(item, list->sel) {
296 row = list_entry(item, struct Row, node);
297 if (row->key == key)
298 break;
301 list->sel = item;
302 tx_list_row_set_top(obj);
305 void tx_list_operate(TXObj *obj, int key) {
306 TX_EXT(TX_List, list);
307 switch(key) {
308 case KEY_UP:
309 if (obj->flags & TX_LIST_SCROLL) tx_list_scroll(obj, -1);
310 break;
311 case KEY_DOWN:
312 if (obj->flags & TX_LIST_SCROLL) tx_list_scroll(obj, +1);
313 break;
314 case KEY_PPAGE:
315 if (obj->flags & TX_LIST_SCROLL) tx_list_scroll(obj, -obj->h/2);
316 break;
317 case KEY_NPAGE:
318 if (obj->flags & TX_LIST_SCROLL) tx_list_scroll(obj, obj->h/2);
319 break;
320 default: if (list->bind_cb) list->bind_cb(obj, key, tx_list_selected_get(obj)); break;
322 /* FIXME: testing, delete at some stage */
323 //case 'D': tx_list_row_remove(obj, tx_list_selected_get(obj)); tx_obj_draw(obj); break;
324 case 'C': tx_list_clear(obj); tx_obj_draw(obj); break;
325 case 'B': tx_obj_flag_toggle(obj, TX_OBJ_BORDER); tx_obj_reset(obj); tx_obj_draw(obj); break;
326 case 'S': tx_obj_flag_toggle(obj, TX_LIST_SEPARATOR); tx_obj_reset(obj); tx_obj_draw(obj); break;
327 case 'T': tx_obj_flag_toggle(obj, TX_LIST_TITLE); tx_obj_reset(obj); tx_list_sync(obj); tx_obj_draw(obj); break;
331 void tx_list_sort_asc_set(TXObj *obj, int asc) {
332 TX_EXT(TX_List, list);
333 list->asc = asc;
336 int tx_list_sort_asc_get(TXObj *obj) {
337 TX_EXT(TX_List, list);
338 return list->asc;
341 void tx_list_sort(TXObj *obj) {
342 TX_EXT(TX_List, list);
343 sort(&list->rows, obj, list->cmpr_cb);
344 tx_list_row_set_top(obj);
347 /* ---------------- column stuff ----------------------- */
348 /* FIXME: abstract out */
349 /* draw in lines */
352 void tx_list_columns_line_draw(TXObj *obj) {
353 TX_EXT(TX_List, list);
354 int i, border = obj->flags & TX_OBJ_BORDER;
356 if (!(obj->flags & TX_LIST_SEPARATOR)) return;
357 wattrset(obj->win, A_NORMAL);
358 for (i = 1; i < list->cols.ncols; ++i) {
359 mvwvline(obj->win,
361 list->cols.offsets[i] - 1,
362 ACS_VLINE,
363 MIN(obj->h, list->count));
364 if ((obj->flags & TX_LIST_TITLE) && list->cols_cb) {
365 mvwvline(obj->bwin, 0 + border, list->cols.offsets[i] - 1 + border, ACS_VLINE, 1);
366 mvwaddch(obj->bwin, 1 + border, list->cols.offsets[i] - 1 + border, ACS_PLUS);
371 char *tx_list_columns_format(TXColumns *cols, char **c, int w) {
372 int i;
373 char *text = tx_text_blank(NULL, w);
375 for(i = 0; i < cols->ncols; ++i)
376 memcpy(text + cols->offsets[i], c[i], MIN(cols->widths[i], strlen(c[i])));
378 return text;
381 /* NOTE: perhaps we could save each col of each row, then hightlight the whole
382 * column eg abscgract this out to take a win and col structures */
383 void tx_list_column_title_draw(TXObj *obj) {
384 TX_EXT(TX_List, list);
385 char *titles;
386 int hl = list->cols.sort;
387 int border = obj->flags & TX_OBJ_BORDER;
389 if (!(obj->flags & TX_LIST_TITLE && list->cols.ncols)) return;
391 titles = tx_list_columns_format(&list->cols, list->cols.titles, obj->w);
392 mvwaddnstr(obj->bwin, border, border, titles, obj->w);
393 free(titles);
395 /* highlight selected */
396 if (!(obj->flags & TX_LIST_HLIGHT)) return;
398 wbkgdset(obj->bwin, '\0' | A_STANDOUT);
399 mvwaddnstr(obj->bwin,
400 border,
401 list->cols.offsets[hl]+border,
402 list->cols.titles[hl],
403 list->cols.widths[hl]);
405 wbkgdset(obj->bwin, '\0' | A_NORMAL);
408 void tx_list_column_sort_set(TXObj *obj, int col) {
409 TX_EXT(TX_List, list);
410 list->cols.sort = col;
411 tx_list_column_title_draw(obj);
414 int tx_list_column_sort_get(TXObj *obj) {
415 TX_EXT(TX_List, list);
416 return list->cols.sort;
419 /* wrapper to convert list of strings into a string */
420 static char *text_cols_cb(TXObj *obj, char *o, void *d) {
421 TX_EXT(TX_List, list);
422 char *text;
423 char **cols = list->cols_cb(obj, o, d);
424 text = tx_list_columns_format(&list->cols, cols, obj->w);
425 free(cols);
426 return text;
429 void tx_list_cols_cb_set(TXObj *obj, cols_fn cols_cb) {
430 TX_EXT(TX_List, list);
432 list->text_cb = text_cols_cb;
433 list->cols_cb = cols_cb;
434 list->draw_cb = tx_list_columns_line_draw;
437 void tx_list_columns_width_adjust(TXColumns *cols, int w, int f) {
438 int i, o;
440 for (i = o = 0; i < cols->ncols-1; ++i) {
441 if (f) cols->widths[i] = f;
442 cols->offsets[i] = o;
443 o += cols->widths[i] + 1;
446 cols->offsets[i] = o;
447 cols->widths[i] = w-o-1;
448 assert(cols->widths[i] > 0);
451 /* FIXME add assumption that data is an array of strings, hence set col_cb */
452 void tx_list_columns_width_set(TXObj *obj, int ncols, int *widths) {
453 TX_EXT(TX_List, list);
454 int i, t;
456 list->cols.ncols = ncols;
457 list->cols.widths = realloc(list->cols.widths, sizeof(int) * ncols);
458 list->cols.offsets = realloc(list->cols.offsets, sizeof(int) * ncols);
459 assert(list->cols.offsets);
460 assert(list->cols.widths);
462 for (t = i = 0; i < ncols; ++i) {
463 t += widths[i];
464 list->cols.widths[i] = widths[i];
466 /* if the sum of the widths is greater then the width of the box, give each
467 * col an equal width */
468 tx_list_columns_width_adjust(&list->cols,
469 obj->w,
470 t > obj->w ? obj->w/ncols : 0);
473 /* FIXME: should i copy this out */
474 int *tx_list_columns_width_get(TXObj *obj) {
475 TX_EXT(TX_List, list);
477 return list->cols.widths;
480 void tx_list_columns_title_set(TXObj *obj, int ncols, char **titles) {
481 TX_EXT(TX_List, list);
482 int i;
484 list->cols.titles = realloc(list->cols.titles, sizeof(char *) * ncols);
485 assert(list->cols.titles);
487 /* FIXME: what if new columns has less columns etc */
488 /* add the titles */
489 for (i = 0; i < ncols; ++i)
490 //if (list->cols.titles[i]) free(list->cols.titles[i]);
491 list->cols.titles[i] = titles[i];
493 /* draw the titles */
494 tx_list_column_title_draw(obj);
497 void tx_list_init(TXObj *obj) {
498 int border = obj->flags & TX_OBJ_BORDER;
500 if (obj->flags & TX_LIST_TITLE) {
501 //fprintf(stderr,"%d %d %d %d\n", obj->x, obj->y, obj->w, obj->h);
502 tx_obj_size_set(obj, obj->x, obj->y+2, obj->w, obj->h-2);
503 mvwhline(obj->bwin, 1+border, 0+border, ACS_HLINE, obj->w);
504 tx_list_column_title_draw(obj);
507 if (obj->flags & TX_LIST_SCROLL)
508 scrollok(obj->win, 1);
510 /* resize existing window */
511 if (obj->win) {
512 wresize(obj->win, obj->h, obj->w);
513 mvwin(obj->win, obj->y, obj->x);
514 tx_list_sync(obj);
515 return;
518 /* make new window */
519 obj->win = newwin(obj->h, obj->w, obj->y, obj->x);
520 assert(obj->win);
521 obj->panel = new_panel(obj->win);
524 void tx_list_new(TXObj *obj) {
525 obj->extend = calloc(1, sizeof(TX_List));
526 assert(obj->extend);
527 TX_EXT(TX_List, list);
529 obj->draw_cb = tx_list_draw;
530 obj->oper_cb = tx_list_operate;
531 obj->init_cb = tx_list_init;
533 /* we add strings by default */
534 list->cmpr_cb = cmpr_cb;
535 list->data_cb = data_cb;
536 list->text_cb = text_cb;
538 list_init(&list->rows);