1 /* Listbox widget implementation. */
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
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
;
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 */
44 dlg_format_listbox(struct terminal
*term
, 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
) {
66 set_box(&widget_data
->box
, x
, *y
, w
, height
);
74 *|item00->root = NULL ,item10->prev
75 *|item00->child <----->|item10->root
76 *|item00->next <-. |item10->child [<->]
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 [<->]
91 * | |item12->child [<->]
95 * `-|item13->root ,item21->prev
96 * |item13->child <----->|item21->root
97 * `item13->next |item21->child [<->]
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 *),
125 struct listbox_item
*visible_item
= item
;
128 int infinite
= !offset
;
130 if (!item
) return NULL
;
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) \
143 croot = box->ops->get_root(item); cprev = item->prev; cnext = item->next; \
145 struct listbox_item
*croot
, *cprev
, *cnext
;
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. */
156 infinite
= 0; /* safety (matches) */
162 /* Otherwise we climb back up when last item in root
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
;
179 && (void *) cnext
== &croot
->child
) {
180 /* Last item in a non-root list, climb to your
182 if (!cragsman
) cragsman
= item
;
187 if (!croot
&& (!cnext
|| (void *) cnext
== box
->items
)) {
188 /* Last item in the root list, quit.. */
191 /* ..and fall back where we were. */
197 /* We're not at the end of anything, go on. */
204 if (!item
|| (follow_visible
&& !item
->visible
)) {
213 if (!infinite
) offset
++;
216 && (void *) cprev
== &croot
->child
) {
217 /* First item in a non-root list, climb to your
224 if (!croot
&& (void *) cprev
== box
->items
) {
225 /* First item in the root list, quit. */
230 /* We're not at the start of anything, go on. */
231 if (!levmove
&& !stop
) {
235 while (item
&& !list_empty(item
->child
)
237 && (!follow_visible
|| item
->visible
)) {
238 /* Descend to children. */
239 item
= item
->child
.prev
;
246 if (!item
|| (follow_visible
&& !item
->visible
)) {
259 calc_dist(struct listbox_item
*item
, void *data_
, int *offset
)
261 int *item_offset
= data_
;
265 else if (*offset
> 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) */
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
,
290 if (!dist
&& !box
->sel
->visible
) dist
= 1;
293 box
->sel
= traverse_listbox_items_list(box
->sel
, box
, dist
, 1,
296 /* box->sel_offset becomes the offset of the new box->sel
300 if (box
->sel_offset
< 0) {
301 /* We must scroll up. */
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
,
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
++;
327 listbox_item_offset(struct listbox_data
*box
, struct listbox_item
*item
)
329 struct listbox_context ctx
;
331 memset(&ctx
, 0, sizeof(ctx
));
335 traverse_listbox_items_list(box
->items
->next
, box
, 0, 1, test_search
, &ctx
);
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. */
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;
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");
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
;
379 for (i
= depth
- d
; i
; i
--) {
381 if (root
) root
= data
->box
->ops
->get_root(root
);
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
);
396 enum border_char str
[5] =
397 { 32, BORDER_SRTEE
, BORDER_SHLINE
, BORDER_SHLINE
, 32 };
400 switch (item
->type
) {
404 struct listbox_item
*root
= data
->box
->ops
->get_root(item
);
407 if (item
== root
->child
.prev
) {
408 str
[1] = BORDER_SDLCORNER
;
411 LIST_OF(struct listbox_item
) *p
= data
->box
->items
;
413 if (p
->next
== item
) {
414 str
[1] = BORDER_SULCORNER
;
415 } else if (p
->prev
== item
) {
416 str
[1] = BORDER_SDLCORNER
;
423 str
[1] = (item
->expanded
) ? '-' : '+';
427 INTERNAL("Unknown item type");
431 if (item
->marked
) str
[4] = '*';
433 x
= data
->widget_data
->box
.x
+ (depth
- 1) * 5;
434 for (i
= 0; i
< 5; i
++) {
435 draw_border_char(data
->term
, x
+ i
, y
, str
[i
], tree_color
);
439 x
= data
->widget_data
->box
.x
+ depth
* 5;
441 if (item
->type
== BI_SEPARATOR
) {
443 int width
= data
->widget_data
->box
.width
- depth
* 5;
445 for (i
= 0; i
< width
; i
++) {
446 draw_border_char(data
->term
, x
+ i
, y
, BORDER_SHLINE
, text_color
);
449 } else if (data
->box
->ops
&& data
->box
->ops
->draw
) {
450 int width
= data
->widget_data
->box
.width
- depth
* 5;
452 data
->box
->ops
->draw(item
, data
, x
, y
, width
);
456 const struct listbox_ops
*ops
= data
->box
->ops
;
459 assert(ops
&& ops
->get_info
);
461 text
= ops
->get_text(item
, data
->term
);
465 int_upper_bound(&len
, int_max(0, data
->widget_data
->box
.width
- depth
* 5));
467 if (data
->term
->utf8_cp
)
468 len_bytes
= utf8_cells2bytes(text
, len
, NULL
);
470 #endif /* CONFIG_UTF8 */
473 draw_text(data
->term
, x
, y
, text
, len_bytes
, 0, text_color
);
478 if (item
== data
->box
->sel
) {
479 /* For blind users: */
480 x
= data
->widget_data
->box
.x
+ 5 + item
->depth
* 5;
481 set_cursor(data
->term
, x
, y
, 1);
482 set_window_ptr(data
->dlg_data
->win
, x
, y
);
490 /* Displays a dialog box */
491 static widget_handler_status_T
492 display_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
494 struct terminal
*term
= dlg_data
->win
->term
;
495 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
496 struct listbox_context data
;
498 listbox_sel_move(widget_data
, 0);
500 draw_box(term
, &widget_data
->box
, ' ', 0,
501 get_bfu_color(term
, "menu.normal"));
503 memset(&data
, 0, sizeof(data
));
505 data
.widget_data
= widget_data
;
507 data
.dlg_data
= dlg_data
;
509 traverse_listbox_items_list(box
->top
, box
, widget_data
->box
.height
,
510 1, display_listbox_item
, &data
);
512 return EVENT_PROCESSED
;
516 check_old_state(struct listbox_item
*item
, void *info_
, int *offset
)
518 struct listbox_data
*box
= info_
;
520 if (box
->sel
== item
)
522 else if (box
->top
== item
)
525 if (!box
->sel
&& !box
->top
)
531 static widget_handler_status_T
532 init_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
534 struct hierbox_browser
*browser
= dlg_data
->dlg
->udata2
;
535 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
537 /* Try to restore the position from last time */
538 if (!list_empty(browser
->root
.child
) && browser
->box_data
.items
) {
539 copy_struct(box
, &browser
->box_data
);
541 traverse_listbox_items_list(browser
->root
.child
.next
, box
, 0, 0,
542 check_old_state
, box
);
544 box
->sel
= (!box
->sel
) ? browser
->box_data
.sel
: NULL
;
545 box
->top
= (!box
->top
) ? browser
->box_data
.top
: NULL
;
546 if (!box
->sel
) box
->sel
= box
->top
;
547 if (!box
->top
) box
->top
= box
->sel
;
550 box
->ops
= browser
->ops
;
551 box
->items
= &browser
->root
.child
;
553 add_to_list(browser
->boxes
, box
);
554 return EVENT_PROCESSED
;
557 static widget_handler_status_T
558 mouse_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
561 struct listbox_data
*box
= get_listbox_widget_data(widget_data
);
562 struct term_event
*ev
= dlg_data
->term_event
;
564 if (!list_empty(*box
->items
)) {
565 if (!box
->top
) box
->top
= box
->items
->next
;
566 if (!box
->sel
) box
->sel
= box
->top
;
569 if (check_mouse_action(ev
, B_DOWN
)) {
570 struct widget_data
*dlg_item
= dlg_data
->widgets_data
;
572 switch (get_mouse_button(ev
)) {
574 listbox_sel_move(dlg_item
, 1);
575 display_widget(dlg_data
, dlg_item
);
576 return EVENT_PROCESSED
;
579 listbox_sel_move(dlg_item
, -1);
580 display_widget(dlg_data
, dlg_item
);
581 return EVENT_PROCESSED
;
585 if (check_mouse_wheel(ev
))
586 return EVENT_NOT_PROCESSED
;
588 if (check_mouse_position(ev
, &widget_data
->box
)) {
589 /* Clicked in the box. */
590 int offset
= ev
->info
.mouse
.y
- widget_data
->box
.y
;
592 box
->sel_offset
= offset
;
593 box
->sel
= offset
? traverse_listbox_items_list(box
->top
, box
,
598 if (box
->sel
&& box
->sel
->type
== BI_FOLDER
) {
599 int xdepth
= widget_data
->box
.x
+ box
->sel
->depth
* 5;
600 int x
= ev
->info
.mouse
.x
;
602 if (x
>= xdepth
&& x
<= xdepth
+ 2)
603 box
->sel
->expanded
= !box
->sel
->expanded
;
606 display_widget(dlg_data
, widget_data
);
608 return EVENT_PROCESSED
;
611 #endif /* CONFIG_MOUSE */
613 return EVENT_NOT_PROCESSED
;
616 static widget_handler_status_T
617 do_kbd_listbox_action(enum menu_action action_id
, struct dialog_data
*dlg_data
,
618 struct widget_data
*widget_data
)
620 struct widget_data
*dlg_item
= dlg_data
->widgets_data
;
624 listbox_sel_move(dlg_item
, 1);
625 display_widget(dlg_data
, dlg_item
);
627 return EVENT_PROCESSED
;
630 listbox_sel_move(dlg_item
, -1);
631 display_widget(dlg_data
, dlg_item
);
633 return EVENT_PROCESSED
;
635 case ACT_MENU_PAGE_DOWN
:
637 struct listbox_data
*box
;
639 box
= get_listbox_widget_data(dlg_item
);
641 listbox_sel_move(dlg_item
,
642 2 * dlg_item
->box
.height
643 - box
->sel_offset
- 1);
645 display_widget(dlg_data
, dlg_item
);
647 return EVENT_PROCESSED
;
650 case ACT_MENU_PAGE_UP
:
652 struct listbox_data
*box
;
654 box
= get_listbox_widget_data(dlg_item
);
656 listbox_sel_move(dlg_item
,
657 -dlg_item
->box
.height
660 display_widget(dlg_data
, dlg_item
);
662 return EVENT_PROCESSED
;
666 listbox_sel_move(dlg_item
, -INT_MAX
);
667 display_widget(dlg_data
, dlg_item
);
669 return EVENT_PROCESSED
;
672 listbox_sel_move(dlg_item
, INT_MAX
);
673 display_widget(dlg_data
, dlg_item
);
675 return EVENT_PROCESSED
;
677 case ACT_MENU_MARK_ITEM
:
679 struct listbox_data
*box
;
681 box
= get_listbox_widget_data(dlg_item
);
683 box
->sel
->marked
= !box
->sel
->marked
;
684 listbox_sel_move(dlg_item
, 1);
686 display_widget(dlg_data
, dlg_item
);
688 return EVENT_PROCESSED
;
691 case ACT_MENU_DELETE
:
693 struct listbox_data
*box
;
695 box
= get_listbox_widget_data(dlg_item
);
698 && box
->ops
->can_delete
)
699 push_hierbox_delete_button(dlg_data
,
702 return EVENT_PROCESSED
;
709 return EVENT_NOT_PROCESSED
;
712 static widget_handler_status_T
713 kbd_listbox(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
715 struct term_event
*ev
= dlg_data
->term_event
;
717 /* Not a pure listbox, but you're not supposed to use this outside of
718 * the listbox browser anyway, so what.. */
723 enum menu_action action_id
;
725 action_id
= kbd_action(KEYMAP_MENU
, ev
, NULL
);
726 return do_kbd_listbox_action(action_id
, dlg_data
, widget_data
);
736 return EVENT_NOT_PROCESSED
;
739 const struct widget_ops listbox_ops
= {