Initial commit of the HEAD branch of the ELinks CVS repository, as of
[elinks/images.git] / src / bfu / listbox.c
blobff465474fd250e61213ef08ac8b57f324b47c75d
1 /* Listbox widget implementation. */
2 /* $Id: listbox.c,v 1.212 2005/08/02 01:20:45 miciah Exp $ */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <string.h>
10 #include "elinks.h"
12 #include "bfu/dialog.h"
13 #include "bfu/hierbox.h"
14 #include "bfu/listbox.h"
15 #include "config/kbdbind.h"
16 #include "intl/gettext/libintl.h"
17 #include "terminal/draw.h"
18 #include "terminal/mouse.h"
19 #include "terminal/terminal.h"
20 #include "util/color.h"
21 #include "util/conv.h"
22 #include "util/lists.h"
25 #define VERTICAL_LISTBOX_MARGIN 3
27 void
28 add_dlg_listbox(struct dialog *dlg, int height, void *box_data)
30 struct widget *widget = &dlg->widgets[dlg->number_of_widgets++];
32 widget->type = WIDGET_LISTBOX;
33 widget->data = box_data;
35 widget->info.listbox.height = height;
38 struct listbox_data *
39 get_listbox_widget_data(struct widget_data *widget_data)
41 assert(widget_data->widget->type == WIDGET_LISTBOX);
42 return ((struct listbox_data *) widget_data->widget->data);
45 /* Layout for generic boxes */
46 void
47 dlg_format_listbox(struct terminal *term, struct widget_data *widget_data,
48 int x, int *y, int w, int max_height, int *rw,
49 enum format_align align)
51 int min, optimal_h, height;
53 /* Height bussiness follows: */
55 /* This is only weird heuristic, it could scale well I hope. */
56 optimal_h = max_height * 7 / 10 - VERTICAL_LISTBOX_MARGIN;
57 min = get_opt_int("ui.dialogs.listbox_min_height");
59 if (max_height - VERTICAL_LISTBOX_MARGIN < min) {
60 /* Big trouble: can't satisfy even the minimum :-(. */
61 height = max_height - VERTICAL_LISTBOX_MARGIN;
62 } else if (optimal_h < min) {
63 height = min;
64 } else {
65 height = optimal_h;
68 set_box(&widget_data->box, x, *y, w, height);
69 (*y) += height;
70 if (rw) *rw = w;
75 *,item00->prev
76 *|item00->root = NULL ,item10->prev
77 *|item00->child <----->|item10->root
78 *|item00->next <-. |item10->child [<->]
79 *| | `item10->next
80 *|item01->prev <-'
81 *|item01->root = NULL
82 *|item01->child [<->]
83 *|item01->next <-.
84 *| |
85 *|item02->prev <-'
86 *|item02->root = NULL ,item11->prev
87 *|item02->child <----->|item11->root ,item20->prev
88 *`item02->next \ |item11->child <----->|item20->root
89 * | |item11->next <-. |item20->child [<->]
90 * | | | `item20->next
91 * | |item12->prev <-'
92 * `-|item12->root
93 * | |item12->child [<->]
94 * | |item12->next <-.
95 * | | |
96 * | |item13->prev <-'
97 * `-|item13->root ,item21->prev
98 * |item13->child <----->|item21->root
99 * `item13->next |item21->child [<->]
100 * `item21->next
104 /* Traverse a hierarchic tree from @item by @offset items, calling @fn,
105 * if it is not NULL, on each item traversed (that is, each of the items
106 * that we move _through_; this means from the passed @item up to,
107 * but not including, the returned item).
109 * @offset may be negative to indicate that we should traverse upwards.
111 * Besides the current item, @fn is also passed @d, which is otherwise unused
112 * by traverse_listbox_items_list, and a pointer to @offset, which @fn can set
113 * to 0 to stop traversal or to other values to change the direction in which
114 * or the number of items over which we will traverse.
116 * @fn should return 1 if it freed its passed item, or return 0 otherwise.
118 * If the passed @offset is zero, we set @offset to 1 and traverse thru
119 * the list (down) until either we reach the end or @fn sets @offset to 0. */
120 /* From the box structure, we should use only 'items' here. */
121 struct listbox_item *
122 traverse_listbox_items_list(struct listbox_item *item, struct listbox_data *box,
123 int offset, int follow_visible,
124 int (*fn)(struct listbox_item *, void *, int *),
125 void *d)
127 struct listbox_item *visible_item = item;
128 int levmove = 0;
129 int stop = 0;
130 int infinite = !offset;
132 if (!item) return NULL;
134 if (infinite)
135 offset = 1;
137 while (offset && !stop) {
138 /* We need to cache these. Or what will happen if something
139 * will free us item too early? However, we rely on item
140 * being at least NULL in that case. */
141 /* There must be no orphaned listbox_items. No free()d roots
142 * and no dangling children. */
143 #define item_cache(item) \
144 do { \
145 croot = box->ops->get_root(item); cprev = item->prev; cnext = item->next; \
146 } while (0)
147 struct listbox_item *croot, *cprev, *cnext;
149 item_cache(item);
151 if (fn && (!follow_visible || item->visible)) {
152 if (fn(item, d, &offset)) {
153 /* We was free()d! Let's try to carry on w/ the
154 * cached coordinates. */
155 item = NULL;
157 if (!offset) {
158 infinite = 0; /* safety (matches) */
159 continue;
163 if (offset > 0) {
164 /* Otherwise we climb back up when last item in root
165 * is a folder. */
166 struct listbox_item *cragsman = NULL;
168 /* Direction DOWN. */
170 if (!infinite) offset--;
172 if (item && !list_empty(item->child) && item->expanded
173 && (!follow_visible || item->visible)) {
174 /* Descend to children. */
175 item = item->child.next;
176 item_cache(item);
177 goto done_down;
180 while (croot
181 && (void *) cnext == &croot->child) {
182 /* Last item in a non-root list, climb to your
183 * root. */
184 if (!cragsman) cragsman = item;
185 item = croot;
186 item_cache(item);
189 if (!croot && (!cnext || (void *) cnext == box->items)) {
190 /* Last item in the root list, quit.. */
191 stop = 1;
192 if (cragsman) {
193 /* ..and fall back where we were. */
194 item = cragsman;
195 item_cache(item);
199 /* We're not at the end of anything, go on. */
200 if (!stop) {
201 item = cnext;
202 item_cache(item);
205 done_down:
206 if (!item || (follow_visible && !item->visible)) {
207 offset++;
208 } else {
209 visible_item = item;
212 } else {
213 /* Direction UP. */
215 if (!infinite) offset++;
217 if (croot
218 && (void *) cprev == &croot->child) {
219 /* First item in a non-root list, climb to your
220 * root. */
221 item = croot;
222 item_cache(item);
223 levmove = 1;
226 if (!croot && (void *) cprev == box->items) {
227 /* First item in the root list, quit. */
228 stop = 1;
229 levmove = 1;
232 /* We're not at the start of anything, go on. */
233 if (!levmove && !stop) {
234 item = cprev;
235 item_cache(item);
237 while (item && !list_empty(item->child)
238 && item->expanded
239 && (!follow_visible || item->visible)) {
240 /* Descend to children. */
241 item = item->child.prev;
242 item_cache(item);
244 } else {
245 levmove = 0;
248 if (!item || (follow_visible && !item->visible)) {
249 offset--;
250 } else {
251 visible_item = item;
254 #undef item_cache
257 return visible_item;
260 static int
261 calc_dist(struct listbox_item *item, void *data_, int *offset)
263 int *item_offset = data_;
265 if (*offset < 0)
266 --*item_offset;
267 else if (*offset > 0)
268 ++*item_offset;
270 return 0;
273 /* Moves the selected item by [dist] items. If [dist] is out of the current
274 * range, the selected item is moved to the extreme (ie, the top or bottom) */
275 void
276 listbox_sel_move(struct widget_data *widget_data, int dist)
278 struct listbox_data *box = get_listbox_widget_data(widget_data);
280 if (list_empty(*box->items)) return;
282 if (!box->top) box->top = box->items->next;
283 if (!box->sel) box->sel = box->top;
285 /* We want to have these visible if possible. */
286 if (box->top && !box->top->visible) {
287 box->top = traverse_listbox_items_list(box->top, box,
288 1, 1, NULL, NULL);
289 box->sel = box->top;
292 if (!dist && !box->sel->visible) dist = 1;
294 if (dist) {
295 box->sel = traverse_listbox_items_list(box->sel, box, dist, 1,
296 calc_dist,
297 &box->sel_offset);
298 /* box->sel_offset becomes the offset of the new box->sel
299 * from box->top. */
302 if (box->sel_offset < 0) {
303 /* We must scroll up. */
304 box->sel_offset = 0;
305 box->top = box->sel;
306 } else if (box->sel_offset >= widget_data->box.height) {
307 /* We must scroll down. */
308 box->sel_offset = widget_data->box.height - 1;
309 box->top = traverse_listbox_items_list(box->sel, box,
310 1 - widget_data->box.height,
311 1, NULL, NULL);
315 static int
316 test_search(struct listbox_item *item, void *data_, int *offset)
318 struct listbox_context *listbox_context = data_;
320 if (item != listbox_context->item)
321 listbox_context->offset++;
322 else
323 *offset = 0;
325 return 0;
328 static int
329 listbox_item_offset(struct listbox_data *box, struct listbox_item *item)
331 struct listbox_context ctx;
333 memset(&ctx, 0, sizeof(ctx));
334 ctx.item = item;
335 ctx.offset = 0;
337 traverse_listbox_items_list(box->items->next, box, 0, 1, test_search, &ctx);
339 return ctx.offset;
342 void
343 listbox_sel(struct widget_data *widget_data, struct listbox_item *item)
345 struct listbox_data *box = get_listbox_widget_data(widget_data);
347 listbox_sel_move(widget_data,
348 listbox_item_offset(box, item)
349 - listbox_item_offset(box, box->sel));
353 /* Takes care about rendering of each listbox item. */
354 static int
355 display_listbox_item(struct listbox_item *item, void *data_, int *offset)
357 struct listbox_context *data = data_;
358 unsigned char *stylename;
359 int len; /* Length of the current text field. */
360 struct color_pair *color;
361 int depth = item->depth + 1;
362 int d;
363 int x, y;
365 stylename = (item == data->box->sel) ? "menu.selected"
366 : ((item->marked) ? "menu.marked"
367 : "menu.normal");
369 color = get_bfu_color(data->term, stylename);
371 y = data->widget_data->box.y + data->offset;
372 for (d = 0; d < depth - 1; d++) {
373 struct listbox_item *root = item;
374 struct listbox_item *child = item;
375 int i, x;
377 for (i = depth - d; i; i--) {
378 child = root;
379 if (root) root = data->box->ops->get_root(root);
382 /* XXX */
383 x = data->widget_data->box.x + d * 5;
384 draw_text(data->term, x, y, " ", 5, 0, color);
386 if (root ? root->child.prev == child
387 : data->box->items->prev == child)
388 continue; /* We were the last branch. */
390 draw_border_char(data->term, x + 1, y, BORDER_SVLINE, color);
393 if (depth) {
394 enum border_char str[5] =
395 { 32, BORDER_SRTEE, BORDER_SHLINE, BORDER_SHLINE, 32 };
396 int i;
398 switch (item->type) {
399 case BI_LEAF:
400 case BI_SEPARATOR:
402 struct listbox_item *root = data->box->ops->get_root(item);
404 if (root) {
405 if (item == root->child.prev) {
406 str[1] = BORDER_SDLCORNER;
408 } else {
409 struct list_head *p = data->box->items;
411 if (p->next == item) {
412 str[1] = BORDER_SULCORNER;
413 } else if (p->prev == item) {
414 str[1] = BORDER_SDLCORNER;
417 break;
419 case BI_FOLDER:
420 str[0] = '[';
421 str[1] = (item->expanded) ? '-' : '+';
422 str[2] = ']';
423 break;
424 default:
425 INTERNAL("Unknown item type");
426 break;
429 if (item->marked) str[4] = '*';
431 x = data->widget_data->box.x + (depth - 1) * 5;
432 for (i = 0; i < 5; i++) {
433 draw_border_char(data->term, x + i, y, str[i], color);
437 x = data->widget_data->box.x + depth * 5;
439 if (item->type == BI_SEPARATOR) {
440 int i;
441 int width = data->widget_data->box.width - depth * 5;
443 for (i = 0; i < width; i++) {
444 draw_border_char(data->term, x + i, y, BORDER_SHLINE, color);
447 } else if (data->box->ops && data->box->ops->draw) {
448 int width = data->widget_data->box.width - depth * 5;
450 data->box->ops->draw(item, data, x, y, width);
452 } else {
453 unsigned char *text;
454 struct listbox_ops *ops = data->box->ops;
456 assert(ops && ops->get_info);
458 text = ops->get_text(item, data->term);
459 if (!text) return 0;
461 len = strlen(text);
462 int_upper_bound(&len, int_max(0, data->widget_data->box.width - depth * 5));
464 draw_text(data->term, x, y, text, len, 0, color);
466 mem_free(text);
469 if (item == data->box->sel) {
470 x = data->widget_data->box.x;
472 /* For blind users: */
473 set_cursor(data->term, x, y, 1);
474 set_window_ptr(data->dlg_data->win, x, y);
477 data->offset++;
479 return 0;
482 /* Displays a dialog box */
483 static widget_handler_status_T
484 display_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
486 struct terminal *term = dlg_data->win->term;
487 struct listbox_data *box = get_listbox_widget_data(widget_data);
488 struct listbox_context data;
490 listbox_sel_move(widget_data, 0);
492 draw_box(term, &widget_data->box, ' ', 0,
493 get_bfu_color(term, "menu.normal"));
495 memset(&data, 0, sizeof(data));
496 data.term = term;
497 data.widget_data = widget_data;
498 data.box = box;
499 data.dlg_data = dlg_data;
501 traverse_listbox_items_list(box->top, box, widget_data->box.height,
502 1, display_listbox_item, &data);
504 return EVENT_PROCESSED;
507 static int
508 check_old_state(struct listbox_item *item, void *info_, int *offset)
510 struct listbox_data *box = info_;
512 if (box->sel == item)
513 box->sel = NULL;
514 else if (box->top == item)
515 box->top = NULL;
517 if (!box->sel && !box->top)
518 *offset = 0;
520 return 0;
523 static widget_handler_status_T
524 init_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
526 struct hierbox_browser *browser = dlg_data->dlg->udata2;
527 struct listbox_data *box = get_listbox_widget_data(widget_data);
529 /* Try to restore the position from last time */
530 if (!list_empty(browser->root.child) && browser->box_data.items) {
531 copy_struct(box, &browser->box_data);
533 traverse_listbox_items_list(browser->root.child.next, box, 0, 0,
534 check_old_state, box);
536 box->sel = (!box->sel) ? browser->box_data.sel : NULL;
537 box->top = (!box->top) ? browser->box_data.top : NULL;
538 if (!box->sel) box->sel = box->top;
539 if (!box->top) box->top = box->sel;
542 box->ops = browser->ops;
543 box->items = &browser->root.child;
545 add_to_list(browser->boxes, box);
546 return EVENT_PROCESSED;
549 static widget_handler_status_T
550 mouse_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
552 #ifdef CONFIG_MOUSE
553 struct listbox_data *box = get_listbox_widget_data(widget_data);
554 struct term_event *ev = dlg_data->term_event;
556 if (!list_empty(*box->items)) {
557 if (!box->top) box->top = box->items->next;
558 if (!box->sel) box->sel = box->top;
561 if (check_mouse_action(ev, B_DOWN)) {
562 struct widget_data *dlg_item = dlg_data->widgets_data;
564 switch (get_mouse_button(ev)) {
565 case B_WHEEL_DOWN:
566 listbox_sel_move(dlg_item, 1);
567 display_widget(dlg_data, dlg_item);
568 return EVENT_PROCESSED;
570 case B_WHEEL_UP:
571 listbox_sel_move(dlg_item, -1);
572 display_widget(dlg_data, dlg_item);
573 return EVENT_PROCESSED;
577 if (check_mouse_wheel(ev))
578 return EVENT_NOT_PROCESSED;
580 if (check_mouse_position(ev, &widget_data->box)) {
581 /* Clicked in the box. */
582 int offset = ev->info.mouse.y - widget_data->box.y;
584 box->sel_offset = offset;
585 box->sel = offset ? traverse_listbox_items_list(box->top, box,
586 offset, 1,
587 NULL, NULL)
588 : box->top;
590 if (box->sel && box->sel->type == BI_FOLDER) {
591 int xdepth = widget_data->box.x + box->sel->depth * 5;
592 int x = ev->info.mouse.x;
594 if (x >= xdepth && x <= xdepth + 2)
595 box->sel->expanded = !box->sel->expanded;
598 display_widget(dlg_data, widget_data);
600 return EVENT_PROCESSED;
603 #endif /* CONFIG_MOUSE */
605 return EVENT_NOT_PROCESSED;
608 static widget_handler_status_T
609 kbd_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
611 struct widget_data *dlg_item = dlg_data->widgets_data;
612 struct term_event *ev = dlg_data->term_event;
614 /* Not a pure listbox, but you're not supposed to use this outside of
615 * the listbox browser anyway, so what.. */
617 switch (ev->ev) {
618 enum menu_action action_id;
620 case EVENT_KBD:
621 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
623 /* Moving the box */
624 if (action_id == ACT_MENU_DOWN) {
625 listbox_sel_move(dlg_item, 1);
626 display_widget(dlg_data, dlg_item);
628 return EVENT_PROCESSED;
631 if (action_id == ACT_MENU_UP) {
632 listbox_sel_move(dlg_item, -1);
633 display_widget(dlg_data, dlg_item);
635 return EVENT_PROCESSED;
638 if (action_id == ACT_MENU_PAGE_DOWN) {
639 struct listbox_data *box;
641 box = get_listbox_widget_data(dlg_item);
643 listbox_sel_move(dlg_item,
644 2 * dlg_item->box.height
645 - box->sel_offset - 1);
647 display_widget(dlg_data, dlg_item);
649 return EVENT_PROCESSED;
652 if (action_id == ACT_MENU_PAGE_UP) {
653 struct listbox_data *box;
655 box = get_listbox_widget_data(dlg_item);
657 listbox_sel_move(dlg_item,
658 -dlg_item->box.height
659 - box->sel_offset);
661 display_widget(dlg_data, dlg_item);
663 return EVENT_PROCESSED;
666 if (action_id == ACT_MENU_HOME) {
667 listbox_sel_move(dlg_item, -INT_MAX);
668 display_widget(dlg_data, dlg_item);
670 return EVENT_PROCESSED;
673 if (action_id == ACT_MENU_END) {
674 listbox_sel_move(dlg_item, INT_MAX);
675 display_widget(dlg_data, dlg_item);
677 return EVENT_PROCESSED;
680 if (action_id == ACT_MENU_MARK_ITEM) {
681 struct listbox_data *box;
683 box = get_listbox_widget_data(dlg_item);
684 if (box->sel) {
685 box->sel->marked = !box->sel->marked;
686 listbox_sel_move(dlg_item, 1);
688 display_widget(dlg_data, dlg_item);
690 return EVENT_PROCESSED;
693 if (action_id == ACT_MENU_DELETE) {
694 struct listbox_data *box;
696 box = get_listbox_widget_data(dlg_item);
697 if (box->ops
698 && box->ops->delete
699 && box->ops->can_delete)
700 push_hierbox_delete_button(dlg_data,
701 widget_data);
703 return EVENT_PROCESSED;
706 /* Selecting a button; most probably ;). */
707 break;
709 case EVENT_INIT:
710 case EVENT_RESIZE:
711 case EVENT_REDRAW:
712 case EVENT_MOUSE:
713 case EVENT_ABORT:
714 break;
717 return EVENT_NOT_PROCESSED;
720 struct widget_ops listbox_ops = {
721 display_listbox,
722 init_listbox,
723 mouse_listbox,
724 kbd_listbox,
725 NULL,
726 NULL,