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 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 const struct listbox_item
*prev
;
406 prev
= traverse_listbox_items_list(item
, data
->box
,
410 /* There is no visible item before @item, so it
411 * must be the first item in the listbox. */
412 str
[1] = BORDER_SULCORNER
;
414 const struct listbox_item
*next
;
416 next
= traverse_listbox_items_list(item
,
417 data
->box
, 1, 1, NULL
, NULL
);
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
;
431 str
[1] = (item
->expanded
) ? '-' : '+';
435 INTERNAL("Unknown item type");
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
) {
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
);
464 const struct listbox_ops
*ops
= data
->box
->ops
;
467 assert(ops
&& ops
->get_info
);
469 text
= ops
->get_text(item
, data
->term
);
473 int_upper_bound(&len
, int_max(0, data
->widget_data
->box
.width
- depth
* 5));
475 if (data
->term
->utf8_cp
)
476 len_bytes
= utf8_cells2bytes(text
, len
, NULL
);
478 #endif /* CONFIG_UTF8 */
481 draw_text(data
->term
, x
, y
, text
, len_bytes
, 0, text_color
);
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
);
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
));
513 data
.widget_data
= widget_data
;
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
;
524 check_old_state(struct listbox_item
*item
, void *info_
, int *offset
)
526 struct listbox_data
*box
= info_
;
528 if (box
->sel
== item
)
530 else if (box
->top
== item
)
533 if (!box
->sel
&& !box
->top
)
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
)
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
)) {
582 listbox_sel_move(dlg_item
, 1);
583 display_widget(dlg_data
, dlg_item
);
584 return EVENT_PROCESSED
;
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
,
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
;
632 listbox_sel_move(dlg_item
, 1);
633 display_widget(dlg_data
, dlg_item
);
635 return EVENT_PROCESSED
;
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
668 display_widget(dlg_data
, dlg_item
);
670 return EVENT_PROCESSED
;
674 listbox_sel_move(dlg_item
, -INT_MAX
);
675 display_widget(dlg_data
, dlg_item
);
677 return EVENT_PROCESSED
;
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
);
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
);
706 && box
->ops
->can_delete
)
707 push_hierbox_delete_button(dlg_data
,
710 return EVENT_PROCESSED
;
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.. */
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
);
744 return EVENT_NOT_PROCESSED
;
747 const struct widget_ops listbox_ops
= {