rewrite: update default dumb and smart prefixes
[elinks/elinks-j605.git] / src / bfu / listbox.c
blob7a63d312ac763597142ea2a254a1cf6aca5f220b
1 /* Listbox widget implementation. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <string.h>
9 #include "elinks.h"
11 #include "bfu/dialog.h"
12 #include "bfu/hierbox.h"
13 #include "bfu/listbox.h"
14 #include "config/kbdbind.h"
15 #include "intl/gettext/libintl.h"
16 #include "terminal/draw.h"
17 #include "terminal/mouse.h"
18 #include "terminal/terminal.h"
19 #include "util/color.h"
20 #include "util/conv.h"
21 #include "util/lists.h"
24 #define VERTICAL_LISTBOX_MARGIN 3
26 void
27 add_dlg_listbox(struct dialog *dlg, void *box_data)
29 struct widget *widget = &dlg->widgets[dlg->number_of_widgets++];
31 widget->type = WIDGET_LISTBOX;
32 widget->data = box_data;
35 struct listbox_data *
36 get_listbox_widget_data(struct widget_data *widget_data)
38 assert(widget_data->widget->type == WIDGET_LISTBOX);
39 return ((struct listbox_data *) widget_data->widget->data);
42 /* Layout for generic boxes */
43 void
44 dlg_format_listbox(struct dialog_data *dlg_data,
45 struct widget_data *widget_data,
46 int x, int *y, int w, int max_height, int *rw,
47 enum format_align align, int format_only)
49 int min, optimal_h, height;
51 /* Height bussiness follows: */
53 /* This is only weird heuristic, it could scale well I hope. */
54 optimal_h = max_height * 7 / 10 - VERTICAL_LISTBOX_MARGIN;
55 min = get_opt_int("ui.dialogs.listbox_min_height", NULL);
57 if (max_height - VERTICAL_LISTBOX_MARGIN < min) {
58 /* Big trouble: can't satisfy even the minimum :-(. */
59 height = max_height - VERTICAL_LISTBOX_MARGIN;
60 } else if (optimal_h < min) {
61 height = min;
62 } else {
63 height = optimal_h;
66 set_box(&widget_data->box, x, *y, w, height);
67 (*y) += height;
68 if (rw) *rw = w;
73 *,item00->prev
74 *|item00->root = NULL ,item10->prev
75 *|item00->child <----->|item10->root
76 *|item00->next <-. |item10->child [<->]
77 *| | `item10->next
78 *|item01->prev <-'
79 *|item01->root = NULL
80 *|item01->child [<->]
81 *|item01->next <-.
82 *| |
83 *|item02->prev <-'
84 *|item02->root = NULL ,item11->prev
85 *|item02->child <----->|item11->root ,item20->prev
86 *`item02->next \ |item11->child <----->|item20->root
87 * | |item11->next <-. |item20->child [<->]
88 * | | | `item20->next
89 * | |item12->prev <-'
90 * `-|item12->root
91 * | |item12->child [<->]
92 * | |item12->next <-.
93 * | | |
94 * | |item13->prev <-'
95 * `-|item13->root ,item21->prev
96 * |item13->child <----->|item21->root
97 * `item13->next |item21->child [<->]
98 * `item21->next
102 /* Traverse a hierarchic tree from @item by @offset items, calling @fn,
103 * if it is not NULL, on each item traversed (that is, each of the items
104 * that we move _through_; this means from the passed @item up to,
105 * but not including, the returned item).
107 * @offset may be negative to indicate that we should traverse upwards.
109 * Besides the current item, @fn is also passed @d, which is otherwise unused
110 * by traverse_listbox_items_list, and a pointer to @offset, which @fn can set
111 * to 0 to stop traversal or to other values to change the direction in which
112 * or the number of items over which we will traverse.
114 * @fn should return 1 if it freed its passed item, or return 0 otherwise.
116 * If the passed @offset is zero, we set @offset to 1 and traverse thru
117 * the list (down) until either we reach the end or @fn sets @offset to 0. */
118 /* From the box structure, we should use only 'items' here. */
119 struct listbox_item *
120 traverse_listbox_items_list(struct listbox_item *item, struct listbox_data *box,
121 int offset, int follow_visible,
122 int (*fn)(struct listbox_item *, void *, int *),
123 void *d)
125 struct listbox_item *visible_item = item;
126 int levmove = 0;
127 int stop = 0;
128 int infinite = !offset;
130 if (!item) return NULL;
132 if (infinite)
133 offset = 1;
135 while (offset && !stop) {
136 /* We need to cache these. Or what will happen if something
137 * will free us item too early? However, we rely on item
138 * being at least NULL in that case. */
139 /* There must be no orphaned listbox_items. No free()d roots
140 * and no dangling children. */
141 #define item_cache(item) \
142 do { \
143 croot = box->ops->get_root(item); cprev = item->prev; cnext = item->next; \
144 } while (0)
145 struct listbox_item *croot, *cprev, *cnext;
147 item_cache(item);
149 if (fn && (!follow_visible || item->visible)) {
150 if (fn(item, d, &offset)) {
151 /* We was free()d! Let's try to carry on w/ the
152 * cached coordinates. */
153 item = NULL;
155 if (!offset) {
156 infinite = 0; /* safety (matches) */
157 continue;
161 if (offset > 0) {
162 /* Otherwise we climb back up when last item in root
163 * is a folder. */
164 struct listbox_item *cragsman = NULL;
166 /* Direction DOWN. */
168 if (!infinite) offset--;
170 if (item && !list_empty(item->child) && item->expanded
171 && (!follow_visible || item->visible)) {
172 /* Descend to children. */
173 item = item->child.next;
174 item_cache(item);
175 goto done_down;
178 while (croot
179 && (void *) cnext == &croot->child) {
180 /* Last item in a non-root list, climb to your
181 * root. */
182 if (!cragsman) cragsman = item;
183 item = croot;
184 item_cache(item);
187 if (!croot && (!cnext || (void *) cnext == box->items)) {
188 /* Last item in the root list, quit.. */
189 stop = 1;
190 if (cragsman) {
191 /* ..and fall back where we were. */
192 item = cragsman;
193 item_cache(item);
197 /* We're not at the end of anything, go on. */
198 if (!stop) {
199 item = cnext;
200 item_cache(item);
203 done_down:
204 if (!item || (follow_visible && !item->visible)) {
205 offset++;
206 } else {
207 visible_item = item;
210 } else {
211 /* Direction UP. */
213 if (!infinite) offset++;
215 if (croot
216 && (void *) cprev == &croot->child) {
217 /* First item in a non-root list, climb to your
218 * root. */
219 item = croot;
220 item_cache(item);
221 levmove = 1;
224 if (!croot && (void *) cprev == box->items) {
225 /* First item in the root list, quit. */
226 stop = 1;
227 levmove = 1;
230 /* We're not at the start of anything, go on. */
231 if (!levmove && !stop) {
232 item = cprev;
233 item_cache(item);
235 while (item && !list_empty(item->child)
236 && item->expanded
237 && (!follow_visible || item->visible)) {
238 /* Descend to children. */
239 item = item->child.prev;
240 item_cache(item);
242 } else {
243 levmove = 0;
246 if (!item || (follow_visible && !item->visible)) {
247 offset--;
248 } else {
249 visible_item = item;
252 #undef item_cache
255 return visible_item;
258 static int
259 calc_dist(struct listbox_item *item, void *data_, int *offset)
261 int *item_offset = data_;
263 if (*offset < 0)
264 --*item_offset;
265 else if (*offset > 0)
266 ++*item_offset;
268 return 0;
271 /* Moves the selected item by [dist] items. If [dist] is out of the current
272 * range, the selected item is moved to the extreme (ie, the top or bottom) */
273 void
274 listbox_sel_move(struct widget_data *widget_data, int dist)
276 struct listbox_data *box = get_listbox_widget_data(widget_data);
278 if (list_empty(*box->items)) return;
280 if (!box->top) box->top = box->items->next;
281 if (!box->sel) box->sel = box->top;
283 /* We want to have these visible if possible. */
284 if (box->top && !box->top->visible) {
285 box->top = traverse_listbox_items_list(box->top, box,
286 1, 1, NULL, NULL);
287 box->sel = box->top;
290 if (!dist && !box->sel->visible) dist = 1;
292 if (dist) {
293 box->sel = traverse_listbox_items_list(box->sel, box, dist, 1,
294 calc_dist,
295 &box->sel_offset);
296 /* box->sel_offset becomes the offset of the new box->sel
297 * from box->top. */
300 if (box->sel_offset < 0) {
301 /* We must scroll up. */
302 box->sel_offset = 0;
303 box->top = box->sel;
304 } else if (box->sel_offset >= widget_data->box.height) {
305 /* We must scroll down. */
306 box->sel_offset = widget_data->box.height - 1;
307 box->top = traverse_listbox_items_list(box->sel, box,
308 1 - widget_data->box.height,
309 1, NULL, NULL);
313 static int
314 test_search(struct listbox_item *item, void *data_, int *offset)
316 struct listbox_context *listbox_context = data_;
318 if (item != listbox_context->item)
319 listbox_context->offset++;
320 else
321 *offset = 0;
323 return 0;
326 static int
327 listbox_item_offset(struct listbox_data *box, struct listbox_item *item)
329 struct listbox_context ctx;
331 memset(&ctx, 0, sizeof(ctx));
332 ctx.item = item;
333 ctx.offset = 0;
335 traverse_listbox_items_list(box->items->next, box, 0, 1, test_search, &ctx);
337 return ctx.offset;
340 void
341 listbox_sel(struct widget_data *widget_data, struct listbox_item *item)
343 struct listbox_data *box = get_listbox_widget_data(widget_data);
345 listbox_sel_move(widget_data,
346 listbox_item_offset(box, item)
347 - listbox_item_offset(box, box->sel));
351 /* Takes care about rendering of each listbox item. */
352 static int
353 display_listbox_item(struct listbox_item *item, void *data_, int *offset)
355 struct listbox_context *data = data_;
356 int len; /* Length of the current text field. */
357 struct color_pair *tree_color, *text_color;
358 int depth = item->depth + 1;
359 int d;
360 int x, y;
362 tree_color = get_bfu_color(data->term, "menu.normal");
363 if (item == data->box->sel) {
364 text_color = get_bfu_color(data->term, "menu.selected");
366 } else if (item->marked) {
367 text_color = get_bfu_color(data->term, "menu.marked");
369 } else {
370 text_color = tree_color;
373 y = data->widget_data->box.y + data->offset;
374 for (d = 0; d < depth - 1; d++) {
375 struct listbox_item *root = item;
376 struct listbox_item *child = item;
377 int i, x;
379 for (i = depth - d; i; i--) {
380 child = root;
381 if (root) root = data->box->ops->get_root(root);
384 /* XXX */
385 x = data->widget_data->box.x + d * 5;
386 draw_text(data->term, x, y, " ", 5, 0, tree_color);
388 if (root ? root->child.prev == child
389 : data->box->items->prev == child)
390 continue; /* We were the last branch. */
392 draw_border_char(data->term, x + 1, y, BORDER_SVLINE, tree_color);
395 if (depth) {
396 enum border_char str[5] =
397 { 32, BORDER_SRTEE, BORDER_SHLINE, BORDER_SHLINE, 32 };
398 int i;
400 switch (item->type) {
401 case BI_LEAF:
402 case BI_SEPARATOR:
404 const struct listbox_item *prev;
406 prev = traverse_listbox_items_list(item, data->box,
407 -1, 1, NULL, NULL);
409 if (item == prev) {
410 /* There is no visible item before @item, so it
411 * must be the first item in the listbox. */
412 str[1] = BORDER_SULCORNER;
413 } else {
414 const struct listbox_item *next;
416 next = traverse_listbox_items_list(item,
417 data->box, 1, 1, NULL, NULL);
419 if (item == next
420 || item->depth != next->depth) {
421 /* There is no visible item after @item
422 * at the same depth, so it must be the
423 * last in its folder. */
424 str[1] = BORDER_SDLCORNER;
427 break;
429 case BI_FOLDER:
430 str[0] = '[';
431 str[1] = (item->expanded) ? '-' : '+';
432 str[2] = ']';
433 break;
434 default:
435 INTERNAL("Unknown item type");
436 break;
439 if (item->marked) str[4] = '*';
441 x = data->widget_data->box.x + (depth - 1) * 5;
442 for (i = 0; i < 5; i++) {
443 draw_border_char(data->term, x + i, y, str[i], tree_color);
447 x = data->widget_data->box.x + depth * 5;
449 if (item->type == BI_SEPARATOR) {
450 int i;
451 int width = data->widget_data->box.width - depth * 5;
453 for (i = 0; i < width; i++) {
454 draw_border_char(data->term, x + i, y, BORDER_SHLINE, text_color);
457 } else if (data->box->ops && data->box->ops->draw) {
458 int width = data->widget_data->box.width - depth * 5;
460 data->box->ops->draw(item, data, x, y, width);
462 } else {
463 unsigned char *text;
464 const struct listbox_ops *ops = data->box->ops;
465 int len_bytes;
467 assert(ops && ops->get_info);
469 text = ops->get_text(item, data->term);
470 if (!text) return 0;
472 len = strlen(text);
473 int_upper_bound(&len, int_max(0, data->widget_data->box.width - depth * 5));
474 #ifdef CONFIG_UTF8
475 if (data->term->utf8_cp)
476 len_bytes = utf8_cells2bytes(text, len, NULL);
477 else
478 #endif /* CONFIG_UTF8 */
479 len_bytes = len;
481 draw_text(data->term, x, y, text, len_bytes, 0, text_color);
483 mem_free(text);
486 if (item == data->box->sel) {
487 /* For blind users: */
488 x = data->widget_data->box.x + 5 + item->depth * 5;
489 set_cursor(data->term, x, y, 1);
490 set_window_ptr(data->dlg_data->win, x, y);
493 data->offset++;
495 return 0;
498 /* Displays a dialog box */
499 static widget_handler_status_T
500 display_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
502 struct terminal *term = dlg_data->win->term;
503 struct listbox_data *box = get_listbox_widget_data(widget_data);
504 struct listbox_context data;
506 listbox_sel_move(widget_data, 0);
508 draw_box(term, &widget_data->box, ' ', 0,
509 get_bfu_color(term, "menu.normal"));
511 memset(&data, 0, sizeof(data));
512 data.term = term;
513 data.widget_data = widget_data;
514 data.box = box;
515 data.dlg_data = dlg_data;
517 traverse_listbox_items_list(box->top, box, widget_data->box.height,
518 1, display_listbox_item, &data);
520 return EVENT_PROCESSED;
523 static int
524 check_old_state(struct listbox_item *item, void *info_, int *offset)
526 struct listbox_data *box = info_;
528 if (box->sel == item)
529 box->sel = NULL;
530 else if (box->top == item)
531 box->top = NULL;
533 if (!box->sel && !box->top)
534 *offset = 0;
536 return 0;
539 static widget_handler_status_T
540 init_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
542 struct hierbox_browser *browser = dlg_data->dlg->udata2;
543 struct listbox_data *box = get_listbox_widget_data(widget_data);
545 /* Try to restore the position from last time */
546 if (!list_empty(browser->root.child) && browser->box_data.items) {
547 copy_struct(box, &browser->box_data);
549 traverse_listbox_items_list(browser->root.child.next, box, 0, 0,
550 check_old_state, box);
552 box->sel = (!box->sel) ? browser->box_data.sel : NULL;
553 box->top = (!box->top) ? browser->box_data.top : NULL;
554 if (!box->sel) box->sel = box->top;
555 if (!box->top) box->top = box->sel;
558 box->ops = browser->ops;
559 box->items = &browser->root.child;
561 add_to_list(browser->boxes, box);
562 return EVENT_PROCESSED;
565 static widget_handler_status_T
566 mouse_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
568 #ifdef CONFIG_MOUSE
569 struct listbox_data *box = get_listbox_widget_data(widget_data);
570 struct term_event *ev = dlg_data->term_event;
572 if (!list_empty(*box->items)) {
573 if (!box->top) box->top = box->items->next;
574 if (!box->sel) box->sel = box->top;
577 if (check_mouse_action(ev, B_DOWN)) {
578 struct widget_data *dlg_item = dlg_data->widgets_data;
580 switch (get_mouse_button(ev)) {
581 case B_WHEEL_DOWN:
582 listbox_sel_move(dlg_item, 1);
583 display_widget(dlg_data, dlg_item);
584 return EVENT_PROCESSED;
586 case B_WHEEL_UP:
587 listbox_sel_move(dlg_item, -1);
588 display_widget(dlg_data, dlg_item);
589 return EVENT_PROCESSED;
593 if (check_mouse_wheel(ev))
594 return EVENT_NOT_PROCESSED;
596 if (check_mouse_position(ev, &widget_data->box)) {
597 /* Clicked in the box. */
598 int offset = ev->info.mouse.y - widget_data->box.y;
600 box->sel_offset = offset;
601 box->sel = offset ? traverse_listbox_items_list(box->top, box,
602 offset, 1,
603 NULL, NULL)
604 : box->top;
606 if (box->sel && box->sel->type == BI_FOLDER) {
607 int xdepth = widget_data->box.x + box->sel->depth * 5;
608 int x = ev->info.mouse.x;
610 if (x >= xdepth && x <= xdepth + 2)
611 box->sel->expanded = !box->sel->expanded;
614 display_widget(dlg_data, widget_data);
616 return EVENT_PROCESSED;
619 #endif /* CONFIG_MOUSE */
621 return EVENT_NOT_PROCESSED;
624 static widget_handler_status_T
625 do_kbd_listbox_action(enum menu_action action_id, struct dialog_data *dlg_data,
626 struct widget_data *widget_data)
628 struct widget_data *dlg_item = dlg_data->widgets_data;
630 switch (action_id) {
631 case ACT_MENU_DOWN:
632 listbox_sel_move(dlg_item, 1);
633 display_widget(dlg_data, dlg_item);
635 return EVENT_PROCESSED;
637 case ACT_MENU_UP:
638 listbox_sel_move(dlg_item, -1);
639 display_widget(dlg_data, dlg_item);
641 return EVENT_PROCESSED;
643 case ACT_MENU_PAGE_DOWN:
645 struct listbox_data *box;
647 box = get_listbox_widget_data(dlg_item);
649 listbox_sel_move(dlg_item,
650 2 * dlg_item->box.height
651 - box->sel_offset - 1);
653 display_widget(dlg_data, dlg_item);
655 return EVENT_PROCESSED;
658 case ACT_MENU_PAGE_UP:
660 struct listbox_data *box;
662 box = get_listbox_widget_data(dlg_item);
664 listbox_sel_move(dlg_item,
665 -dlg_item->box.height
666 - box->sel_offset);
668 display_widget(dlg_data, dlg_item);
670 return EVENT_PROCESSED;
673 case ACT_MENU_HOME:
674 listbox_sel_move(dlg_item, -INT_MAX);
675 display_widget(dlg_data, dlg_item);
677 return EVENT_PROCESSED;
679 case ACT_MENU_END:
680 listbox_sel_move(dlg_item, INT_MAX);
681 display_widget(dlg_data, dlg_item);
683 return EVENT_PROCESSED;
685 case ACT_MENU_MARK_ITEM:
687 struct listbox_data *box;
689 box = get_listbox_widget_data(dlg_item);
690 if (box->sel) {
691 box->sel->marked = !box->sel->marked;
692 listbox_sel_move(dlg_item, 1);
694 display_widget(dlg_data, dlg_item);
696 return EVENT_PROCESSED;
699 case ACT_MENU_DELETE:
701 struct listbox_data *box;
703 box = get_listbox_widget_data(dlg_item);
704 if (box->ops
705 && box->ops->delete
706 && box->ops->can_delete)
707 push_hierbox_delete_button(dlg_data,
708 widget_data);
710 return EVENT_PROCESSED;
713 default:
714 break;
717 return EVENT_NOT_PROCESSED;
720 static widget_handler_status_T
721 kbd_listbox(struct dialog_data *dlg_data, struct widget_data *widget_data)
723 struct term_event *ev = dlg_data->term_event;
725 /* Not a pure listbox, but you're not supposed to use this outside of
726 * the listbox browser anyway, so what.. */
728 switch (ev->ev) {
729 case EVENT_KBD:
731 enum menu_action action_id;
733 action_id = kbd_action(KEYMAP_MENU, ev, NULL);
734 return do_kbd_listbox_action(action_id, dlg_data, widget_data);
736 case EVENT_INIT:
737 case EVENT_RESIZE:
738 case EVENT_REDRAW:
739 case EVENT_MOUSE:
740 case EVENT_ABORT:
741 break;
744 return EVENT_NOT_PROCESSED;
747 const struct widget_ops listbox_ops = {
748 display_listbox,
749 init_listbox,
750 mouse_listbox,
751 kbd_listbox,
752 NULL,
753 NULL,