2 * listbox.c - scrollable listbox
4 * Written by Joshua Davis
5 * Copyright (c) Josh Davis 2008
19 /* default cb for list of strings */
20 static char *text_cb(TXObj
*obj
, char *o
, void *d
) {
24 static void *data_cb(TXObj
*obj
, void *o
, void *d
) {
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
) {
67 TX_EXT(TX_List
, list
);
69 list
->map
= realloc(list
->map
, sizeof(struct Row
*) * (n
+ 1));
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
)
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
));
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
);
93 if (k
> list
->size
- 1) tx_list_map_resize(obj
, k
);
94 if (!list
->map
[k
]) tx_list_row_init(obj
, k
);
96 row
->data
= list
->data_cb(obj
, row
->data
, d
);
97 row
->text
= list
->text_cb(obj
, row
->text
, row
->data
);
101 void tx_list_sync(TXObj
*obj
) {
102 TX_EXT(TX_List
, list
);
105 for (i
= 0; i
< list
->count
; ++i
)
107 list
->map
[i
]->text
= list
->text_cb(obj
,
112 int tx_list_rows_draw(TXObj
*obj
,
113 struct list_head
*top
,
114 struct list_head
*bot
,
117 TX_EXT(TX_List
, list
);
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 */
135 int tx_list_draw(TXObj
*obj
) {
136 TX_EXT(TX_List
, list
);
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
);
146 void tx_list_clear(TXObj
*obj
) {
147 TX_EXT(TX_List
, list
);
150 /* FIXME: proper free */
151 for (i
= 0; i
< list
->count
; ++i
)
159 list_init(&list
->rows
);
160 list
->sel
= list
->top
= NULL
;
165 void tx_draw_blank_line(TXObj
*obj
, int i
) {
167 text
= tx_text_blank(NULL
, obj
->w
);
168 mvwaddnstr(obj
->win
, i
, 0, text
, obj
->w
);
172 void tx_list_row_remove(TXObj
*obj
, int k
) {
173 TX_EXT(TX_List
, list
);
174 struct Row
*row
= list
->map
[k
];
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 */
188 i
= tx_list_draw(obj
);
191 tx_draw_blank_line(obj
, i
);
194 /* we 'walk' over the items in the list either forwards or backwards and set
196 void tx_list_row_walk(TXObj
*obj
, int s
) {
197 TX_EXT(TX_List
, list
);
198 struct list_head
*up
, *down
;
201 up
= down
= list
->sel
;
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
;
212 if (u
&& up
->prev
== &list
->rows
) {
213 list
->sel
= i
? list
->rows
.next
: list
->rows
.prev
;
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
);
231 struct list_head
*up
, *down
;
233 if (list
->count
< obj
->h
) {
234 list
->top
= list
->rows
.next
;
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;
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;
251 /* can we hit the top */
252 for (u
= 0; u
< s
; u
++) {
253 if (up
== list
->rows
.next
) break;
257 /* displace top if at the bottom of the list */
259 for (i
= u
- d
- o
; i
> 0; i
--) {
260 if (up
== &list
->rows
) break;
268 /* using wscrl is about twice as fast, but more complicated so scrapped as 1%
270 void tx_list_scroll(TXObj
*obj
, int scroll
) {
271 tx_list_row_walk(obj
, scroll
);
272 tx_list_row_set_top(obj
);
274 //wscrl(obj->win, scroll);
279 int tx_list_selected_get(TXObj
*obj
) {
280 TX_EXT(TX_List
, list
);
283 row
= list_entry(list
->sel
, struct Row
, node
);
286 /* no item is selected, ie list is empty */
290 void tx_list_selected_set(TXObj
*obj
, int key
) {
291 TX_EXT(TX_List
, list
);
293 struct list_head
*item
;
295 list_for_each(item
, list
->sel
) {
296 row
= list_entry(item
, struct Row
, node
);
302 tx_list_row_set_top(obj
);
305 void tx_list_operate(TXObj
*obj
, int key
) {
306 TX_EXT(TX_List
, list
);
309 if (obj
->flags
& TX_LIST_SCROLL
) tx_list_scroll(obj
, -1);
312 if (obj
->flags
& TX_LIST_SCROLL
) tx_list_scroll(obj
, +1);
315 if (obj
->flags
& TX_LIST_SCROLL
) tx_list_scroll(obj
, -obj
->h
/2);
318 if (obj
->flags
& TX_LIST_SCROLL
) tx_list_scroll(obj
, obj
->h
/2);
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
);
336 int tx_list_sort_asc_get(TXObj
*obj
) {
337 TX_EXT(TX_List
, list
);
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 */
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
) {
361 list
->cols
.offsets
[i
] - 1,
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
) {
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
])));
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
);
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
);
395 /* highlight selected */
396 if (!(obj
->flags
& TX_LIST_HLIGHT
)) return;
398 wbkgdset(obj
->bwin
, '\0' | A_STANDOUT
);
399 mvwaddnstr(obj
->bwin
,
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
);
423 char **cols
= list
->cols_cb(obj
, o
, d
);
424 text
= tx_list_columns_format(&list
->cols
, cols
, obj
->w
);
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
) {
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
);
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
) {
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
,
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
);
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 */
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 */
512 wresize(obj
->win
, obj
->h
, obj
->w
);
513 mvwin(obj
->win
, obj
->y
, obj
->x
);
518 /* make new window */
519 obj
->win
= newwin(obj
->h
, obj
->w
, obj
->y
, obj
->x
);
521 obj
->panel
= new_panel(obj
->win
);
524 void tx_list_new(TXObj
*obj
) {
525 obj
->extend
= calloc(1, sizeof(TX_List
));
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
);